docs(rfc): generated lifecycle status index, supersession declarations, checkpoint logs moved to ledger - #5078
Conversation
huangruiteng
left a comment
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
Exact head: 34c951c45280491b34e4183851072faf68f1590f; immutable merge base: 3e443ad7c285973c40be883293970be6c0c51e06.
动机
要让维护者从 RFC 找到当前生命周期、替代关系与历史证据,同时保持规范和交付成熟度分开。现有 README、RFC 头和历史段落已有这些信息;把索引派生出来、把历史搬到既有 ledger 是有价值的维护切片。判断目标依据是 RFC README/TEMPLATE 和整体 roadmap 的规范/历史边界,不是新增文件或作者的“完成”声明。
本轮检查整个 103 文件、+1324/-501 的 base-to-head 改动。实际 Git diff 补齐了 GitHub files 视图只返回前 100 文件的截断。没有继承其他任务的测试或结论。
改动思路
沿用 RFC 文档作为人工决策来源,生成双语 STATUS;沿用既有 ledger 和 docs-governance smoke,避免另建运行时状态或归档目录。这比新建 RFC 管理服务合适。最强的反对理由是校验器把“字段存在”和“关键词匹配”当作语义,可能生成错误关系或把规范误判为历史;正常仓库全绿不能证实这些边界。
另一个维护成本是 README 仍保留手工生命周期值。隔离副本中只把一个 RFC 头从 Draft 改为 Under review,再按 STATUS 的操作说明执行 --write,得到退出 1,--check 继续报 README Draft / header Under review。应至少修正“只改头即可”的说明;后续可在同一生成器内派生 README 生命周期,保留人工 Delivery/Current boundary。无需新的状态服务。
具体改动
关键代码讲解
scripts/generate_rfc_status_index.py:102的normalize_state把头部/README 字符串投影为生命周期。startswith没有状态词边界,非法值会进入合法桶。- 同文件
collect从顶层完整 RFC 文件集读取头部、镜像声明及 ledger 条目数;validate:218对 README、声明存在性和标题做检查,render生成两个索引。只有--write写生成文件,--check应仅验证。 examples/docs-governance-smoke.py:224的check_rfc_status_index用sys.executable调用真实生成器,再重复扫描 checkpoint 标题;main 已接入此检查。- 历史迁移继续使用
ledger/<rfc>/YYYY-MM-DD-slug.md,新增 provenance 与原位置指针。23 个原语言搬迁正文中,21 个在归一化相对链接深度及已迁移锚点后完全一致;另外两份 owner-authorized acceptance 文本把#3836写成了##3836,是应恢复的小笔误。另 3 文件是明示的新语言镜像,未伪称逐字相同。
已验证的阻塞问题:
- [P2] 校验实际的替代关系,而不只检查字段存在。
validate:235–240仅检查None。用真实 CLI 在完整隔离文档树上,把 RFC 设为 Superseded 并同步 README 后,Superseded by: none、不存在的missing-successor-v0.md、存在后继但后继仍声明Supersedes / closes: none,三种输入的--write和--check都退出 0。README 明确要求指向后继且后继反向命名前驱,当前校验会把不成立的关系发布为已检查索引。最小修复是在既有文档集合中解析none | RFC links,验证目标存在和规定的双向关系,加入合法关系和上述负例。 - [P2] 用有限状态词解析,拒绝前缀相似的非法值。
normalize_state:104–106将Drafting notes is not a lifecycle state当作 Draft;把 RFC 头和 README 都设成此值时两个命令均退出 0,输出索引中的 Draft。这样“非法头部应失败”的检查失效。保留合法的Draft, .../Accepted; ...描述,但要求精确状态词及明确分隔符,再用有类型的生命周期值渲染。 - [P2] 不要把规范性的 checkpoint 合同判成历史日志。
CHECKPOINT_HEADING_RE和 smoke 中的同类正则拒绝任何含 checkpoint / 检查点的标题。在未增加任何日期或进度记录的现有规范中加入## Checkpoint persistence contract及 crash-recovery 规则,实际--check就要求搬入非规范 ledger。它既误挡合法规范,也漏过不含该关键词的日期日志。应围绕明确的历史条目/规范分类执行规则,采用有类型的分类或明确结构;至少以“合法规范标题通过、已标记日期历史留在正文失败”的成对负例保护边界,不靠把标题换词绕过检查。
语义与 CI 对齐
生命周期是文档决策,STATUS 是派生视图,不能代表交付完成或 Goal 接受。新的头部、替代关系与历史位置规则是机器强制义务,README/TEMPLATE 已披露,不能称为建议。该工具没有 optional/default-off 承诺;既有 docs smoke 默认新增检查。没有泛化控制平面错误文本、权限授予、scheduler/quota 或执行状态改动,CLI/Lark/frontend 运行路径无需伴随产品改动。
对主干的风险
从独立 exact-head checkout 执行:uv run --extra test python examples/docs-governance-smoke.py 通过;作者引用的四个 RFC 消费者测试文件共 147 passed;base 的 docs-governance smoke 也通过。真实生成器的正常、缺声明、过期索引和合法双向关系控制已执行;以上反例穿过实际 --write/--check 和文件 backend。Git diff whitespace 检查通过,103 文件边界扫描 errors=0;扫描返回的两个工作区状态提示与本 PR 文件无关,没有据此修改任何 Goal。
初次审查脚本使用了不匹配的表格头 fixture,已纠正为实际支持的粗体头并断言输入确实改变后重跑;不把初次输出当证据。历史比较保留内容、状态和关系差异,仅归一化链接搬迁。未执行远端 CI 获取/等待,遵循本 packet 的 not_consulted / wait_for_ci=false。并发写入生成文件的锁/事务不在当前文档生成器承诺内;不同分支继续由 Git 审核合并,不能据此宣称运行时协调能力。
我的整体评价
REQUEST_CHANGES。 useful delta 和既有边界选择成立,但生命周期/关系真实性和规范内容的可维护性尚未满足本切片。长期成本具体表现为错误 supersession 延续到后来维护、合法规范因词面被迫改写;读者体验则是得到“校验通过”的错误关系和不完整操作说明。修复上述三个有界问题并重跑原生反例后,再判断整个 PR。未来改动友好性检查已做:优先在现有生成器集中生命周期/关系解析,复用同一个历史分类检查,减少 smoke 与 generator 的重复规则;不要求框架化或无关重构。本评审没有合并或认证整条 roadmap。
English verdict: REQUEST_CHANGES
The derived index and ledger migration are useful, but the real CLI accepts invalid lifecycle prefixes and unusable supersession relationships, and the keyword-only checkpoint guard rejects normative contracts. Repair these bounded semantic checks and rerun the paired cases before approval.
…ogs to the ledger Add scripts/generate_rfc_status_index.py, which derives STATUS.md and STATUS.zh-CN.md from each RFC's status header (Accepted / Active / Superseded / Retired), cross-checks the README index status lines, and requires every RFC to declare **Supersedes / closes** (none or links) and every Superseded RFC to name **Superseded by**. The docs governance smoke runs its --check mode, so a stale index, a header without a lifecycle state, or a missing supersession declaration fails CI. The README keeps no hand-maintained status matrix; the generated file is the only enumerating surface. Move the dated checkpoint sections out of the alignment, authority, TypeScript-migration and capable-manager RFC bodies into ledger/<rfc-slug>/YYYY-MM-DD-slug.md entries (en + zh), leaving a one-line pointer where each section was. Each moved entry opens with a provenance line naming the source RFC, former heading, move date and RFC baseline. Relative links inside moved text are rewritten for the new location; the two anchors that pointed at a moved section now point at the ledger entry. A heading containing 'checkpoint' inside an RFC body is now a smoke failure. The ledger path stays docs/architecture/rfcs/ledger/. Header normalisation: add lifecycle status lines to the three RFCs that had none, label the inbox-hook and Stage A continuation RFCs Accepted (both were shipped and documented as such), index the three RFCs the README never listed, and rename the two 'Current implementation checkpoint' sections to 'Current implementation facts' because they describe present state rather than a dated log. TEMPLATE.md and ledger/README* document the new header fields and the checkpoint rule. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
The generated index claimed three rules it did not check, so a document could break each one and still pass `--check`: - state parsing matched a lifecycle word by prefix, so `Drafting notes are not a lifecycle state` was read as `Draft` and accepted. The value now has to end at a delimiter (`,`, `;`, `:`, `(` or an em dash), and the four headers plus five README index lines that described a state without one were given one. - `Superseded by: none`, a successor that does not exist, and a successor that never names its predecessor all passed. Supersession is now checked in both directions: a named RFC has to exist, has to be marked Superseded, and has to point back. - the body guard rejected any heading containing "checkpoint", which failed a legitimate `## Checkpoint persistence contract`, while dated logs without that keyword slipped through. The guard now flags dated log headings above the first appendix, which keeps append-only dated history and normative headings legal. `examples/docs-governance-smoke.py` states each of these as a fixture and runs the real CLI over a scratch copy of the RFC tree, so a rule that stops rejecting (or stops accepting) fails the smoke instead of passing quietly. The README and the generated index prose now say that a state change edits the header and the README index line, which is what the cross-check has always required. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
34c951c to
b38dea1
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
Reviewed head: b38dea1cade6f5101baa43855bc0228e1fc9ff5a
Base: b15413ffcb9d362e6b30c960a6be49359dab1471 (main)
This head answers the three P2 findings and the README finding in the previous
REQUEST_CHANGES review (pullrequestreview-5326601428) and supersedes it.
动机
上一个 head 把「RFC 头部状态」确立为生命周期索引的唯一权威输入,但生成器只在文档里宣称了三条规则,并没有把它们变成能拒绝的检查,于是三条规则各自都能被绕过去,而它们本来要保护的东西正在静默腐烂。逐条复现(前一轮 review 的 P2):状态按前缀匹配,**RFC status:** Drafting notes are not a lifecycle state 会被读成 Draft 并通过;**Superseded by:** none、指向不存在的 missing-successor-v0.md、以及继任者自己写 Supersedes / closes: none 的反向不一致,三种情况 --write 与 --check 都退出 0;正文守卫按关键词匹配,真正该迁走的带日期记录标题(如 ### 2026-09-24 — shipped)漏过去,而合法的规范标题 ## Checkpoint persistence contract 反而被判违规、被迫改名。加上 README 里「只改头部」的指引与实际交叉校验不一致(只改头部会让 --check 变红),这套靠约定维持的生命周期治理等于没有守卫:登记错了不会变红,迁移过的正文可以重新长回带日期的日志。本次请求就是把这四条落地,目标判定为 goal_achieved:暴露出的规则空洞已闭合,不需要后续 PR 承接。对持续进展的意义是,RFC 状态与 ledger 迁移从此由机器而不是读者注意力维持;对使用者而言,报错信息直接给出修复动作,而不是让人猜为什么变红。
改动思路
入口仍然是 python3 scripts/generate_rfc_status_index.py --check(维护者本地执行,examples/docs-governance-smoke.py 也会调用它),没有新增命令、模块或第二份索引。权威输入是每个 RFC 头部三行(RFC status、Supersedes / closes、Superseded by)、README 索引里对应条目的 **RFC status:** 行,以及正文的标题结构。判定权集中在生成器的三个函数:normalize_state 负责把生命周期词解析成受控取值,_supersession_problems 负责双向一致性,dated_checkpoint_headings 负责区分「附录里的历史」和「正文里的带日期日志」。副作用是重写 STATUS.md 与 STATUS.zh-CN.md 两个生成文件,消费者是文档站导航和这些 RFC 的读者;失败时 --check 退出 1、逐行打印问题,并在消息里写明修复动作(补分隔符、补 Superseded by、把记录移进 ledger/<slug>/),所以失败方自己就能恢复。复用与去重的判断:三条规则都留在既有生成器内,fixture 复用 examples/docs-governance-smoke.py 既有的检查表,没有另开测试文件;smoke 里那份独立正则是仓库树的自查,fixture 组则直接驱动真实 CLI,两者是「独立复核」和「真实执行」的分工,而不是两套并行实现。
具体改动
生成器侧:生命周期取值改成「精确状态词 + 分隔符」再映射到受控枚举;Superseded by 与 Supersedes / closes 现在双向校验——被指向的 RFC 必须存在、必须标为 Superseded、必须在自己的 Supersedes / closes 里回指先驱,none 不再被当作继任者;正文守卫从前缀无关的 checkpoint|检查点 关键词改为「首个附录标题之前出现带日期的记录标题」。文档侧:四个头部(benchmark-study-upload-dashboard-v0、external-evidence-research-capability-v0、loopx-overall-roadmap-v0、obelisk-session-evidence-provider-v0)和五行 README 索引把描述性后缀改成用逗号分隔,README 的生命周期小节改写为「头部 + README 索引行一起改、继任者必须回指」,生成索引里的说明文字同步更新。验证侧:smoke 新增五个正向 fixture(含类型化后缀、无边日的 Checkpoint 规范标题、附录内保留的带日期历史、双向成立的替代链、带中文镜像声明的 RFC)和八个反向 fixture(未类型化状态、Superseded by: none、继任者不存在、继任者不回指、先驱不承认继任者、Supersedes / closes 既不是 none 也不是 RFC、正文带日期日志、正文带日期的 checkpoint 标题),每个反向用例还断言只报出这一条问题,避免被无关错误掩盖。
关键代码讲解
scripts/generate_rfc_status_index.py:130normalize_state:把状态值解析为受控生命周期枚举。改动前用startswith逐前缀匹配,Drafting notes are not a lifecycle state会命中draft;改动后要求状态词后面紧跟行尾或(、,、;、:、—之类的显式分隔符,再把小写形式映射回Draft/Under review/Accepted/Superseded/Retired/Rejected。输入是头部或 README 索引行的原始文本,返回None时validate会打印「does not begin with a lifecycle state」并指出需要分隔符。scripts/generate_rfc_status_index.py:329_supersession_problems:替代关系的双向一致性判定,是本 head 新增的语义。输入是单条记录加上整目录的名字索引;Superseded状态必须给出非none的继任者,被指向的文件必须在目录里存在,继任者的Supersedes / closes必须回指本条,反向亦然。它只返回问题列表,由validate统一打印,因此不会改变渲染结果。scripts/generate_rfc_status_index.py:192dated_checkpoint_headings:正文守卫的判定范围。改动前是全文正则匹配checkpoint/检查点,改动后先按^##\s+(Appendix\b|附录)切出附录起始位置,只检查其之前的正文,并只在标题以日期开头、或同时含 checkpoint 字样与日期时报错——这样规范标题和附录里保留的带日期历史都能继续通过。examples/docs-governance-smoke.py:275check_rfc_status_index_rules:把上述规则变成可失败的验收。它把生成器和整个docs/architecture/rfcs/复制到临时根目录,按真实 RFC 形状写入 fixture、补 README 索引行,然后先--write再--check,正向用例要求退出 0、反向用例要求退出非 0 且错误信息命中预期,并断言只报一条问题。examples/docs-governance-smoke.py:238check_rfc_status_index:仓库树上的自查仍然独立成立,只是判定条件与生成器同步为「附录之前的带日期日志」,因此 CI 与本机执行的是同一个契约。
对主干的风险
No blocking finding. 本轮没有发现阻塞性问题,下面是残留风险、最强缺失验证与已知的无关红灯。
最大风险是「守卫比事实更严」,把合法文档判为违规——这正是上一版的镜像问题。缓解证据有两层:其一,改动后的 --check 在整个 docs/architecture/rfcs/ 树上退出 0,说明现有 37 个 RFC 的头部、README 交叉校验、替代声明与正文结构都满足新规则;其二,五个正向 fixture 分别覆盖了曾经会被误判或容易误判的写法——带描述后缀的状态值 Draft, under maintainer review、无边日的 ## Checkpoint persistence contract、附录内的带日期历史、双向成立的替代链、带中文镜像声明的新 RFC,都在真实 CLI 上退出 0。可以预见的正当失败是:以后有人把头部写成 Draft something(无分隔符)会变红,这是有意的收紧,错误信息直接给出「需要 , 或 ; 之类的分隔符」。另一处残留风险是 smoke 中的独立正则与生成器正则需要同步演进,本次通过注释互相指向、并让 fixture 组以真实 CLI 为准来收敛该风险;若两者将来分歧,fixture 组会先失败。未验证项按计划保留:不轮询远端 CI,本 head 的验证全部是仓库内本地执行,报告在验证矩阵中。三个与该改动无关的既存红灯(test_semantic_vocabulary_drift.py 的 3 条)已用 base/head 同命令同签名归因,属于 pre_existing_unrelated,不由本 PR 承接。
语义与 CI 对齐
本 PR 不引入新词表,只把既有生命周期取值(TEMPLATE.md 与 README 早已使用的 Draft/Under review/Accepted/Superseded/Retired/Rejected)变成可执行判定,属于 reuse_existing。CI 义务按仓库现有本地检查执行:scripts/generate_rfc_status_index.py --check、examples/docs-governance-smoke.py、ruff check 以及受影响的 RFC 消费者测试;其中 docs-governance-smoke.py 已包含新 fixture,因此规则退化会在仓库内 CI 直接失败,而不是等到人工审阅。
我的整体评价
这份改动修的是「声称有守卫、实际没有守卫」的问题,方向正确且规模克制:没有新模块、没有新命令、没有第二份状态源,三处规则各自可拒绝也可放过,并用真实 CLI 上的正反 fixture 把「能拒绝」与「不误伤」同时钉住。我的整体结论是 APPROVE,并建议按维护者流程合并;outcome_impact 两个维度都是 improved——长期看 RFC 生命周期与 ledger 迁移由机器守卫维持,使用者看到的是带修复动作的失败信息而不是无声腐烂。唯一需要保留的注意点是上面写的头部分隔符收紧属于有意的默认行为变化,已在 README 生命周期小节、生成索引说明和 smoke 用例中披露。
English verdict: APPROVE
|
Merge decision (maintainer repair at the merged head) Reviewed head: What changed since the first head ( Validation at this head: The earlier review |
Summary
RFC governance in three parts, keeping the existing
docs/architecture/rfcs/ledger/path (nodocs/archive/move).scripts/generate_rfc_status_index.pyderivesdocs/architecture/rfcs/STATUS.md+STATUS.zh-CN.mdfrom each RFC's own status header, bucketed as Accepted / Active (Draft, Under review) / Superseded / Retired (Retired, Rejected). It cross-checks the README index status lines against the headers and reports RFCs missing from the index.--writeregenerates,--checkfails on stale index or header violations, andexamples/docs-governance-smoke.pynow runs--check. The README keeps no hand-maintained status matrix (the existing"## Status matrix" not in rfc_indexassertion is untouched); the generated file is the only enumerating surface.**Supersedes / closes:** none | <links>(zh:**替代 / 关闭:**);SupersededRFCs must also carry**Superseded by:**. All 37 current RFCs were given an explicitnone, so there is no legacy allowlist — the rule is hard from this PR on. TEMPLATE.md documents the fields and addsRetiredto the status vocabulary.… checkpointsections (en+zh, 23 files) in the alignment, authority, TS-migration and capable-manager RFCs moved unchanged intoledger/<rfc-slug>/YYYY-MM-DD-slug.md, leaving a one-line pointer where each section was. Each moved entry opens with a provenance line (source RFC, former heading, move date, baseline3e443ad7c). Relative links inside moved text were rewritten for the new depth and verified to resolve; the two anchors that pointed at the moved "Legacy field-rule retirement checkpoint" now point at the ledger entry. Three entries that existed in only one language got a mirror. A heading containingcheckpoint/检查点in an RFC body is now a smoke failure.Reviewer-visible decisions (please push back if any is wrong)
cross-session-memory-substrate-v0(Explicit Todo continuation, Stage A) had no status header; it is labelled Accepted because Stage A shipped in feat(todos): explicit revision-guarded cross-agent session continuation (Stage A) #4094 and the README describes it as shipped. README entry gained a status line.provider-neutral-turn-start-inbox-hook-v0statusImplemented behind explicit provider configuration→Accepted; implemented behind explicit provider configurationso the lifecycle state is machine-readable; delivery wording preserved.harness-selection-dsh-pi-v0andmanager-runtime-profile-v0had free-textStatus:lines and were not in the README index; both getRFC status: Draft (…)headers with the original wording kept in parentheses, and README entries.external-evidence-research-capability-v0was also unindexed and gets an entry.## Current implementation checkpoint→## Current implementation factsin the authority and TS-migration RFCs (en+zh): the content is present-state description, not a dated log, so it stays in the body.## Observation retirement checkpoint (2026-09-24)was a summary that already pointed at an existing ledger entry; it was folded into a bold lead paragraph rather than duplicated as a second entry.LEDGER_APPENDIX_HEADINGin the smoke now acceptsAppendix B: Decision and execution ledger(capable-manager) since that RFC now has a ledger directory; the alignment RFC gained anAppendix A: Execution ledgerpointer.Not in this PR
Dated sections whose headings do not say "checkpoint" (e.g.
## Managed Execution Surface (2026-09-15)and the steward-channel sections in the DSH/Pi RFC,## Delivery status and integration direction (2026-09-13)in Stage A, the inline2026-09-24: …pointer lines at the tail of the TS-migration zh mirror) are left as-is; they are candidates for a follow-up once this convention has landed.README.zh-CN.mdfor the RFC index does not exist and is not created here.Validation
python3 scripts/generate_rfc_status_index.py --check→ 0 problems; stale detection verified by mutating STATUS.md (exit 1).examples/docs-governance-smoke.py→docs-governance-smoke ok(includes mirror pairing, ledger naming, local-link resolution, new status-index and checkpoint-heading checks).pytest tests/capabilities/test_steward_executor_machine_defaults.py tests/test_chat_machine_configuration_api.py tests/architecture/test_semantic_vocabulary_drift.py tests/test_loopx_turn_settlement_parity.py(the tests that cite RFC files) → all pass.loopx check --scan-pathon all 103 touched files →errors=0.git diff --checkclean. DCO signed.Maintainer repair at the merged head
Reviewed head:
b38dea1cade6f5101baa43855bc0228e1fc9ff5a, rebased ontomainb15413ffcb9d362e6b30c960a6be49359dab1471; merge commitdfa6d005abbf3628ef7bc9e7d6b86be9294ba603. The merged tree hash equals thereviewed head tree, so
maincarries exactly the reviewed content.The earlier head (
34c951c45) carried a review that required three rulerepairs; this head answers them and supersedes that review:
delimiter (
,,;,:,(or an em dash).Drafting notes are not a lifecycle stateis no longer read asDraft. Four RFC headers and fiveREADME index lines that described a state without a delimiter were
normalised in the same commit.
Superseded by: noneis nota supersession, a named successor has to exist, and the successor has to
name the predecessor back in
Supersedes / closes.including dated
checkpointheadings — instead of rejecting any headingthat contains the word
checkpoint. A normative## Checkpoint persistence contractand the append-only dated history anappendix keeps stay legal.
examples/docs-governance-smoke.pystates each rule as a fixture and runs thereal CLI over a scratch copy of the RFC tree (5 positive, 8 negative cases, one
asserting that exactly one problem is reported), so a rule that stops rejecting
or stops accepting fails the smoke. The README lifecycle section and the
generated index prose now say that a state change edits the header and the
README index line, which is what the cross-check has always required.
Validation at the repair head
python3 scripts/generate_rfc_status_index.py --check→ exit 0 on the fullRFC tree.
examples/docs-governance-smoke.py→docs-governance-smoke ok, includingthe new fixture suite.
ruff check examples/docs-governance-smoke.py scripts/generate_rfc_status_index.py→ clean.
and restoring the keyword-only checkpoint guard each abort the smoke, so the
fixtures are not vacuous.
pytest -q tests/architecture/test_semantic_vocabulary_drift.py tests/test_loopx_turn_settlement_parity.py tests/test_chat_machine_configuration_api.py tests/capabilities/test_steward_executor_machine_defaults.py→ 3 failed,144 passed at both
main(b15413ffc) and this head, with identical failingidentities; the three failures are pre-existing and unrelated to this diff
(they belong to the semantic-vocabulary inventory report, which this PR does
not touch).