Edge Content

Integration Guide

Edge Extension 集成指南

面向下游开发者的架构说明与接入契约。本文解释 Edge Content 为什么把权威留在 Host、把体验放在边缘, 以及如何用一条脚本接入老旧系统、用一份契约构建受信 Extension。

印有红色 Edge 标志的档案卡片,旁边放着一支铅笔
Figure 01 — Edge Content Integration Kit v1

01 · Principles

权威在 Host,体验在边缘

传统营销内容审核分散在表格、聊天和邮件中:编辑不知道遵循哪一版规则, 审核意见与实际修改分离,批准后缺少不可变快照。Edge Content 的回答不是再造一个巨型系统,而是明确分工——Host 继续作为记录事实与流程状态的权威系统,受信 Extension 作为面向具体审校任务的边缘工作台,二者只通过受保护 API 和受约束的消息协议协作。

以下六条产品原则是所有接入决策的出发点。它们同时约束 Host 开发者、Extension 开发者和部署方,任何实现细节与原则冲突时,以原则为准。

01

Host 是权威系统

身份、权限、工作流、规则版本、草稿版本和批准快照一律以 API 数据为准,客户端不持有权威状态。

02

Extension 是任务工作台

Extension 可以优化审校体验,但不能绕过 Host 权限,也不能自行决定流程状态。

03

上下文引用代替数据搬运

URL 和消息只携带不透明 ID 与协调元数据,完整业务数据由 API 按当前身份重新获取。

04

一套契约,两种形态

外链与 iframe 使用同一个 Extension 应用和同一份业务契约,不产生两套权限、数据或保存逻辑。

05

人工拥有最终决定权

AI 可以发现、解释和建议,但不能批准或发布,也绝不自动保存任何修改。

06

一切决定绑定版本

审核决定与 AI 结果必须绑定具体草稿版本和规则版本,不能模糊地附着在“最新内容”上。

02 · Architecture

三个 Origin,一条信任链

系统由 Host、静态 Extension 与 Python API Server 三个独立 Origin 构成。浏览器会话由 Host 登录建立,API 设置 HttpOnly、SameSite 会话 Cookie 并签发会话绑定的 CSRF token;Extension 无论以外链还是 iframe 打开,都携带同一 Cookie 调用受保护 API。业务数据从不经过 postMessage——消息只做协调,权威读取永远回到 API。

Host 权威系统 登录 · 工作队列 · 审核状态 · 规则 · 审计 Host Manifest(部署配置) Host Loader v1 → Host Bridge v1 页面匹配 · Context resolver · DOM placement · 生命周期 metadata-only postMessage Trusted Extensions + Extension Runtime v1 product-content-review · marketing-claims-checklist protected API(Cookie + CSRF + CORS) Python API Server 会话 · 对象权限 · 工作流 · 乐观锁 · 审计 仅服务端调用(Phase 3) AI Provider 按服务端固化的版本与规则返回结构化分析
Figure 02 — 组件关系与信任链
三块屏幕分别显示 Host、受保护 API 与 Edge Extension 界面,示意三个独立 Origin 协同工作
Figure 03 — Host、API 与 Extension 运行于三个独立 Origin

权威数据归属

所有业务数据只有一个权威来源。Extension 持有的只是按当前身份获取的视图;未保存输入只存在于 Extension 内存中,不写入 Web Storage。

数据 权威归属 Extension 使用方式
当前用户与权限 API 查询 session 与 Context permissions,并据此控制可用操作
草稿与版本 API 编辑后以 expected_version 乐观锁保存
评论与检查记录 API 按各 Extension 的职责只读同步或提交结构化记录
审核状态与决定 API 展示状态、辅助完成检查,不自行流转流程
AI 运行与发现 API 发起运行、查看发现、处理建议,结果绑定版本
未保存输入 Extension 内存 临时持有并上报 dirty 状态,不写 Web Storage

为什么需要三个 Origin

Origin 隔离让 Extension 可以被独立部署、独立审查、独立下线;浏览器同源策略与精确 CORS、CSRF、sandbox 配置共同构成信任边界,而不是依赖前端代码“自觉”。

03 · Host Loader

一脚本接入老旧系统

