feat(authority): add verified portable journal recovery - #4887
Conversation
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…play Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
这个 PR 补齐的是 canonical authority 的“可恢复历史”,不是又一条 provider promotion 路径。
已有 projection copy 或 shadow rollback artifact 可以保存某个状态切片,却不能证明完整保留了每个 operation id、event、receipt 和历史 projection。对 AuthorityStore 来说,这些历史不是可丢的调试信息:lost-ACK recovery、idempotency fencing 和后续重放都依赖它们。只复制最终 head,即使 hash 一样,也可能隐藏被遗漏、重排或补偿掉的中间操作。
本 PR 提供了一个完整而独立的恢复 artifact:从选中的 canonical store 固定一个 retained prefix,流式重建并 seal;随后可以完整 verify,并在不触碰活跃 Goal 的前提下恢复到独立 File/SQLite store,或由 service-owned AuthorityStore 恢复到 PostgreSQL。真正的 provider adoption、writer fencing 和 later-write accounting 仍留在单独的 authority transition 边界。
改动思路
实现严格复用现有 owner,而不是引入 provider-specific backup format:
- Source truth 仍是
AuthorityStore.loadAuthority/storeIdentity/scanCommitted;每条 transaction 保存 operation/events/receipts,加上用现有authority_state_logcodec 计算的 state delta。 - Archive 是 versioned NDJSON:header 固定 goal/provider/store identity/cursor/revision/final projection digest;transaction 必须 cursor 连续、operation 唯一、previous digest 连续,且逐条重建后的 projection hash 精确匹配;terminal seal 必须覆盖 header 指定的 captured head,且后面只能 EOF。
- Export 允许 source 在 captured cursor 之后继续 append,但不允许 captured prefix、store identity 或 captured head 被改写。完成文件先独立 verify,再用同目录 hard link 发布,已有 output 永不替换。
- Restore 先完整 verify 和 digest bind,再只对缺失 suffix 执行 commit;每次写后读取 exact journal row 和 receipt。lost response 不触发第二次 write,而是由 readback 证明;existing prefix 必须逐条 semantic match,extra/divergent target 直接拒绝。
- Python CLI 只做参数/路径传输;格式、state reconstruction 和 write authority 全在 TypeScript。Local restore 只能进入新建或 exact-bound 的私有目录,永远不注册为 active runtime。
具体改动
authority_archive.ts新增严格 decoder、streaming exporter 和 resumable exact-prefix restorer。local_authority_archive.ts新增本地 admin envelope:verify 纯读、export 读取 registry-selected source、restore preview 不创建目录、--execute才创建 0700 destination + binding + isolated store。loopx authority-archive export|verify|restore注册到 CLI;response 明确authority_changed=false、execution_authority_granted=false。authority-archive.md给出可运行命令、digest readback、重复恢复、隐私边界、hash 非签名、PostgreSQL service boundary 和 separate-cutover 要求。- tests 覆盖真实 File/SQLite/PostgreSQL 双向恢复、CLI 独立进程 readback、corruption/re-signed tampering、source concurrent append、checkpoint window、interrupted/lost-ACK restore 和 occupied target。
关键代码讲解
verifyAuthorityArchive/records:只有读到 seal 后又确认 EOF,才 yieldverified。校验不是只看每行 checksum,而是边读边重建 state、检查 cursor/operation/revision/head,因此重新计算被篡改行的 hash 也无法绕过语义规则。exportAuthorityArchive:初始 head 是 captured boundary。它只扫描到该 cursor,然后检查最后一条 revision/重建 projection/store identity 与 captured header 一致;后续 append 被允许,不需要不必要地停止 source writes。restoreAuthorityArchive:source provider revision 只作为 provenance,destination 生成自己的 physical revision;semantic comparison 精确保留 cursor、operation、events、receipts、projection。对 ambiguous write,代码只 read back,不重发。manageLocalAuthorityArchive:restore-binding 把 destination 与 goal/archive digest/provider 绑定,existing unbound/mismatched directory 不会被“顺手采用”。即使成功,也只返回requires_separate_authority_cutover=true。handle_authority_archive_command:retry_safe=false,restore 需要显式 digest,并通过第二次--execute将 preview 与 mutation 分开;Python 不读取或构造 canonical journal。
对主干的风险
未发现 blocking finding。最重要的风险不是 JSON 能否 parse,而是一个最终 state 正确但历史不完整的 artifact 被误认为可恢复。本实现通过 cursor continuity、unique operation、逐 transaction projection hash、receipt readback 和 terminal head/seal 把这一反例锁死。
我执行的验证:
- latest-main integration merge:干净,无冲突;
git diff --check通过。 npm run typecheck:control-plane:通过。authority_archive.test.ts:22/22 passed,包含 File/SQLite roundtrip、tampering、concurrent append、interrupted/lost-ACK recovery 和 divergent target。test_authority_archive.py:2 passed,走真实 source-checkout CLI 的 export/verify/preview/execute/repeat readback。- 我启动了一个隔离的本地 PostgreSQL 16 实例并运行
authority_archive_postgresql.integration.test.ts:4/4 passed(File↔PostgreSQL、SQLite↔PostgreSQL);实例在测试后停止,没有接触任何活跃 Goal 或共享数据库。
最初 detached worktree 没有 node_modules,PostgreSQL test 在 import pg 前失败;绑定仓库依赖树后真实测试通过。这是 setup gap,不是 product failure。
语义与 CI 对齐
新的 loopx_authority_archive_v0 是对已有 AuthorityStore retention/state-delta vocabulary 的合理扩展。它准确叫 archive/isolated restore,没有把 artifact 描述成 provider activation、rollback 或 execution authority。archive_sha256 明确是 record-chain terminal digest,不是 raw file SHA,也不是 author signature;文档把 authentication/tenant/cutover 留给现有 owner。CLI availability 不会 activation,normal runtime、scheduler、quota、frontend、Lark 和 provider selection 均保持原行为。packet 明确 wait_for_ci=false,本结论不依赖远端 CI。
我的整体评价
这个 exact head 的范围虽然超过一千行,但生产机制集中:271 行核心 format/replay、65 行本地隔离 adapter、63 行 Python transport,其余主要是公共文档和真实后端/negative coverage。它没有为了“以后可能迁移”新造 provider abstraction,而是直接复用了 AuthorityStore 与 state-delta owner;对恢复这种低频高后果能力,这个严格度和体量是合理的。
剩余风险也被正确限定:hash chain 只在现有 filesystem trust boundary 内检测 corruption/bind reviewed content,不认证作者;production tenant auth、大体量长 soak、credentialed service integration 和 active provider cutover 仍是独立 gate。由于这是 control-plane/runtime 与 persisted recovery contract,仍必须由 maintainer 决定合并,我没有执行 merge。
English verdict: APPROVE — the exact head provides a complete, verified, provider-neutral journal recovery artifact while keeping restored state isolated from active authority; no blocking finding remains.
Goal And Delivered Outcome
Canonical writes need a recoverable history before whole-Goal cutover. Existing shadow rollback quarantines a pre-promotion lineage; copying only the current projection would lose older operation identities and receipts.
loopx authority-archive export|verify|restorenow captures one exact retained prefix, verifies its complete state/history, and restores it into an isolated File or SQLite store with resumable exact-prefix readback. The same TS contract supports a service-owned PostgreSQL store. Related to #4574, shared-authority D3/L8 and TS migration T3/T4; base:main.Scope And Continuation
Complete within the recovery-artifact scope. The CLI previews restore, requires an exact archive digest and explicit execution, rejects occupied unrelated destinations, and never changes a registry, provider selector, writer fence or execution grant. Operation ids, events, receipts and historical projections survive; destination physical revision tokens belong to the destination.
The existing L8 package still owns source draining, executor fencing, target adoption, later-write accounting and cohort approval. Integrate the accepted reviewed-promotion work in #4879 and command recovery in #4886 separately. SQLite D2/profile qualification and default onboarding remain open; the RFC's conditional 5–8 delivery-package estimate is unchanged. This archive covers coordination authority, not the entire Goal runtime.
The maintenance pass reuses
AuthorityStoreand the existing state-delta codec instead of adding provider-specific export formats or Python state reconstruction. No existing Python business writer is retired in this slice.Validation
aa41ae4959eedce93ddfc2e9dc7d574adebc1ac1(runtime code unchanged after495b8f90e; final two-line digest documentation clarification requalified)Coverage: complete archive/recovery user journey and negative boundaries; whole-Goal promotion, source fencing/adoption, production default qualification and elapsed soak are not claimed. Archive checksums detect corruption, not author authenticity. A modified input during recovery leaves a failed isolated copy rather than an activated authority.
Frontend / Visual Evidence
UI impact: none. This is an administrative CLI transport with compact responses; it does not change frontend/Lark settings, provider selection, the projection owner or packaged presentation.
Type Of Change / Area
New feature, documentation and tests; control plane. Shared Goal Authority, R5/G2/G5; shared-authority D3/L8 recovery artifact and TS-owned persistence semantics.
Shared-authority RFC Fixture Impact
loopx_coordination_production_scale_fixture_v0, both native and imported schemas.Boundary Checklist