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

consolidation-inventory

业务收拢清单:101 项

上级:events

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

全仓库扫描(backend、app、sdk、builder、im-bridge、infra/plugins、updater)得到 101 项手接的“写完 X 顺手做 Y”,每一项都有且只有一个去向。标记:E 发事件 + 订阅方,J 持久任务,P River 周期任务,W 开放为 webhook 事件类型,K 保持现状。行号对应盘点原稿(基于 origin/main 6eb8c0a64)。“实现”一栏写每项最终落到了哪里。分期见 events-roadmap。

要搬的:写后副作用

#做什么原位置原来怎么坏去向实现
1访客申请 → 给 owner 发邮件access_requests.go:59 → request_notify.go:44请求被整个重试过程堵住;失败即丢;Redis 限流静默丢弃E access_request.created + Jowner.notify(owner/subscriber/mail.go);每小时 5 封的突发上限改成 Postgres 名额,仍是有意丢弃
2批准申请 → 发带码邮件 + 标记已回复access_approval.go:86-89邮件发了但状态没写上则不一致E + Jaccess_request.approval_mail 与签发码同一事务入队;发送后才写“已回复”
4换邮箱确认邮件email_change.go:138发送失败留下悬空的 pending 行Jowner.email_confirmation;链接令牌在发送时才生成
5预约 → 通知 ownerbooker-mcp.js:458 → invoke_background.go:53游离 goroutine,重启即丢E booking.created + Jbooking.record host op 在一个事务里写 booking.* 和一行 booking_notices;owner.notify 发送后删掉它
7预约落库失败后删日历事件(补偿)booker-mcp.js:309,337删失败留下孤儿日程,没人重试J持久的 supplier.invoke
8–10Meili upsert / 删除 / 发布后重建corpus_crud.go、subjectivity.go、corpus.go:139、output.go:61、seo.go:126,140漏调、拖慢请求、进程内 dirtyE corpus.note.changed + Jcorpus_notes 触发器 + corpus.index(P1)
11Obsidian 导入后全量重建obsidian.go:115,133goroutine,重启即丢J逐篇的触发器事件;goroutine 删除(P1)
14构建完成 → 首页自动发布builds.go:242失败只记日志不重试E microsite.build.settled + Jowner.SettleBuild 在一个事务里写构建行、事件和 pg_notify('standmeet_build_settled', owner);订阅方 microsite.homepage_publish
15构建完成 → 重算素材引用builds.go:247,256同上E + J订阅方 microsite.asset_refs
16构建完成 → 唤醒预览长轮询builds.go:193 → buildnotify进程内广播,多副本失效E(LISTEN/NOTIFY)按 owner 分键的 pgstore.Listener;版本号是该 owner 最近一次构建完成的持久毫秒时间戳;infra/buildnotify 删除

要搬的:后台 goroutine、周期任务、请求内外呼