Host Loader 面向难以修改源代码的老旧系统。Host 不需要为每个 Extension 编写 iframe、外链或消息处理代码,只需在公共 layout 或部署响应层注入一个版本固定的脚本。Loader 读取声明式 Host Manifest,只有当前 pathname 命中 placement 时才继续加载同版本的 Bridge 与样式,解析业务 Context,等待目标 DOM,并创建受控的 Extension UI。

注入方式

<script
  defer
  src="https://edge.example.com/sdk/edge-host-loader.v1.js"
  data-manifest="https://edge.example.com/config/legacy-host.json"
></script>

按推荐顺序选择注入位置:公共 layout、应用服务器的模板/响应过滤、 或反向代理对明确 HTML 响应的注入。响应级注入必须限制 Content-Type、路径和上游应用;生产环境同步配置 CSP script-src、style-src、frame-src 与 Extension 的 frame-ancestors。不要使用自动跟随 latest 的远程脚本,也不要通过标签管理器赋予可变第三方代码完整的 Host DOM 权限。

声明式 Manifest

一个 placement 声明页面匹配、Context 定位与解析、DOM 挂载位置、Extension 地址和通用 UI 文本。Manifest 只包含数据,不允许脚本、HTML 或任意事件 callback。

{
  "id": "claims-checklist-workspace",
  "when": { "paths": ["/release.html"] },
  "context": {
    "source": { "kind": "query", "name": "id" },
    "resolve": {
      "pathTemplate": "/api/releases/{value}",
      "responsePath": "context_id"
    }
  },
  "mount": {
    "selector": "#release-content",
    "position": "append",
    "match": "first",
    "waitForMs": 10000
  },
  "extension": {
    "id": "marketing-claims-checklist",
    "url": "https://claims.extensions.example.com/index.html",
    "title": "营销主张检查清单 Extension"
  },
  "display": "standalone",
  "ui": {
    "kind": "确定性规则检查",
    "title": "营销主张检查清单",
    "badge": "No AI",
    "standaloneLabel": "打开检查清单"
  }
}
display Host 表现 ui 必填
embedded 仅页内 sandboxed iframe dirtyLabel(可选 action)
standalone 仅新窗口外链按钮 standaloneLabel

DOM placement

mount.selector 必须指向真实 DOM 元素;CSS 伪元素不能承载 iframe,会被部署校验拒绝。Loader 使用 MutationObserver 等待延迟出现的 selector,并在 waitForMs 超时后安全失败,不阻塞原 Host。

position DOM 行为 对应直觉
before 插入为目标的前一个兄弟节点 元素外部之前
prepend 插入为目标的第一个子节点 类似 ::before
append / inside 插入为目标的最后一个子节点 类似 ::after
after 插入为目标的后一个兄弟节点 元素外部之后
replace 替换目标元素 高风险,谨慎使用

Context 定位与解析

URL、DOM 和全局变量提供的值都只是 locator,不代表权限。推荐通过 context.resolve 调用受保护 API,把 hostId + legacy locator 映射为不透明 Edge Context ID;解析请求始终使用 credentials: include,API 仍需校验用户、对象权限和 CORS。Manifest 不支持 eval、任意 resolver function 或从 DOM 抓取完整业务正文。

kind 配置 示例
query name ?id=legacy-release-id
path 含 {value} 的 pattern /orders/{value}
attribute selector + attribute data-order-id
input selector 隐藏 input 的 value
global 安全属性路径 LegacyApp.currentOrder.id

Loader 自动提供的 UI 与协调

  • 通用 UI。标题、说明、可选 badge、只携带 Context ID 与 mode 的外链、连接与 dirty 状态、save/refresh 操作,以及由 Bridge 管理的 sandboxed iframe。
  • 多 Extension 协调。一个 Extension 保存或评论后,Loader 自动刷新同页其他 Extension,并向 Host 发出 metadata-only CustomEvent。
  • 作用域样式。全部 Loader DOM 使用 edge-loader- class 与 data-edge-* 属性,不依赖也不修改 Host 业务样式。
// 请求所有已连接 Extension 从 API 刷新
document.dispatchEvent(
  new CustomEvent("edgehost:refresh-extensions")
);

