feat(coordination): support canonical lease renewal - #4348
Conversation
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
…-renew Signed-off-by: Lihua <1017343802@qq.com> # Conflicts: # loopx/control_plane/coordination/local_authority_runtime.ts
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
Signed-off-by: Lihua <1017343802@qq.com>
huangruiteng
left a comment
There was a problem hiding this comment.
复审 exact head:e98191faa9422e0a09dde1a1406668500c896e1e
动机
这个 PR 修复的是 promoted File/SQLite Goal 上的真实生命周期缺口:公开的 task-lease renew 仍走 legacy lifecycle wire,因而会被 canonical writer fence 拒绝。继续放任这个缺口会让长任务只能等 lease 过期,或诱导调用方通过移除 fence / 写 legacy lease 的方式制造双权威。
改动思路
实现没有放宽 legacy fence,而是在既有 promotion 边界后增加 canonical-only renew request。Python 只负责识别 promotion、采集并冻结 registry 中的 registered-agent facts;TypeScript 重新读取 provider head,把 handoff mode、Todo、lease 交给现有 decideTaskLeaseLifecycle,最后通过现有 AuthorityStore CAS 和 CoordinationCommandReceipt 一次性提交 lease mutation、event 与 original receipt。
我重点核查了:旧 operation identity 是否保持兼容、TTL 是否仍绑定 request digest、历史 replay 是否错误地再次续期/授予当前 authority、File/SQLite 是否共用同一决策、fence/registry source 在 commit 前是否重验,以及 promoted 路径是否还会附加 legacy lease/shadow 写入。当前实现都守住了这些边界。
具体改动
- 新增
loopx_canonical_task_lease_renew_request_v0,旧 runtime 会明确拒绝,避免新请求被旧 writer 误接收。 execute_native_task_lease_lifecycle仅在 durable promotion 成立时发送新 schema;否则保留原 lifecycle wire。renewCanonicalTaskLease在 maintenance guard 内两次校验 fence,打开已选择的 File/SQLite provider,并在写前重验 registry source receipts。executeCanonicalTaskLeaseRenew复用既有 lifecycle decision,合法续租仅递增 version、更新时间/expiry,并保留 owner、idempotency key、lease epoch、write scopes。- receipt replay 返回 original lease,但
changed=false/idempotent=true,不会再次延长,也不会被文档描述成当前执行证明。 - 对 canonical-only 路径明确拒绝
dry_run、transfer/release 控制字段和未知 provider;不生成lease_path,不写 legacy lifecycle receipt 或 shadow outbox。
验证方面:我在 exact head 上用 qualified bundled Node 24.19.0 / SQLite 3.53.3 跑过 41 个 File/SQLite Node 测试,全部通过;公开 Python CLI File/SQLite 矩阵 6 个用例全部通过;TypeScript control-plane compile、changed Python Ruff 和 git diff --check 通过。最初用系统 Node 25.5 / SQLite 3.51.2 跑 Python 时,3 个 SQLite case 被既有 runtime-admission gate 正确拒绝,换到 qualified runtime 后均通过。GitHub 上 Sign-off、build、pytest、四个 test shard、Stage2c、Windows 与 Node compatibility 等当前均为通过,发布类 job 按预期跳过。
对主干的风险
主要剩余风险是 PR 当前落后于 main,合入前如果需要更新 base,应让新 exact head 重新跑完整 checks。这个切片不宣称完成 PostgreSQL、canonical transfer/release 或 SQLite D2 soak;这些边界在代码和双语文档里都已明确。回滚时也必须保留 canonical state 与 writer fence,不能退回 legacy 双写。
我的整体评价
这个改动体量不小,但大部分增量是实际 File/SQLite、CLI、进程崩溃、并发 CAS、replay 和 fence parity 覆盖。设计上把决策继续留在 typed TypeScript owner,把 Python 限制为 transport,并复用了 provider/receipt/projection 现有边界;未发现阻塞性语义问题。我批准这个 exact head,后续只需按正常流程处理 base update 并重新验证新 head。
English verdict: APPROVE — exact head e98191faa9422e0a09dde1a1406668500c896e1e closes the promoted canonical lease-renew gap through the existing typed lifecycle decision and provider CAS/receipt boundaries, with real File/SQLite CLI, crash, replay and race coverage; re-run checks if the behind branch is updated before merge.
Promoted local File/SQLite Goals currently reject
task-lease renewat the legacy writer fence. This adds a canonical-only request path that renews the lease, event and original receipt in one provider CAS transaction. Unpromoted Goals and legacy wire requests retain their existing behavior.The transaction reuses the existing TS lease decision, clock helpers and lifecycle identity encoding. Canonical Todo/lease/mode facts come from the provider; Python supplies only source-bound registration context. A missing fence or unsupported runtime cannot downgrade the request. Historical replay returns the original deadline without extending it again or granting current authority. The adapter avoids legacy lease/shadow writes, and the existing
inspectcommand reads the current lease.The bounded companion refactor shares the existing canonical maintenance gate across local runtime, handoff and renew, and extracts unchanged lifecycle hashing. The merged handoff enum is reused. No SQLite table/format change, provider-default switch, active-Goal migration, transfer/release expansion or PostgreSQL service routing is included. Refs #4224; this does not complete D2 or authorize soak/promotion.
Latest-main refresh validation at
e98191faa9422e0a09dde1a1406668500c896e1e(mergedorigin/mainatfa731a869fd695a5241bea69c2d5049e227504dfwith signed append-only history):The only content conflict during the refresh was the SQLite authority-store guide; its merged capacity/qualification guidance and this PR's renewal contract were both retained. The PR diff remains the same 20-file File/SQLite renewal scope. Exact-head hosted checks: 23 success, 4 expected conditional skips, including merge-gate success. Maintainer review and merge remain pending.
CLI is the affected user entrypoint. The bilingual operation guide covers version selection, replay, source/readback fields and rollback boundaries; no frontend configuration change is needed.
中文:为已 promoted 的 File/SQLite Goal 补齐现有 CLI 的 canonical renew,使用同一次 provider CAS 保存 lease/event/原回执。复用 TS 租约规则与既有 identity 编码,旧 wire 和未 promoted 的 legacy 路径保持不变。旧回执重放不再次延长期限、不授予当前执行权;不创建第二份 legacy/shadow 状态。真实 CLI、File/SQLite、进程中断/竞争和 wheel 验证通过;完整 D2、自然时间、PostgreSQL renewal 和 promotion 仍分别保留范围边界。