Record pending audit write-order correction

Why: An isolated PostgreSQL test demonstrates that explicit low IDs bypass the current committed-prefix guard and disappear across pages.

Evidence: The test-only RED and reviewed database/migration tradeoffs are bound to the documented candidate and exact SHA256.

Impact: Adds a human-review design for all online audit writes and exclusive offline copy; no runtime change or migration.

Refs: feature/rdm-two-trees-latest-templates W9
This commit is contained in:
2026-10-14 06:02:15 +00:00
parent b61b8433a3
commit 12e20c6701
3 changed files with 38 additions and 0 deletions
@@ -97,6 +97,7 @@ RDM保有领域、schema/事件与业务权限;Backend仅补必要通用公开
- [W2/W4实施顺序修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w2-w4-sequence-adjustment-r1.md):W2的并发字典修订与事务回滚在当前RDM分支保留两条RED;将已批准W4事务隔离基础W4a移到W3之前,由W2 owner在同一工作区修复,W3仍等待DHF U1合入,W4提案/真人确认W4b仍在W3后。用户于本聊天call_c6f6ccef6a5b4e92ad473d85623d4b5f第0项选择“批准修订,先实现 W4a(推荐)”;批准对象SHA256 `aaf8c091772149a3547cbec272a7f38de3a47cc41c84b7bcef05e74510efbacf`,原稿保留评审前状态。 - [W2/W4实施顺序修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w2-w4-sequence-adjustment-r1.md):W2的并发字典修订与事务回滚在当前RDM分支保留两条RED;将已批准W4事务隔离基础W4a移到W3之前,由W2 owner在同一工作区修复,W3仍等待DHF U1合入,W4提案/真人确认W4b仍在W3后。用户于本聊天call_c6f6ccef6a5b4e92ad473d85623d4b5f第0项选择“批准修订,先实现 W4a(推荐)”;批准对象SHA256 `aaf8c091772149a3547cbec272a7f38de3a47cc41c84b7bcef05e74510efbacf`,原稿保留评审前状态。
- [W9 PostgreSQL审计截止水位修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w9-pg-audit-cutoff-adjustment-r1.md):现有序列ID可低号晚提交,固定页游标缺少已提交前缀保证;在序列CACHE 1等前提下串行化在线审计ID分配,并补迁移/回滚/离线复制检查。用户于本聊天call_22d8212219f946d8b117837d7e5ccb3f第0项选择“批准修订,实施并验证(推荐)”;批准对象SHA256 `2357b715a7c8f3451b34cc25a578d655e8a48f3f26d9cf7561ea7a13998ca857`,只放行隔离分支,真实运行库迁移仍按原边界。 - [W9 PostgreSQL审计截止水位修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w9-pg-audit-cutoff-adjustment-r1.md):现有序列ID可低号晚提交,固定页游标缺少已提交前缀保证;在序列CACHE 1等前提下串行化在线审计ID分配,并补迁移/回滚/离线复制检查。用户于本聊天call_22d8212219f946d8b117837d7e5ccb3f第0项选择“批准修订,实施并验证(推荐)”;批准对象SHA256 `2357b715a7c8f3451b34cc25a578d655e8a48f3f26d9cf7561ea7a13998ca857`,只放行隔离分支,真实运行库迁移仍按原边界。
- [W6项目模型历史来源逐项复验修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w6-source-recheck-design-adjustment-r1.md):独立复审发现候选`resolve_goal`把完整user/judge正文写入长期回执,已有RED证实。以实际纳入的稳定来源ID/版本/hash经可选领域接口逐项重验,移除原文持久化;用户于本聊天call_751be0cc02914fe2b1fe591042d029ec第0项选择“批准修订,继续 W6(推荐)”。批准对象SHA256 `d46168392d4fd8ea9aa7f73206be039268aff6be100faef63efa052323511616`;仅放行隔离分支,真实库迁移/tag/阶段验收仍按原边界。 - [W6项目模型历史来源逐项复验修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w6-source-recheck-design-adjustment-r1.md):独立复审发现候选`resolve_goal`把完整user/judge正文写入长期回执,已有RED证实。以实际纳入的稳定来源ID/版本/hash经可选领域接口逐项重验,移除原文持久化;用户于本聊天call_751be0cc02914fe2b1fe591042d029ec第0项选择“批准修订,继续 W6(推荐)”。批准对象SHA256 `d46168392d4fd8ea9aa7f73206be039268aff6be100faef63efa052323511616`;仅放行隔离分支,真实库迁移/tag/阶段验收仍按原边界。
- [W9全部审计写入口截止水位修订r1](../records/2026-10-14-rdm-two-trees-latest-templates/rdm-two-trees-stage6-w9-all-writers-cutoff-adjustment-r1.md):独立复审发现并在隔离PG以测试-only RED实测显式在线低ID绕过已批r1默认函数,`committed-prefix`误报并跨页漏行。建议所有INSERT同事务锁/单调门禁、UPDATE/DELETE不可改、排他离线复制与只前进序列;影响数据库门禁、迁移和运行成本,待人工评审。当前W9 `63ebc85`不能强报已提交前缀。SHA256 `44f6f7cd34bf95a9c1eb844bfdd70a5c2233e1b5ffc89b3091359dcb5fc3b5d8`。
## 阶段6启动记录 ## 阶段6启动记录
@@ -16,6 +16,7 @@
- [W2/W4关键依赖顺序修订r1](rdm-two-trees-stage6-w2-w4-sequence-adjustment-r1.md):用户已批准W4a先行,原对象保留评审前状态。 - [W2/W4关键依赖顺序修订r1](rdm-two-trees-stage6-w2-w4-sequence-adjustment-r1.md):用户已批准W4a先行,原对象保留评审前状态。
- [W9 PostgreSQL审计截止水位修订r1](rdm-two-trees-stage6-w9-pg-audit-cutoff-adjustment-r1.md):用户已批准隔离分支实现/验证;真实运行库迁移未获放行,原对象保留评审前状态。 - [W9 PostgreSQL审计截止水位修订r1](rdm-two-trees-stage6-w9-pg-audit-cutoff-adjustment-r1.md):用户已批准隔离分支实现/验证;真实运行库迁移未获放行,原对象保留评审前状态。
- [W6项目模型历史来源逐项复验修订r1](rdm-two-trees-stage6-w6-source-recheck-design-adjustment-r1.md):长期回执原文RED后的X1受影响公开合同已获用户批准;原对象保留评审前状态,W6仅隔离分支续办。 - [W6项目模型历史来源逐项复验修订r1](rdm-two-trees-stage6-w6-source-recheck-design-adjustment-r1.md):长期回执原文RED后的X1受影响公开合同已获用户批准;原对象保留评审前状态,W6仅隔离分支续办。
- [W9全部审计写入口截止水位修订r1](rdm-two-trees-stage6-w9-all-writers-cutoff-adjustment-r1.md):显式在线ID绕过后的DB门禁与排他复制修订;待人工评审,强coverage暂不放行。
- [阶段4源码与在途依赖快照](stage4-sources.json) - [阶段4源码与在途依赖快照](stage4-sources.json)
- [模板与会议来源台账](rdm-two-trees-source-inventory.json) - [模板与会议来源台账](rdm-two-trees-source-inventory.json)
- [旧DOC源码补充](rdm-two-trees-legacy-source-addendum.md) - [旧DOC源码补充](rdm-two-trees-legacy-source-addendum.md)
@@ -0,0 +1,36 @@
# 阶段6受影响设计修订:审计 ID 的全部写入路径 r1
记录日期:2026-10-14。状态:**待人工评审**。本修订补足已批准 [PostgreSQL 截止水位修订 r1](rdm-two-trees-stage6-w9-pg-audit-cutoff-adjustment-r1.md) 的一个未满足负例;不扩大可共享审计范围,不改变 X2“仅可信已提交项目业务操作可跨成员链接”的权限边界。Backend W9 隔离分支当前本地 HEAD `63ebc85695790e7d0c2b758e5c0ed1b4adaec008`,未推送、合入或部署。
## 发现与影响
W9 v33 给省略 `command_audit.id` 的在线 INSERT 加事务级 advisory 锁和 `CACHE 1` 序列检查,默认写入口的真实跨页、并发、迁移及回滚测试为 SELF PASS。独立复审发现:显式 `INSERT(id=空洞低号)` 不求值列默认函数,因此不取得该锁。新增测试-only `TestProjectAuditPostgresExplicitLowIDCannotHideAcrossPages` 已在隔离 PostgreSQL 18.6 实测为 RED:已提交 ID 1/2、隐式取 3 回滚,事务 A 显式插入 3 暂不提交,B 正常 `AppendAudit` 提交 4;真实 `QueryProjectAudit` 第一页返回 `[4,2]`、`CutoffOffset=4`、`NextOffset=2` 且报告 `committed-prefix`,A 提交后下一页只有 `[1]`,永久遗漏 3。完整失败收据 `/tmp/backend-w9-explicit-low-red-20261014-01.json`,来源 SELF,退出码 1;Backend HEAD `63ebc85695790e7d0c2b758e5c0ed1b4adaec008`,唯一未提交改动为该测试,diff SHA256 `a85633c58a0841ecb1f479b9ceaed237aacacad1a5bafe761996fc7e9b711ecd`。这证明当前强 coverage 断言错误,**并不证明修复已就位**。
已批准 r1 明确把在线显式 ID 列为禁止维护动作,并要求负例证明强 coverage 不会误报。当前软件无法强制这一约束;只靠操作规程或当前 Backend 唯一生产调用点,不能给项目审计页面作无条件“已提交前缀”的保证。W9 `63ebc85` 因此仅是受支持默认写入口的局部候选,完整 W9 与阶段6增量 C 都未完成。当前强 coverage 先保持未发布;必要时读面退回 `offset-upper-bound-only` 或失败关闭,不能发布已知可误报版本。
## 选择与推荐
| 方案 | 对现有接口和数据的影响 | 取舍 |
|---|---|---|
| 保持默认函数,只在文档禁止显式 ID | 对直连 SQL 不设数据库门禁,强 coverage 仍可误报 | 不满足本次验收 |
| 保留默认函数,给**所有** `command_audit` INSERT 加同一事务锁与单调检查的行级门禁 | 在线隐式/显式写均在提交前排队;公开游标字段不变;需调整离线显式复制与重试 | 推荐,先在隔离 PG 验证可实现性 |
| 运行账号与迁移账号分离,按列权限阻止运行账号写 `id` | 权限与部署配置变化大,现有 DSN/测试未证明双账号可用;维护账号仍可破坏顺序 | 备选 |
| 用服务端持久快照/新游标身份替换整数 cutoff | 不再依赖提交序 ID,但改公开分页合同、授权 epoch 和快照清理 | 若门禁方案无法证明则重新评估 |
推荐的数据库门禁是 `BEFORE INSERT` 行级 trigger 或等价受信 DB 路径:在任何行进入 `command_audit` 前取得与当前默认函数**相同**的事务 advisory 锁,先确认 `current_setting('transaction_isolation')` 为 `read committed`,再以锁后的新可见快照验证 `NEW.id > 0` 且严格大于当前表最大 ID。`REPEATABLE READ/SERIALIZABLE` 事务即使等到了锁,也可能用旧快照漏看已提交高 ID,故项目在线写入须明确拒绝这两档,不能靠“锁已取得”推断安全。显式高 ID 若超过当前已预留序列位置则直接拒绝,不能让随后默认取号退回其后;本轮不增加在线 `setval` 通道。事务持锁至提交/回滚,后续在线写者不能在低 ID 尚未确定时提交高 ID。保留当前默认函数供省略 ID 的 Backend `AppendAudit` 使用;trigger 不二次分配 ID,避免正常写入双取号。PostgreSQL 默认值在行级 BEFORE trigger 前已经填入,因此“`NEW.id` 非空即拒绝”会误伤正常写入。[默认值规则](https://www.postgresql.org/docs/18/ddl-default.html)、[触发器与事务](https://www.postgresql.org/docs/18/trigger-definition.html)、[旧事务快照与锁](https://www.postgresql.org/docs/18/sql-lock.html)。
门禁的证明前提需明确:`CACHE 1`、正向非循环序列、默认函数与 trigger 均存在,`pg_trigger` 显示确切 BEFORE ROW/函数身份且 `ENABLE ALWAYS`,强读和写入均核验,而不只看默认表达式。`ENABLE ALWAYS` 防 session_replication_role 改变通常的触发器触发范围;受控离线复制只短暂禁用**指定审计门禁**,保留主键/外键等约束触发器,并以排他表锁阻断读写。[ALTER TABLE 触发器启用模式](https://www.postgresql.org/docs/18/sql-altertable.html)。审计行还须 append-only:在线 `UPDATE` 或 `DELETE` 都应由独立的 DB 级门禁拒绝,业务修正只能追加新事实。受控升级建议使用版本化函数身份;运行时能检出 trigger 禁用、改绑、默认表达式/序列参数或schema版本漂移,不以只看同一 OID 声称能发现有权限者的 `CREATE OR REPLACE FUNCTION` 同 OID 换体。DB 管理员改写/禁用门禁、强制修改序列或物理库文件仍属于受控维护/事故,不声称软件能防恶意 DBA。任一受支持运行不变量无法核验时不返回强 coverage。
## 离线迁移与恢复边界
现有 `storage-migrate.copyRows` 逐表开启一个事务,源查询没有 ID 顺序保证,使用显式 ID + `ON CONFLICT DO NOTHING`,事务提交后才由 `advanceSequences` 另行推进序列,`migration_table_state` 又另写 done。直接加单调 trigger 会拒绝乱序复制、已存在 ID 的幂等重试,也会留下“复制已提交而序列未推进/状态未记”的窗口。需给 `command_audit` 单独设计**排他停写、独立于在线请求的受控复制事务**:锁表并验证无在线写者 → 在同一事务内临时停用指定门禁或采用等价受限写入能力 → 复制及逐行/最终 hash 对账 → 只向前推进序列至不低于当前已预留位置和最大保留 ID → 恢复门禁并核验启用与参数 → 写同一事务的完成状态 → 提交。复制和对账期间 `ACCESS EXCLUSIVE` 会阻塞该表读取及写入,停顿随源数据量增长,必须在隔离库量测。现有 `advanceSequences` 只按目标 `MAX(id)` 调用 `setval`,可能把更前进的序列**回拨**,不能原样复用;须在停写/排空旧缓存后比较序列当前状态和目标最大 ID,空表、失败重跑也不能降低下一个 ID。失败或进程退出应使门禁停用/复制/完成状态一同回滚;`setval` 非事务性,失败后仅允许向前空洞,仍以运行时检查证明下一个 ID 大于已提交最大值。[序列状态与回滚](https://www.postgresql.org/docs/18/functions-sequence.html)。`migration_table_state` 的 done/idempotent 重跑也应在这一约束下对账,不把 `ON CONFLICT DO NOTHING` 当成功证明。
真实运行库迁移仍未获授权。本修订只放行隔离库升级/回滚/复制演练;W6 的 v31–v35 与 W9 的 v31–v33 冲突仍须真实串行合入后重排,并以最终组合版本重测。公开 `CutoffOffset`/`BeforeOffset`/链接 `HostAuditOffset` 保持现状,RDM W8 的 operation 级分页及对象权限仍未实现。
## 验证与停止条件
1. 先在隔离 PostgreSQL 将上述“低号显式插入等待、高号先提交、跨页漏行”制成 RED;门禁后它应表现为低号先提交或该显式操作被拒,绝不再出现 `committed-prefix` 加漏行。并测多行乱序、`ON CONFLICT` 幂等、`REPEATABLE READ/SERIALIZABLE` 旧快照拒绝、锁超时/取消、回滚空洞、显式高 ID、在线 UPDATE/DELETE、默认函数与门禁 trigger 的禁用/改绑及可检测配置漂移;同 OID 的有权限函数体替换属于受控DDL事故,不虚构运行时可检测。
2. 旧库→新 schema、v33 候选已应用库、离线 SQLite→隔离 PG 复制及中断重跑各保持原 ID/链接/owner 项目边界;门禁恢复、序列推进、对账与强 coverage 相互一致。读写 8 并发及大页查询记录吞吐/p95/锁等待,和 W9 r1 的 SELF 条件并列;用户未批准数值阈值,成本须留阶段6评审决定。
3. 任何路径无法保证全部在线 INSERT 经同一提交顺序,立即停止强 coverage 声明,保留真实 `offset-upper-bound-only`/失败状态并回到设计评审,不改写旧记录、放宽权限或用前端隐藏遗漏。
请评审是否批准覆盖所有在线 `command_audit` INSERT 的事务门禁,以及上述排他离线复制/恢复边界。批准仅放行 W9 隔离分支实现与测试;真实库迁移、阶段6/7验收、PR/合入与部署仍按原门禁。