// Extension 保存或评论后,Loader 发出:
// edgehost:content-changed
// detail 只包含 hostId、placementId、extensionId 和事件类型

当前边界

v1 以完整页面导航和精确 pathname 匹配为基线;SPA 无刷新 route rescan 尚未实现。Loader 解决 UI 接入,不会把不可信 Host 用户名转换为 Edge 身份——真实旧系统仍需共享 SSO、独立 Edge 登录或网关签发的受限身份凭据。

04 · Host Bridge

可选的 Bridge 深度集成

能够修改 Host 应用代码时,可以跳过 Loader,直接部署 Host Bridge 获得完整控制能力。Bridge 与 Loader 遵循同一份消息协议和版本契约, 二者可共存:Loader 是默认接入层,Bridge 是深度集成选项。

嵌入 Extension

Host 页面只需一个容器;EdgeHost.mount() 接受 CSS selector、普通容器元素或既有 iframe。

const controller = EdgeHost.mount("#content-review-extension", {
  extensionId: "product-content-review",
  extensionUrl:
    "https://extensions.example.com/product-content-review/index.html",
  contextId: release.extensionContextId,
  title: "商品内容审校",
  minHeight: 480,
  maxHeight: 960,
  onEvent(event) {
    switch (event.type) {
      case "extension.saved":
      case "extension.comment-added":
        refreshReleaseFromApi();
        break;
      case "extension.auth-required":
        redirectToLogin();
        break;
    }
  }
});

Bridge 自动完成:生成只含 Context ID 与 mode 的 iframe URL、设置 sandbox 与 referrer policy、固定精确 Extension Origin、集中校验和分发消息、响应 extension.ready、 限制并应用建议高度、生成 Event ID,并在页面销毁时清理监听器。

独立窗口外链

const standaloneUrl = EdgeHost.createExtensionUrl({
  extensionUrl:
    "https://extensions.example.com/product-content-review/index.html",
  contextId: release.extensionContextId,
  mode: "standalone"
});

// 推荐使用普通链接,隔离 opener:
// <a target="_blank" rel="noopener noreferrer">
// 或由 Bridge 安全打开:
controller.openStandalone();

Host 可接收事件

事件 payload Host 推荐行为
extension.ready extensionId、mode、runtimeVersion 显示已连接
extension.resize height Bridge 已自动调整高度
extension.dirty-changed dirty 提示未保存修改
extension.saved version 从 API 重新读取权威内容
extension.comment-added commentId 从 API 重新读取评论
extension.auth-required 空对象 跳转登录
extension.error code、可选 status 显示有限错误提示并记录遥测

注意:事件不包含正文、评论内容、Cookie 或 CSRF token。收到 extension.saved 后的正确动作是回到 API 重新读取权威快照,而不是信任消息中的任何业务数据。

05 · Extension Contract

Extension 开发契约

Extension 是一个无构建的静态 HTML/CSS/JavaScript 应用,必须同时支持 standalone 与 embedded 两种模式。URL 输入仅限于不透明 UUID Context ID 和 mode;业务数据一律通过 Extension Runtime 从受保护 API 获取。Manifest 声明请求的 capabilities,但声明从不授予权限——API 仍按当前用户和 Context 校验。

禁止的实现模式

不使用 document.cookie、Local/Session Storage;不直接 fetch 或 postMessage;不访问父页面 DOM;不使用通配消息 Origin;不用 innerHTML 插入动态数据;不引入远程脚本、字体、分析或其他网络来源;不把凭据、CSRF、正文放入 URL 或消息。

最小接入

页面按 config → Runtime → 业务代码的固定顺序加载:

<script src="./config.js" defer></script>
<script src="/sdk/edge-extension-runtime.v1.js" defer></script>
<script src="./app.js" defer></script>
// config.js
window.MY_EXTENSION_CONFIG = Object.freeze({
  API_BASE: "https://api.example.com",
  HOST_BASE: "https://host.example.com"
});

// app.js — 创建 Runtime;非法 context/mode 抛出带 code 的 RuntimeError
const runtime = EdgeExtension.create({
  extensionId: "product-content-review",
  apiBase: MY_EXTENSION_CONFIG.API_BASE,
  hostBase: MY_EXTENSION_CONFIG.HOST_BASE
});

