2026-09-27·by Sijie Wang#standmeet#architecture#design#events

async-response-contract

异步之后的响应契约

上级:events

状态: 已在 v0.1.76 发布(2026-09-27)—— 设计与落地记录见 StandMeet 仓库的 docs/design/event-bus-outbox-webhooks.md。

副作用变异步后,响应不能撒谎:响应只承诺已经提交的事,还在路上的事给一个回执(事件 id / 任务 id),状态可查。需要“写完马上读到”的地方,用“在请求里等一小会儿,等不到就落回持久任务”。

每种副作用

副作用响应里承诺什么调用方怎么知道后续
搜索索引(写后马上搜)corpus.create / corpus.update / corpus.promote 最多等 2 秒,返回 indexed: true;超时返回 indexed: false + index_job_idMCP / UI 凭任务 id 调 tasks.get;AI 看到 false 就知道稍后再搜
访客申请通知“已提交”,不说“已通知 owner”owner 在任务面板看到邮件任务状态;失败最终 discarded 会告警
批准邮件码(同步签发)+ 邮件任务;MCP access_requests.approve 最多等 2 秒申请行显示邮件状态:发送中、已发送或失败(completion-hooks)
预约通知预约成功(预约行已提交),通知状态单列同访客申请通知
Obsidian 导入导入的笔记数(已提交)逐篇的触发器事件给每篇建索引;任务面板里看得到这些任务
微站构建已排队 + 构建 id等待者被 microsite.build.settled(按 owner 分键的 NOTIFY)唤醒,不再靠进程内信号
jobs.fetch_new所有源 20 秒内抓完就返回职位列表;否则返回 {job_ids, pending: true}jobs.fetch_result {job_ids} 返回同一形状
webhook对 owner 的写入响应里不承诺任何投递投递日志

最多等 2 秒,等不到给回执

等待分三步:找到这篇笔记最新的 corpus.note.changed 事件(LatestFor);等 relay 把它扇出(AwaitFanout,由 NOTIFY standmeet_events_fanned 唤醒);再等 corpus.index 任务(Wait,由 NOTIFY standmeet_job_final 唤醒)。每一步都靠每个进程一条共享的 LISTEN 连接,不是每个请求各开一个连接轮询。每个频道最多 64 个等待者,超了就直接返回 indexed: false,不排队(concurrency-control)。完结信号是一条 pg_notify,不是 outbox 事件(completion-hooks)。

常见情况下,回执的耗时是 index 队列的一次轮询(≤ 100 毫秒,concurrency-control)加 River 批量完结器的周期:它每 250 毫秒记一次完结。

文案规则

这条规则也管文案:UI 和 MCP 的文字只说已确认的事,“已发送”只在任务 completed 后出现。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →