embed 更新 hook:语料变了告诉它的 embed

更新于 · 在 sijie.xyz 查看原条目 ↗

状态: 已在 v0.1.76 发布(2026-09-27)—— 设计与落地记录见 StandMeet 仓库的 docs/design/event-bus-outbox-webhooks.md。standmeet.com 那一侧在它自己的仓库里已写好,尚未部署。

embed 的更新 hook 是挂在 embed 上的一个 webhook 端点,范围就是这个 embed 的访问码。范围内的笔记一变,展示它的站点就会知道,并且只重读变了的部分。

想法从哪来

standmeet.com 通过一个 embed 把 owner 的语料展示成博客。它原本无从得知语料变了:盘点里“embed 的消费方(standmeet.com)”一栏的机制是“无”。更新 hook 是事件总线计划的第三期,standmeet.com 是它的第一个消费方。

一个字段,一个挂上的端点

embeds.create 和 embeds.update 接受 update_hook_url;embed 表单里有「更新 hook 地址」字段。填了就创建或更新这个 embed 的端点(这个 upsert 有自己的 UT):

  • event_types = ["corpus.note.changed"]。
  • 设上 embed_id,范围取自 embed 的访问码:角色 glob 减去码的 deny。由 access 域回答(EmbedAdmits),用的就是所有读取都走的 entity.AllowsCorpusEntry。码被吊销或过期、embed 被删,一律不放行。raw:// 永不出。
  • 响应带 update_hook {endpoint_id, url};签名密钥只在端点刚创建时返回。
  • payload 是 thin 的;接收方用 embed 本来就用的授权 API 读正文。embed 怎样在不携带访问码的前提下认证,见 embed-credential-never-carries-the-code。
  • /api/v1/corpus-cards 带 updated_at(精确到秒),接收方据此判断哪些卡片变了。
erDiagram
  access_codes ||--o| embeds : "exposed by"
  embeds |o--o| webhook_endpoints : "update hook"

standmeet.com 收到更新的全过程

sequenceDiagram
  autonumber
  actor Owner
  participant SM as sijie.xyz (StandMeet)
  participant W as standmeet.com Worker
  participant KV as Workers KV
  actor V as Visitor
  Owner->>SM: edit a note in the standmeet subtree
  SM->>SM: trigger → outbox → relay → webhook.fanout
  SM->>SM: EmbedAdmits, inside LANDING-BLOG's code scope ✓
  SM->>W: webhook.deliver POST /api/corpus-hook (signed)
  W->>W: verify signature (±5 min), skip a webhook-id seen in the last 24 h
  W->>SM: GET /api/v1/corpus-cards (with updated_at)
  W->>KV: diff against the index, find changed / new / gone
  loop each change × zh/en
    W->>SM: GET /api/v1/wiki/{path}?lang=
    W->>KV: store the note
  end
  W->>KV: delete gone notes, update the index, remember the webhook-id
  W-->>SM: 2xx
  V->>W: GET /zh/blog/…
  W->>KV: read note + index
  W-->>V: HTML rendered per request (links resolved against the current index)
  • 博客页、RSS、博客 sitemap 和“最新笔记”都在请求时从 Workers KV 渲染(Astro 的 Cloudflare 适配器)。只有博客路由设 prerender = false;落地页上的“最新笔记”是一个 server island。
  • POST /api/corpus-hook 按 Standard Webhooks 验签,时间戳窗口 ±5 分钟;按 webhook-id 去重(在 KV 里记 24 小时);重新列出卡片,只抓 updated_at 变了的条目,两种语言都抓。
  • 链接在请求时按当前索引解析,重命名或删除不会留下失效链接。
  • KV 为空时,第一个请求从实例把它填满(自举)。没有定时任务。
  • 部署需要 KV namespace id 和 CORPUS_HOOK_SECRET。
  • standmeet.com 这一侧在另一个仓库(atmaxmoj/standmeet-landing)。

不要对账任务

owner 决定不要任何对账循环。送达要么有保证,要么失败看得见:outbox、持久任务、面板告警已经兜住(message-loss-guarantees)。对账是在掩盖不可靠的投递;投递可靠了就不需要。

验收

  • e2e embed-update-hook(已通过):embed 表单填 hook → 改笔记 → sink 收到;cards 带 updated_at;改范围外的笔记不会到达 hook(哨兵式)。
  • 真环境(未完成):在 sijie.xyz 改笔记 → 60 秒内 standmeet.com 页面更新,不部署。部署 standmeet.com 之后验证,归档到 docs/real-env-verification/。见 events-roadmap · events-test-plan。