加载身份与 Context

try {
  const session = await runtime.authenticate();
  const context = await runtime.getContext();
  render(context, session.user);
  runtime.ready();
} catch (error) {
  if (error instanceof EdgeExtension.ApiError && error.status === 401) {
    handleLoggedOut(runtime.loginUrl());
  }
}

Runtime 自动完成:所有请求使用 credentials: "include"; CSRF token 只保存在 Runtime 内存;修改请求自动携带 X-CSRF-Token;禁止请求配置 API Origin 以外的地址;非 2xx 响应统一转换为带 status 与 payload 的 ApiError。未 authenticate() 就发起的修改请求会被直接拒绝。

调用业务 API

const draft = await runtime.request(
  `/api/extension-contexts/${runtime.contextId}/draft`,
  {
    method: "PATCH",
    json: {
      text: editor.value,
      expected_version: currentVersion
    }
  }
);

与 Host 协作

// 接收 Host 操作(消息已经过 Runtime 集中校验)
runtime.on("host.init", () => runtime.requestResize());
runtime.on("host.refresh", refreshFromApi);
runtime.on("host.save-request", saveDraft);

// 上报协调状态;独立窗口模式下安全返回 false
runtime.setDirty(true);
runtime.requestResize();
runtime.notifySaved(draft.version);
runtime.notifyCommentAdded(comment.id);
runtime.notifyAuthRequired();
runtime.reportError("version-conflict", 409);

在 pagehide 时调用 runtime.destroy() 清理消息监听与内存中的 session。可恢复错误(401、403、409、网络失败) 必须保留用户输入;viewer 等只读身份在 UI 与 API 两层同时被拒绝。

06 · Protocol

metadata-only 消息协议

Host 与 Extension 之间只交换协调信号,不交换业务数据。所有消息使用统一信封, 由 Host Bridge 与 Extension Runtime 在两端集中校验——业务代码从不直接处理原始 postMessage。

{
  "channel": "edge-extension",
  "protocolVersion": "1.0",
  "type": "extension.saved",
  "contextId": "opaque-context-id",
  "eventId": "unique-event-id",
  "payload": {}
}

双方必须校验:

  • 精确 event.origin 与精确 event.source;
  • channel 与 protocolVersion 完全匹配;
  • 当前 Context ID 与非空 Event ID;
  • 已知消息类型及其 payload 结构。
方向 消息 用途
Host → Extension host.init iframe 建立连接后初始化
Host → Extension host.refresh 请求 Extension 从 API 刷新
Host → Extension host.save-request 请求 Extension 保存当前本地修改
Extension → Host extension.ready Context 加载完毕
Extension → Host extension.resize 建议 iframe 高度
Extension → Host extension.dirty-changed 通知是否有未保存修改
Extension → Host extension.saved 通知某版本已保存,Host 随后自行刷新
Extension → Host extension.comment-added 通知评论已添加,Host 随后自行刷新
Extension → Host extension.auth-required 通知 Host 会话失效
Extension → Host extension.error 上报有限错误代码

新增事件的门槛

新需求优先复用现有 host.refresh 与“保存后刷新”模式。只有确需改善嵌入体验时才增加 metadata-only 事件;任何新增事件都不得携带正文、完整发现内容或凭据。

07 · Security & Versioning

安全要求与版本策略

Integration Kit 是内部、受信 Extension 的稳定接入层,而不是绕过后端集成的注入工具。 以下要求对 Host、Extension 与部署方同时生效:

  • 固定版本自托管。Loader、Bridge 与 Runtime 必须自托管或使用固定、可审计、带完整性校验的版本;不发布会被原地替换的 latest URL。
  • Manifest 只含数据。Host Manifest 是受信部署配置,只允许 Schema 内的声明数据,不允许脚本、HTML 或 callback。
  • 精确 Origin。不允许 postMessage(..., "*");Host 与 Extension 各自从配置计算对方的精确 Origin;CORS 使用精确 Origin 且只允许需要的方法与请求头。
  • 定位不等于授权。Context ID 仅用于定位,每次 API 请求仍做角色与对象级权限校验。
  • 敏感数据不出边界。不在 URL 或消息中放正文、身份、权限、Cookie、CSRF 或 AI 结果;Extension 不使用 Web Storage 或 document.cookie 保存敏感信息,业务代码不访问父页面 DOM。
  • 生产环境基线。HTTPS、HSTS、CSP、frame-src/frame-ancestors、Secure Cookie 与最小 sandbox 权限缺一不可;AI Provider 密钥只存在于服务端。
