Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Event replay integrity and the remaining cutover work

Owner: overall roadmap #4574 R1/R5, shared authority L7/L8 and TS migration T3.
Baseline: `2e1e63260`, 2026-09-25. PR states below are an inventory at this
checkpoint, not a promise of acceptance or a continuously updated counter.

## Delivered boundary

The legacy event source had independent Python state rules. A second
`todo_added` with a different event identity could overwrite a completed Todo;
a priority-only update left old text, a role update left its old source section,
and planner order zero sorted as missing. Four independent counterexamples fail
on the baseline and pass with the typed replay owner.

`goals/state_event_replay.ts` owns ordered replay admission, Todo identity,
lifecycle, role/priority, binding immutability, exclusive addressing and removed
continuation-policy repair. Python retains legacy value decoding, content,
checksums and rendering. The runtime receives compact facts and content-field
names; the returned field-source ordinals address that same immutable batch.
Evidence, validation commands and arbitrary payload bodies do not cross this RPC.
One large evidence regression preserves more than 2 MiB of content with under
4 KiB of replay facts. The adapter folds at most 256 events per call, carrying
only the affected Todo continuation rows. A 4,100-event history crosses 17
calls without losing original fields or duplicate-creation protection. Individual
facts and continuation rows remain subject to the existing RPC budget; no
unlimited per-field size is promised. Historic byte/checksum ordering remains
in the legacy codec; TS returns the final display sort keys.

Exact duplicate event identities retain existing codec replay/conflict behavior.
Different create events targeting one Todo now reject with an actionable update
instruction. Invalid role/priority and unsafe sequence/order facts reject instead
of producing an ambiguous typed projection. Existing stored logs are not
rewritten. Reverting restores the earlier reader; no storage migration is needed.

A detached real-source rehearsal preserves the complete baseline projection and
checksum, with 900 backfilled events and 398 Todos. A disposable registry and real
CLI read back an original record. The original source stays unchanged. Synthetic
mixed history covers dependencies, validation declarations, claims, deferred and
completed work, independent review, owner work, attribution and all event kinds.
Seven alternating warm samples on the same detached input measured median
9.28 ms before and 66.12 ms after. This is an explicit RPC/type-owner cost, not
a speedup. No latency budget was increased; wider cold/throughput qualification
is not claimed. Existing compaction, Markdown-backfill, task-graph and API smokes retain their
original assertions. File/SQLite caller regression remains real, not in-memory.

This does **not** append or bind event writes to the outbox. The
`event_log_writer_not_bound` hold remains. #5003 owns atomic append/completion;
this change does not reproduce its writer or change its persistent event schema.
No provider default, live promotion, PostgreSQL store or external executor changes.
The existing source-readback type narrowing also matches the small correction
already carried by #5012/#5013; it adds no new projection rule.

## Count requirements, open implementations and evidence separately

The older 5–8 / 7–9 estimates mixed units and must not be reused. The reconciled
inventory in #5006 already identifies source transport as implemented on an open
branch. It is not unstarted work. The remaining *planned new code deliveries*
are four including this event-replay slice:

| Planned PR boundary | Exit | After this delivery |
| --- | --- | --- |
| Event replay integrity (this PR) | One typed replay owner; corruption counterexamples and real-source parity/readback | Ready for review, not merged |
| External-effect executor fence | Audit real executor consumers; prove how current execution ownership protects the actual effect interval and uncertain result recovery | Unstarted; downstream idempotency/fencing must be explicit, a pre-call check alone cannot promise this |
| Event-writer binding + integrated whole-Goal migration/rollback | Reuse #5003/#5006, bind actual writer locks/publication to outbox, mixed-writer crash/replay/drain, canonical consumers and fenced rollback on one exact revision/profile | Unstarted; this replay slice closes a proven reader gap within that boundary, not capture admission |
| Default/onboarding + bounded Python retirement | Qualified profile selected by new-Goal creation/settings/install and packaged entrypoints; explicit existing-Goal migration and rollback; delete only writers with no remaining legal callers | Depends on preceding acceptance and applicable D1–D3 |

Thus **three planned new implementation boundaries remain after this PR**;
that is not an assertion that three more merges enable a global default.
Combining writer binding and migration is a plan to verify; if their integration
reveals a defect, record the concrete defect and revised boundary here rather
than keep a floating range. The replay slice is separated because its source
reader and state rules are independently testable/reversible while #5003's
writer is still under review. It also fixes shipped behavior immediately.

Existing open work to integrate, not implement again:

- #5006 complete source transport; #5003 atomic event completion; #5011 observer
retirement: three source/authority integrations.
- #4991, #4992, #4995: three quota/lease caller repairs. #4994 merged during
this delivery and is now included in the rebased baseline, not the open count.
- #5005, #5012, #5013: three demonstrated long-horizon recovery fixes. These are
relevant R1 reliability work, not three additional storage implementations.
- #4931 and contributor-owned #4224: SQLite D2 performance/qualification.
#4915 changes local filesystem placement, not authority selection; #5010/#5008
are release/platform integration work, not unstarted provider implementations.

