docs: bilingual counterparts for the projection READMEs; review-driven RFC precision
New zh pairs for the session-projection group and package READMEs (both gained their missing language-switcher lines); the packages/README rows, apiproxy README, and tool-todo README zh sides catch up with their edited English; the group README's stale ProjectionProvider name becomes ProjectionDefinition. RFC precision from review: asOfSeq is the last event's seq (session.seq - 1, -1 empty; subscribed.lastSeq vocabulary) and a new risk names the accepted dev-only staleness window when registry churn changes the key set mid-session. Pairing re-recorded; 540 pairs consistent.
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md
|
||||
2026-07-27-session-projection-and-command-log.md: 060ea402cf621cc3303fddae897b04df7187bb90
|
||||
2026-07-27-session-projection-and-command-log.zh.md: 9077331ec8315b09c4feebb4073bea9dead752c3
|
||||
2026-07-27-session-projection-and-command-log.md: 60795057fb86c7ae930045362e5ca4a95fbc16ad
|
||||
2026-07-27-session-projection-and-command-log.zh.md: 1dca532af8974d33c41fa421d9b7ad3e4061d946
|
||||
|
||||
@@ -64,7 +64,7 @@ declare module 'cordis' {
|
||||
projections?: { asOfSeq: number, values: Partial<SessionProjectionMap> } }
|
||||
```
|
||||
|
||||
The api-proxy history handler, after slicing the tail page, reads `session.seq`, then synchronously walks the registry — no `await` anywhere, so every key's value and `asOfSeq` form one consistent cut, and `asOfSeq` equals the window tail seq. Api-proxy holds zero domain knowledge (the same carrier/contributor relationship as `viewFor` against `ctx.tools`).
|
||||
The api-proxy history handler, after slicing the tail page, synchronously walks the registry — no `await` anywhere, so every key's value and `asOfSeq` form one consistent cut. `asOfSeq` is the **last event's seq** (`session.seq - 1`; `-1` for an empty log, the same vocabulary as `session/subscribed.lastSeq`), so a push frame carrying the first post-baseline change always compares strictly greater. Api-proxy holds zero domain knowledge (the same carrier/contributor relationship as `viewFor` against `ctx.tools`).
|
||||
|
||||
No new RPC method. The timing coincidence is exact: every moment the client needs a fresh baseline (open, reconnect resync, gap repair) already pulls the tail page, and the only path that never needs one (loadOlder) is the only path that passes `beforeSeq`. The client therefore has **no** independent "refetch the baseline" decision at all. Window content is never a signal: "no domain event in the window" is unanswerable there by construction, and only the baseline answers it.
|
||||
|
||||
@@ -176,6 +176,7 @@ Infrastructure first; the three in-flight PRs are left untouched and re-target a
|
||||
|
||||
- **Whole-value rule is load-bearing**: a future domain logging bare deltas cannot serve consumers from its latest event and complicates its own unit. Mitigation: the rule is stated here and in the projection package README; the unit contract makes the full state explicit at every transition.
|
||||
- **Synchronous unit discipline**: `init`/`apply`/`view` that await would tear the consistency cut. The registry documents and the invariant companion asserts synchronicity as far as practical; review owns the rest.
|
||||
- **Live registry churn is not pushed**: loading or unloading a domain plugin mid-session changes the key set, but no session event fires and no frame is pushed; open clients hold the stale key until the next tail pull (reconnect, gap repair, open). Accepted as a dev-only (HMR) staleness window — a registry-change push can be added to the change feed later without contract impact.
|
||||
- **Eager drive costs on busy sessions**: every committed event passes every registered unit's `apply`. Units are cheap per-event by construction (whole-value rule), non-matching events return the same reference, and the count of registered domains is small; if a hot path ever shows, per-unit event-type prefilters can be added without contract change.
|
||||
- **Projection payload growth**: every tail page carries every registered key. Payloads are whole values of UI-scale state (a todo list, a goal snapshot); if a future domain's value is large, per-key opt-out or lazy keys can be added to the request without changing the model.
|
||||
- **Command log volume**: two log-only events per slash command; bounded by human command frequency, negligible against chunk volume.
|
||||
|
||||
@@ -64,7 +64,7 @@ declare module 'cordis' {
|
||||
projections?: { asOfSeq: number, values: Partial<SessionProjectionMap> } }
|
||||
```
|
||||
|
||||
api-proxy 的历史处理器切出尾页后读取 `session.seq`,然后同步遍历注册表——全程没有一个 `await`,因此所有 key 的值与 `asOfSeq` 构成同一个一致切面,且 `asOfSeq` 等于窗口尾部 seq。api-proxy 不持有任何领域知识(与 `viewFor` 面向 `ctx.tools` 是同一种载体/贡献方关系)。
|
||||
api-proxy 的历史处理器切出尾页后同步遍历注册表——全程没有一个 `await`,因此所有 key 的值与 `asOfSeq` 构成同一个一致切面。`asOfSeq` 是**最后一个事件的 seq**(`session.seq - 1`;空日志为 `-1`,与 `session/subscribed.lastSeq` 同一套词汇),因此携带基线之后首个变更的推送帧在比较时恒严格更大。api-proxy 不持有任何领域知识(与 `viewFor` 面向 `ctx.tools` 是同一种载体/贡献方关系)。
|
||||
|
||||
不新增 RPC 方法。时机上的重合是精确的:客户端每一个需要新基线的时刻(打开、重连重同步、缺口修补)本来就要拉尾页,而唯一永远不需要基线的路径(loadOlder)恰好是唯一传 `beforeSeq` 的路径。因此客户端**完全没有**独立的「重取基线」决策。窗口内容从不充当信号:「窗口里没有该领域的事件」这个问题在窗口内从构造上就无法回答,只有基线能回答它。
|
||||
|
||||
@@ -176,6 +176,7 @@ type UseProjection = {
|
||||
|
||||
- **全量值规则是承重结构**:未来某个领域若只记裸增量,就无法凭其最新事件服务消费方,还会让自己的单元复杂化。缓解:该规则写明在本 Note 与投影包的 README 里;单元契约让完整状态在每次转移处都是显式的。
|
||||
- **单元的同步纪律**:`init`/`apply`/`view` 一旦 await 就会撕裂一致性切面。注册表在文档中申明这条纪律,invariant 配套在可行范围内断言同步性;其余由评审把关。
|
||||
- **注册表的实时增删不做推送**:会话中途加载或卸载领域插件会改变键集,但不会触发任何会话事件、也不会推任何帧;开着的客户端持有陈旧的 key 直到下次尾页拉取(重连、缺口修补、打开)。接受为仅开发期(HMR)的陈旧时窗——日后可以在变更流上加一个注册表变更推送,契约不受影响。
|
||||
- **忙碌会话上的正向驱动开销**:每个已提交事件都要过每个已注册单元的 `apply`。按构造,单元的逐事件开销很低(全量值规则),不匹配的事件返回同一引用,且已注册领域的数量很小;若真出现热点路径,可以加按单元的事件类型预过滤,契约不变。
|
||||
- **投影载荷膨胀**:每个尾页携带每个已注册的 key。载荷是 UI 量级状态的全量值(一张 todo 清单、一份 goal 快照);将来若某领域的值很大,可以在请求上加逐 key 的 opt-out 或惰性 key,模型本身不用改。
|
||||
- **命令日志体量**:每条斜杠命令两个仅日志事件;上限由人敲命令的频率决定,相对分片体量可忽略不计。
|
||||
|
||||
Reference in New Issue
Block a user