#做什么原位置原来怎么坏去向实现
22InvokeBackground(后台 supplier 调用 + retry)invoke_background.go:49-65重启即丢、无死信、单进程J supplier.invokeinternal/infra/sideeffect/supplier;invoke_background.go 和 notifyPolicy 删除(P4)
23Obsidian 重建 goroutineobsidian.go:135同 #11J删除(P1)
30周期任务调度器本身periodic.go:47-92每副本重复跑,状态在内存PRiver 周期任务(选主、启动即跑);进程内调度器删除(P1)
31Meili 8 秒对账corpus_index_periodic.go:40靠进程内标记删除已删除(P1)
32–36公共对话清理、gas 补充、简历草稿清理、流量保留、用量清理各 *_periodic.go每副本重复跑P以数据声明,跑在 River 上(P1)
38启动时 Meili 建索引 + 全量回填wire/search_index.go:22拖慢启动J启动时入队 corpus.reindex(P1)
39, 62招聘源抓取(请求内串行抓全部源)sources_write.go:133、jobfetch.go:149慢,可能撞 30 秒写超时每源一个 J + E jobs.fetchedfetch 队列上的 jobs.fetch_source;jobs.fetch_result。不做定时抓取:job-loop.md 否决了每日自动抓取(P4)
63出站邮件(请求内 retry.Do)mail_retry.go:49、outbound_sender.go:103请求被退避堵住J(覆盖 #1 #2 #4)邮件端口 internal/infra/sideeffect/mail;internal/infra/retry 删除(P4)
98邮件限流mailthrottle.go:82静默丢弃限流命中改为 snooze每个收件人每小时 30 封,用完 snooze 到下个窗口(P4)
101monitor 面板的后台任务列表jobreg_registry.go:24重启清零改读 River 任务表JobRegistry 删除;instance.jobs 读 River(tasks-panel)(P1)

客户端轮询 → 事件推送(Phase 5,未做)

#轮询位置间隔去向
50微站列表长轮询use-microsites.ts:127挂起请求SSE
51轮询构建直到完成use-microsites.ts:2901.5sSSE
52侧栏申请角标use-sidebar-badges.ts:3960sSSE
54Google 日历 OAuth 后等连接use-gcal.ts:1611s × 15SSE
56SDK 掉线后恢复回合agent-adapters.ts:1861s × 6SSE

开放为 webhook 的事件类型(全部 thin,默认不订阅)

盘点列了 23 行,声明出来是 37 个类型,加上 corpus.note.changed 和 webhook.test,共 39 个,全部 Webhook 可见(event-model)。投递机制见 webhooks。

#事件期
75–76access_request.created / .approved / .status_changedP2
77–78code.issued / .revoked / .redeemedP2
79–82conversation.started / .message / .pruned、ghost.acceptedP2
83booking.created / .cancelled / .rescheduledP4
84–85application.committed、jobs.fetchedP4
86–88writing.published / .unpublished、corpus.note.changed、vault.importedP2
89–90microsite.build.settled、page.promoted_live / .rolled_back / .unpublished、microsite.store.doc_insertedP4
91–93api_key.issued / .revoked、supplier.connected / .disconnected / .activated、block.installed / .failedP2
94–97gas.exhausted / .refilled、instance.upgrade_requested、owner.login / .email_changed / .recovery_requested、ip_ban.addedP2

保持现状(K)

类别项
写入只在同一库、且本身就是事实来源#12 交叉引用重建、#13 导入回执、#17 block 失败记录、#19 推理用量、#20 对话 / 卡片 / ghost、#21 已见招聘 id
进程管道和服务器#24–#29
必须每个节点本地跑的沙箱清理#37
updater 的文件信号与轮询(它故意不碰数据库)#44–#46
Telegram 长轮询与 im-bridge 内存会话#48–#49
访客必须当场拿到结果的外呼(现在只发一次,请求内不重试)#64 日历、#66 媒体抓取、#67 模型列表、#68 规格校验、#71 OAuth、#72 验证码、#73 系统探测、#74 推理
会话与缓存失效#99–#100
升级后等重启(服务器正在重启)#55
访问监控(已决定不进总线)#18

存疑项的定案(按建议)

#项定案理由
3恢复短语邮件K,同步用户在登录页等结果,必须当场告诉发没发出去
6访客预约确认邮件K,同步访客要当场得到确认;补偿删除改走 J(#7)
41–43构建队列(手写 SKIP LOCKED + 租约)K 队列本身;完成后的钩子走 Ebuilder 是跨进程的 Node sidecar,现在就是持久队列,搬到 River 收益小
47im-bridge 15 秒轮询配置K,P5 再考虑 SSE配置极少变化
53系统信息 1 秒刷新K实时指标,天生是轮询
57访客工具“下次轮询出现”P5 前先查清调用方盘点没找到客户端调用点
59–61投递提交 / 草稿 / 公开报告的 PDF 渲染K,同步用户要的就是这份 PDF;投递故意“先渲染再提交”
69市场安装K安装结果当场要看
18访问流量记录K已决定
75–97webhook 暴露面全部可订阅,默认不订阅,一律 thin都是 owner 自己实例的事实;安全类事件恰好适合告警

顺手修掉的(不在计划里)

  • SDK hydration:BlockWidget 和 use-chat-session 在首次渲染时读 localStorage(预渲染的微站上报 React #418);改为挂载后再读。
  • plugin/mount 的 knownToolSpecs 数据竞争;Meili 的 Index 每次调用拿新的索引句柄;任务详情在切换行后还作用在旧任务上。
  • 三处“wiki 对、代码错”:schema.sql 缺 visit_event / visit_viewer(schema 一致性 UT 发现的);drafts_edit.go 里一条过时的 Typst 注释;手动升级文案让用户设置一个不存在的 STANDMEET_REDEPLOY_HOOK(8 种语言)。

被搬走的操作里有 4 个原来有调用方在等结果,它们的完成信号见 completion-hooks 与 async-response-contract。

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 →

consolidation-inventory