D2 has a measured 1 MiB receipt/scan failure and outstanding recovery, lag,
restore/upgrade, runtime/OS and elapsed-soak evidence. A stated soak end date is
not a verified final result. Its further PR count cannot be inferred from this
inventory. D1 command/consumer coverage and D3 exact-profile/cohort acceptance
also remain evidence gates. Do not add them to code PR counts or subtract them
because an unrelated refactor merged. Maintainer approval is needed for actual
cohort cutover; this task does not modify an active Goal.

PostgreSQL already implements the provider contract. Deployed authentication,
tenancy, operations/restore/failover and capacity qualification remain separate
medium-term outcomes. Local default does not wait for that deployment; provider
conformance alone is not a production service qualification.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 事件重放完整性与剩余切换工作

归属:总路线 #4574 R1/R5、shared authority L7/L8、TS 迁移 T3。
基线:`2e1e63260`,2026-09-25。以下是此时的 PR 清单,不是持续更新的计数器。

## 本次交付

旧事件来源仍由 Python 单独解释状态:不同 event_id 的第二次 `todo_added`
会覆盖已完成任务;只改 priority 留下旧文本;改 role 留下旧来源分区;
planner_order=0 被当作缺省值。四个独立反例在基线失败,在新实现通过。

`goals/state_event_replay.ts` 统一负责有序重放准入、Todo 身份、生命周期、角色与优先级、
能力绑定不可变、用户寻址互斥,以及废弃续接策略的显式修复。Python 保留历史值
解码、正文、校验和与渲染。RPC 只传紧凑事实和内容字段名,TS 返回字段对应的
原批次下标;证据、验证命令和任意正文不传入 RPC。超过 2 MiB 的证据仍完整
保留,事实请求小于 4 KiB。每次最多折叠 256 条事件,仅传涉及的 Todo 续接状态;
4,100 条历史经 17 次调用仍保留原字段及重复创建保护。单个事实/续接行仍受 RPC
预算约束,不声称单字段无限大。历史字节/校验和顺序保留在旧编解码器,最终
展示排序键由 TS 给出。

同一 event_id 的重放和冲突判断仍由原编解码合同处理。不同创建事件不能覆盖
同一个 Todo,改用明确的更新;非法角色、优先级和不安全整数拒绝进入类型化
投影。旧日志不重写,撤回代码无需数据迁移。

真实来源的隔离演练覆盖 900 条回填事件、398 个 Todo,新旧完整投影及校验和
一致,隔离注册表下真实 CLI 读回原记录,原来源未改变。混合合成历史覆盖依赖、
验证声明、领取、延期、完成、独立评审、用户工作、归因和各事件类型。原有 API、
压缩重放、Markdown 回填和任务图 smoke 保留原断言;File/SQLite 调用者回归
使用真实实现。同一隔离输入交替运行七组 warm 样本,中位数由 9.28 ms 变为
66.12 ms。这是跨 runtime 和类型化规则的明确成本,不是性能优化;没有增加
延迟预算,也不声称完成更广的冷启动/吞吐资格。

本次不写事件 outbox,`event_log_writer_not_bound` 保持。#5003 负责原子追加和
完成,不重复其 writer,不改事件持久格式、默认 provider、活动 Goal 或外部
执行器。来源读回处的小型类型收窄与 #5012/#5013 已有修正相同,不增加业务规则。

## 剩余工作如何计数

旧 5–8 / 7–9 混算了代码、在审实现和验收证据,不能继续引用。#5006 已经实现了
完整来源传输,当前状态是在审,不能重新算成尚未开发。当前规划的新代码交付
是四个边界,含本次:

| 规划 PR | 可观察验收 | 本次之后 |
| --- | --- | --- |
| 本次事件重放完整性 | 单一 TS 重放 owner、反例、真实来源一致性及读回 | 待评审,未合入 |
| 外部 effect 的执行保护 | 审计实际执行入口,证明执行区间及不确定结果恢复;下游幂等和 fencing 合同明确 | 未开始;调用前检查不能保证整个区间 |
| 事件 writer 绑定与整 Goal 迁移/回滚 | 集成 #5003/#5006,绑定真实写锁与 outbox,混合写入、崩溃恢复、drain、canonical 读回及有 fence 的回滚 | 未开始;本次关闭其中已证实的 reader 缺陷,不开放捕获准入 |
| 默认启用与有限 Python 退役 | 新 Goal、设置、安装、打包入口一致选择合格 profile;现有 Goal 显式迁移;删除最后调用者已迁走的 writer | 依赖前面验收及适用 D1–D3 |

所以本次之后是**三个规划中的新实现边界**,不等于再合三个 PR 就能全量切换。
writer 绑定与整体验收是否仍能同批交付,要用集成证据确认;若发现新问题,记录
具体缺陷及拆分原因,不能继续保留一个浮动范围。此次 reader 单独交付,是因为
它可独立验证/回滚并立即修复现有行为,而 #5003 writer 仍在评审。

已有在审工作另列,不重复开发:

