Repository navigation
Fix PostgreSQL identity rotation race with commits - #5943
huangruiteng merged 4 commits into
Conversation
Signed-off-by: Duang777 <duangjl007@gmail.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=xhigh
动机
使用 PostgreSQL 保存长期任务状态、并由管理员轮换数据库身份的调用者。 原来:一次写入读取旧身份后暂停,管理员完成轮换,写入随后提交成功却返回旧 revision;调用者用这个成功回执继续工作时立即遇到 CAS 冲突。现在:写入与轮换有数据库保证的先后次序,不能先轮换完成再提交旧身份的写入;普通不同 Goal 的提交仍可并行。
相同真实数据库反例在基线复现旧身份回执与新身份读回不一致,在本 head 通过;反向交错、旧 revision 拒绝、新 revision 继续写入也通过。 本 PR 不启用 PostgreSQL、不晋升 provider、不改变 File/SQLite/NoKV,也不授予 agent 数据库管理或恢复权限。
改动思路
身份一致性是既有 PostgreSQL 提交不变量,应由当前 TypeScript provider 在事务内保证;共享/独占 advisory lock 复用同一 helper,不新增并行策略 owner。 本 PR 只交付数据库提交与身份轮换的序列化修复及回归测试,服务部署、认证 transport、完整备份恢复和跨版本升级编排保持既有边界。
不改会保留可复现竞态;只锁 Goal head 无法保护全库身份;让每次写入独占身份锁会丢失独立 Goal 并行;对 metadata 使用行锁可能需要扩大 runtime 的 metadata 权限。因此采用事务级共享/独占 advisory lock,仍由原 provider 和管理员轮换 owner 执行。此处 advisory 是数据库锁 API 名称,保证由数据库强制执行。
独立验收是 Issue #5941,spec_revision 82d1b837479dd5eb64b581bb0ca36d8c1ff1f0cc 的审计基线;Expected result:写入/轮换有共同先后次序且独立 Goal 并行;Proposed slice:身份锁先于 Goal head,其他 provider/schema/revision 保持;Validation:真实临时 PostgreSQL、类型检查及独立并行反例。另读了该基线的 docs/reference/postgresql-authority-service-v0.md,保留 admin 轮换与旧 revision 拒绝、历史 receipt 可读的既有契约。
具体改动
完整 head 178048b94b8cf0bcc9183331f99cb87a6c299ba1;实际共同基线 82d1b837479dd5eb64b581bb0ca36d8c1ff1f0cc,当前 PR base 为较新的 5cb9e4b8222a94a24a38ad2d7163e32322c11f73。全4文件 +149/-1,其中生产代码仅18行。
postgresql_authority_store.ts:360 的私有 lockStoreIdentityTransaction 用局部 shared | exclusive 类型选择两个事务级锁函数,并把同一 bigint key 参数化传入。commitAuthority(:655)在事务内先取共享锁,再读身份、锁 Goal head、检查 CAS 并提交原 events/receipts;rotatePostgreSqlAuthorityStoreIdentity(:465)先取对应独占锁,再读/更新 singleton metadata。释放仍随 COMMIT/ROLLBACK,原 failed/conflict/ambiguous 处理及连接清理不变。
postgresql_authority_service.integration.test.ts 新测试在真实 metadata 查询之后暂停写入:另一 Goal 必须能完成,而带 lock timeout 的轮换不能完成;随后提交回执与独立 load 一致。另两个测试文件更新 mock 对锁 query 的响应并检查锁先于 metadata 读取,没有新增公开 schema、CLI 或并行决策源。
对主干的风险
本轮使用独立的相同数据库 harness 验证完整顺序。基线实际返回 A:1,但 load 与 receipt 都为 B:1,并触发“轮换必须等待”的失败;本 head 相同合成 fixture 通过,另一 Goal 在 held writer 未释放时确实完成。又在真实轮换 UPDATE 后暂停,新的 Goal 提交必须等待,轮换结束后产生 B:1;旧 A:1 CAS 被拒绝,新 B:1 CAS 实际写到 B:2。这覆盖超时拒绝后的恢复到进度,不能只靠两个 mock 的成功结果。
真实隔离 PostgreSQL17:服务11项、store339项通过,均零 skipped;store 包含 restricted runtime role、tenant RLS、metadata UPDATE 拒绝、atomic rollback、丢失 COMMIT 回应与历史 receipt 恢复。轮换单测6项、Python 工作流6项、TypeScript typecheck 通过。原生 premerge 的3项直接、13项选定检查(5项 catalog +8项 risk) 检查全部通过,无 manual hold。未查询、轮询或等待 CI。
主要剩余风险是参与者版本:旧写入/旧轮换实现不认识这个 advisory key,因此混合版本部署不能声称同样受保护;部署必须让这两个当前 owner 一起升级。锁是全数据库 identity 的必要序列化点,管理员轮换可能暂时等待普通写入;长时运行、真实恢复/failover、部署 transport 和安装版产品采用未在本轮证明。
我的整体评价
APPROVE,无阻塞发现。身份一致性是既有 PostgreSQL 提交不变量,应由当前 TypeScript provider 在事务内保证;共享/独占 advisory lock 复用同一 helper,不新增并行策略 owner。 本 PR 只交付数据库提交与身份轮换的序列化修复及回归测试,服务部署、认证 transport、完整备份恢复和跨版本升级编排保持既有边界。 有限未来重构已应用为一个共享 helper 与明确锁顺序,没有新增 provider/框架,也没有第二份 Python 状态规则。新模式是局部类型词汇;既有身份、revision、授权与其他 provider 保持原 owner。
该结论覆盖当前源代码的实际数据库路径;不把它当作 PostgreSQL 晋升、完整恢复或部署资格。维护者负责 core 合并。
English verdict: APPROVE - 178048b; transaction-scoped shared/exclusive identity fencing fixes the reproduced commit/rotation race while preserving independent Goal concurrency and runtime metadata privileges. Same-harness baseline/head and reverse-order/stale/new-CAS checks passed; 11 service,339 store,6 unit,6 Python tests and typecheck/premerge passed with real PostgreSQL17 and no skipped integration. Mixed-version/deployed recovery remains unqualified.
Signed-off-by: Duang777 <duangjl007@gmail.com>
Signed-off-by: Duang777 <duangjl007@gmail.com>
…tion-race-20261008
Summary
Problem
A commit could read store identity A, pause before locking its Goal head, and then finish after rotation had committed identity B. The commit returned an A-scoped revision even though subsequent reads and receipts were B-scoped, causing the next CAS to conflict immediately.
The lock order is now identity lock, then per-Goal head lock. Shared commit locks remain compatible with one another; rotation uses exclusive mode.
Closes #5941
Validation
npm run typecheck:control-planenpm run test:postgresql-authority-servicewith both live PostgreSQL URLs: 11 passed, 0 skippednpm run test:postgresql-authority-storewith live PostgreSQL: 339 passedpython -m pytest tests/test_postgresql_integration_workflow.py -q: 6 passedloopx checkon the four changed paths: 0 errors, 0 warningsloopx canary premerge --from-git-diff --git-diff-base upstream/main: 13 selected checks passedgit diff --checkBaseline
Audited and implemented from
upstream/main@82d1b837479dd5eb64b581bb0ca36d8c1ff1f0cc.