Edge 平台网络示意图:用户层、独立 Origin 层与独立服务层之间通过 HTTPS 与跨域安全策略连接
Figure 04 — 独立 Origin 与独立服务之间的网络隔离

版本策略

层 当前版本 兼容规则
Loader / Bridge / Runtime JS API 1.0.0(SemVer) patch 修缺陷;minor 增加可选 API,旧消费者继续工作;major 允许不兼容变化
Host ↔ Extension 消息协议 1.0 major 变化需要双方显式升级,不静默降级
Host Manifest Schema 1.0 独立于消息协议演进

Bridge 在 extension.ready 握手时校验 Extension ID、Runtime 版本存在、protocolVersion 完全匹配,以及 Context ID 与当前挂载一致。

08 · Extension Factory

构建一个新的 Extension

Extension Factory 是构建时开发工具:它用声明式 manifest、隔离脚手架、静态策略校验和 Fixture Harness,让人类开发者与 AI coding agent 共用同一份接入契约。Factory 不在浏览器中动态下载或安装 Extension,也不赋予 agent 访问 Host 源码、生产凭据或部署环境的权限。

创建隔离 workspace

uv run python scripts/new_extension.py \
  --id marketing-claims-checklist \
  --name "营销主张检查清单"

生成的 workspace 包含 manifest.json、TASK.md、 PRD.md、agent 指令与静态模板。启动 agent 前,负责人必须先固定 TASK、PRD 和 capabilities;agent 的写入权限应限制在单个 workspace,不获得生产凭据、生产数据或 AI Provider 密钥。

验证、Harness 与 Promotion

# 静态策略校验:Schema、脚本顺序、capability 与 API 使用一致性,
# 以及远程资源、直接 fetch/postMessage、Cookie/Storage、
# parent DOM、innerHTML、eval 等违规模式
uv run python scripts/validate_extension.py workspaces/<extension-id>

# 在真实 Host Bridge + Runtime + mock API 下运行浏览器 fixture:
# editor / viewer / conflict / unauthenticated
uv run python scripts/extension_harness.py \
  workspaces/<extension-id> --fixture editor

# 通过后安全 promotion:只部署运行资产,拒绝覆盖已有受信 Extension
uv run python scripts/promote_extension.py \
  workspaces/<extension-id> --dry-run

开发者清单

  1. 阅读 workspace 的 TASK.md、PRD.md、manifest 与仓库级 EXTENSION_AUTHORING.md。
  2. 在 manifest 中声明 capabilities 之后,再实现任何写操作。
  3. 只实现限定任务,不修改 Host、API 或共享 SDK。
  4. 加载版本固定的 Runtime,配置 API Base 与 Host Base,使用唯一稳定的 Extension ID。
  5. 调用 authenticate() 与 getContext();所有写操作通过 runtime.request()。
  6. 实现 standalone 与 embedded 两种布局;只通过 Runtime 发送协调事件。
  7. 覆盖 401、403、409、网络错误与 dirty 防丢失;保持业务行为确定性。
  8. 运行 validator 与 workspace 测试;汇报修改文件、验证结果与未解决风险。

生成的代码只是候选:capability 批准、集成与生产部署始终由人工控制。 extensions/product-content-review/ 是可运行的参考实现, marketing-claims-checklist 则证明了该契约可以被独立 coding agent 在隔离 workspace 中重复使用。

本文是接入导览,权威定义以仓库文档为准:产品边界见 PRD.md,Loader 部署见 HOST_LOADER.md,开发契约见 EXTENSION_AUTHORING.md,SDK 参考见 SDK.md。 Integration Kit 当前版本:Loader / Bridge / Runtime 1.0.0,消息协议 1.0。