- #5006 来源传输、#5003 原子事件完成、#5011 观察路径退役,共三个来源集成。
- #4991、#4992、#4995,共三个 quota/lease 调用者修复。#4994 已在本次
开发期间合入,已包含在更新后的基线,不再计入在审数量。
- #5005、#5012、#5013,共三个真实长程恢复缺陷;它们是 R1 可靠性工作,不是
三套新的存储实现。
- #4931 与贡献者负责的 #4224 是 SQLite D2 性能/资格。#4915 是目录迁移,
不等于 authority 切换;#5010/#5008 是发布及平台集成,不是未实现 provider。

D2 已测出 1 MiB 回执/扫描预算失败,恢复、lag、备份/升级、OS/runtime 和自然
时间 soak 仍需各自证据。到达预计 soak 截止日不等于验收成功,无法据此精确
分配后续 PR 数量。D1 调用者覆盖、D3 精确 profile/迁移 cohort 验收也不能混入
代码数量;不因无关 PR 合入而扣减。活动 Goal 的迁移仍需维护者批准。

PostgreSQL 已有 provider 实现,中期另需部署鉴权、租户边界、恢复/故障转移、
运维及容量资格。本地默认不等待其部署,conformance 通过也不等于生产服务就绪。
Original file line number Diff line number Diff line change
Expand Up @@ -3392,3 +3392,5 @@ beside each entry, and that this appendix exists for the directory it names.
2026-09-24: [Typed complete-source assembly and remaining delivery packages](ledger/shared-goal-authority-state-provider-v0/2026-09-24-source-capture.md) unify source construction, identity rejection and current-graph membership; L7/D2/D3 and provider defaults remain open.

2026-09-24: [Leased continuation and remaining local-default packages](ledger/shared-goal-authority-state-provider-v0/2026-09-24-leased-continuation.md).

Event replay and the reconciled cutover inventory: [2026-09-25](ledger/shared-goal-authority-state-provider-v0/2026-09-25-event-replay.md).
Original file line number Diff line number Diff line change
Expand Up @@ -2613,3 +2613,5 @@ onboarding 按当前核对表分别归为代码、在途 PR 和验收活动,
2026-09-24:[完整源捕获的 TS 组装与剩余交付包](ledger/shared-goal-authority-state-provider-v0/2026-09-24-source-capture.zh-CN.md)统一源构造、身份拒绝和当前图成员规则;不关闭 L7/D2/D3 或启用默认 provider。

2026-09-24: [带租约接力与剩余本地默认交付包](ledger/shared-goal-authority-state-provider-v0/2026-09-24-leased-continuation.zh-CN.md).

事件重放与剩余切换清单见 [2026-09-25](ledger/shared-goal-authority-state-provider-v0/2026-09-25-event-replay.zh-CN.md).
Original file line number Diff line number Diff line change
Expand Up @@ -1931,3 +1931,5 @@ This advances T3/L5; it does not replace D2/D3 or flip a provider default.
2026-09-24: [Typed complete-source assembly and remaining delivery packages](ledger/shared-goal-authority-state-provider-v0/2026-09-24-source-capture.md) unify source construction, identity rejection and current-graph membership; L7/D2/D3 and provider defaults remain open.

2026-09-24: [Leased continuation and remaining local-default packages](ledger/shared-goal-authority-state-provider-v0/2026-09-24-leased-continuation.md).

Event replay and the reconciled cutover inventory: [2026-09-25](ledger/shared-goal-authority-state-provider-v0/2026-09-25-event-replay.md).
Original file line number Diff line number Diff line change
Expand Up @@ -1490,3 +1490,5 @@ TS 摘要批次;Python 保留旧格式解码、公开字段筛选及渲染。
2026-09-24:[完整源捕获的 TS 组装与剩余交付包](ledger/shared-goal-authority-state-provider-v0/2026-09-24-source-capture.zh-CN.md)统一源构造、身份拒绝和当前图成员规则;不关闭 L7/D2/D3 或启用默认 provider。

2026-09-24: [带租约接力与剩余本地默认交付包](ledger/shared-goal-authority-state-provider-v0/2026-09-24-leased-continuation.zh-CN.md).

事件重放与剩余切换清单见 [2026-09-25](ledger/shared-goal-authority-state-provider-v0/2026-09-25-event-replay.zh-CN.md).
2 changes: 2 additions & 0 deletions loopx/control_plane/effect_runtime_handlers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {planStateEventReplay} from "./goals/state_event_replay.ts";
import {projectTodoSummary} from "./todos/summary_projection.ts";
import {admitAutomationStart, confirmAutomationStart, manageAutomationCadence, projectCadenceSchedule} from "./quota/automation_cadence.ts";
import {deliverShadowEntry} from "./coordination/shadow_entry_delivery.ts";
Expand Down Expand Up @@ -438,6 +439,7 @@ export function createEffectRuntimeHandlers(
(params) => interpretTurnJournal(turnJournalInspectionRequest(params)),
],
["turn_journal.write", commitTurnJournal],
["goal.state_event.plan_replay", planStateEventReplay],
["todo.completion_fence.evaluate", evaluateTodoCompletionFence],
["todo.completion_state.normalize", normalizeTodoCompletionValue],
["todo.completion_state.require_metadata", requireTodoCompletionMetadataValue],
Expand Down
Loading
Loading