diff --git a/docs/architecture/rfcs/ledger/README.md b/docs/architecture/rfcs/ledger/README.md new file mode 100644 index 0000000000..48ba494f19 --- /dev/null +++ b/docs/architecture/rfcs/ledger/README.md @@ -0,0 +1,32 @@ +# RFC execution ledger + +One file per entry. A ledger entry records what a change measured, what it +changed, and what it deliberately did not establish — the same content that used +to be appended to an RFC's Appendix A. + +## Why entries are files + +Appendix A was a single append cluster. Every branch adding an entry inserted at +the same position, so concurrent work conflicted there by construction: eight +times in one afternoon during the #4447 repair round, each resolved by hand as +"both sides are disjoint, keep both". That is mechanical work with a real +failure mode, because one careless resolution silently drops an entry. + +A file per entry removes the shared line. Two branches adding entries on the +same day touch two different files and merge cleanly with no resolution at all. + +## Convention + +- `/YYYY-MM-DD-slug.md` — one directory per RFC, named exactly like + the RFC file it belongs to. Six RFCs carry an execution-ledger appendix, so + an entry has to say which one it extends. +- The name carries the date the work was measured, not the merge date. +- Each entry carries a Chinese mirror at `/YYYY-MM-DD-slug.zh-CN.md`, + the same rule the RFCs themselves follow. +- A directory is the index for its RFC. Nothing enumerates entries across + RFCs, because an index line is the append cluster this directory exists to + remove. +- Entries are append-only history. Correct a wrong entry with a later entry that + says what was wrong; do not rewrite a record someone may have cited. + +`examples/docs-governance-smoke.py` checks the naming and the mirror pairing. diff --git a/docs/architecture/rfcs/ledger/README.zh-CN.md b/docs/architecture/rfcs/ledger/README.zh-CN.md new file mode 100644 index 0000000000..0f6f33243b --- /dev/null +++ b/docs/architecture/rfcs/ledger/README.zh-CN.md @@ -0,0 +1,27 @@ +# RFC 执行账本 + +每条一个文件。一条账本条目记录某次改动测到了什么、改了什么、以及明确没有确立 +什么——就是过去追加到 RFC 附录 A 的那些内容。 + +## 为什么条目是文件 + +附录 A 是一个共享的追加簇。每个新增条目的分支都插在同一个位置,所以并发工作在 +那里**必然**冲突:#4447 的修复轮里一个下午撞了八次,每次的解法都是"两侧不相交、 +保留双方"。这是机械劳动,而且有真实的失败模式——一次粗心的解决就会静默丢掉一条 +记录。 + +一条一个文件消除了这行共享。同一天新增条目的两个分支碰的是两个不同文件,无需 +任何解决即可干净合并。 + +## 约定 + +- `/YYYY-MM-DD-slug.md` —— 每份 RFC 一个目录,目录名与其所属 RFC + 文件名完全一致。有六份 RFC 带执行账本附录,所以条目必须写明它补的是哪一份。 +- 文件名中的日期是**测量**当天,不是合并当天。 +- 每条配一份中文镜像 `/YYYY-MM-DD-slug.zh-CN.md`,与 RFC 自身同一规则。 +- 每个 RFC 的目录就是该 RFC 的索引。跨 RFC 不设枚举,因为索引行正是本目录要消除的那种 + 追加簇。 +- 条目是只追加的历史。要更正一条写错的条目,用后续条目说明错在哪;不要重写别人 + 可能已经引用过的记录。 + +命名与镜像配对由 `examples/docs-governance-smoke.py` 校验。 diff --git a/docs/architecture/rfcs/ledger/semantic-vocabulary-convergence-v0/2026-09-18-ledger-entries-become-files.md b/docs/architecture/rfcs/ledger/semantic-vocabulary-convergence-v0/2026-09-18-ledger-entries-become-files.md new file mode 100644 index 0000000000..472282c6d8 --- /dev/null +++ b/docs/architecture/rfcs/ledger/semantic-vocabulary-convergence-v0/2026-09-18-ledger-entries-become-files.md @@ -0,0 +1,34 @@ +# Ledger entries become files + +Non-normative for the semantic model; it adds no invariant and changes no +check's verdict on any tree. What changes is where an execution-ledger entry +lives. + +- **The problem was measured, not anticipated.** RFC Appendix A was one append + cluster. During the #4447 repair round, four branches each added an entry and + every pair conflicted at the same position — eight resolutions in one + afternoon, all of the form "the two sides are disjoint additions, keep both". +- **The failure mode is silence.** A conflict that is always resolved the same + way stops being read. The resolution that drops an entry looks exactly like + the seven that did not, and nothing downstream notices: no code parses + Appendix A, so a lost entry surfaces only when someone goes looking for a + record that is no longer there. +- **A file per entry removes the shared line.** Two branches adding entries on + the same day now touch two different files and merge with no resolution. +- **Nothing enumerates the entries.** An index in the RFC would be a one-line + append cluster — smaller than the original, but the same class of problem. + The directory listing is the index, and the `YYYY-MM-DD-slug` name sorts. +- **The convention is checked, not described.** `check_rfc_ledger_entries` in + `examples/docs-governance-smoke.py` requires the dated name, a Chinese mirror + beside each entry, and non-empty content. Verified by mutation: a file named + `badname.md` and an entry with no mirror each fail the smoke. +- **Existing entries were left in place.** The entries already in + Appendix A are append-only history that nobody edits, so they were never the + thing that conflicted. Migrating them would have produced a large mechanical + diff, forced rework on the one open #4447 branch, and fixed nothing. + +What this does not do: it does not make ledger entries reviewable evidence. +They remain non-normative records of what a change measured and what it did not +establish. It also does not add them to the hosted documentation navigation — +the nav check requires a file for every nav entry, not a nav entry for every +file. diff --git a/docs/architecture/rfcs/ledger/semantic-vocabulary-convergence-v0/2026-09-18-ledger-entries-become-files.zh-CN.md b/docs/architecture/rfcs/ledger/semantic-vocabulary-convergence-v0/2026-09-18-ledger-entries-become-files.zh-CN.md new file mode 100644 index 0000000000..41780d6a43 --- /dev/null +++ b/docs/architecture/rfcs/ledger/semantic-vocabulary-convergence-v0/2026-09-18-ledger-entries-become-files.zh-CN.md @@ -0,0 +1,27 @@ +# 账本条目改为一条一个文件 + +对语义模型非规范:不新增任何不变量,也不改变任何检查在任何树上的结论。改变的是 +执行账本条目存放的位置。 + +- **问题是实测出来的,不是预想的。** RFC 附录 A 是一个追加簇。在 #4447 的修复轮 + 里,四个分支各自新增一条条目,两两都在同一位置冲突——一个下午解了八次,形式 + 全都是"两侧是不相交的新增,保留双方"。 +- **它的失败模式是静默的。** 一个每次都用同样方式解决的冲突,就不再有人真的去 + 读。那次丢掉条目的解决,看起来和没丢的另外七次一模一样,而且下游毫无反应: + 没有任何代码解析附录 A,所以丢失只有在某人回头找一条已经不存在的记录时才会 + 浮现。 +- **一条一个文件消除了这行共享。** 同一天新增条目的两个分支现在碰的是两个不同 + 文件,无需任何解决即可合并。 +- **没有任何地方枚举条目。** 在 RFC 里放一份索引会变成一行的追加簇——比原来小, + 但属于同一类问题。目录列表就是索引,`YYYY-MM-DD-slug` 命名本身可排序。 +- **约定是被校验的,不是被描述的。** `examples/docs-governance-smoke.py` 里的 + `check_rfc_ledger_entries` 要求带日期的命名、每条配一份中文镜像、内容非空。 + 已用突变验证:一个名为 `badname.md` 的文件、一条没有镜像的条目,都会让 smoke + 失败。 +- **已有条目原地保留。** 附录 A 里已有的条目是只追加的历史、没有人会去编辑, + 所以它们从来不是产生冲突的那一部分。迁移它们只会产出一个庞大的机械 diff、迫使 + 唯一一个开着的 #4447 分支返工,而且什么问题都解决不了。 + +它没有做什么:它不会让账本条目变成可评审的证据——它们仍然是非规范的记录,记的是 +某次改动测到了什么、以及明确没有确立什么。它也不把这些条目加进托管文档导航:导航 +检查要求"每个导航条目都有文件",而不是"每个文件都有导航条目"。 diff --git a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md index 77490363fc..5b00686cc5 100644 --- a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md +++ b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md @@ -1158,6 +1158,22 @@ introduce a competing target state. ## Appendix A: Execution ledger (non-normative) +New entries are files, not sections here. Each one lives in +[`ledger//`](ledger/README.md) as `YYYY-MM-DD-slug.md` with a Chinese mirror +beside it, and nothing enumerates them — the directory listing is the index. + +The reason is measured, not stylistic. This section was a single append cluster: +every branch adding an entry inserted at the same position, so concurrent work +conflicted here by construction — eight times in one afternoon during the +#4447 repair round, each resolved by hand as "both sides are disjoint, keep +both". That is mechanical work whose failure mode is silent: one careless +resolution drops an entry nobody notices is gone. A file per entry removes the +shared line, and `examples/docs-governance-smoke.py` checks the naming and the +mirror pairing so the convention cannot rot back. + +The entries below predate that split and stay where they are. They are +append-only history, never edited, so they were never the thing that conflicted. + ### 2026-09-17 — B3: the retirement metric separates readers from mentions The six legacy should-run fields were budgeted by a token count: modules whose diff --git a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md index 090c332e53..47b25e14f0 100644 --- a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md +++ b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md @@ -932,6 +932,19 @@ PR review 保留这些层级。普通改动记录检查范围和理由,无共 ## 附录 A:执行账本(非规范) +新条目是文件,不再写在这一节里。每条放在 +[`ledger//`](ledger/README.zh-CN.md) 下,命名 `YYYY-MM-DD-slug.md` 并配 +一份中文镜像;目录名即所属 RFC 文件名;跨 RFC 不设枚举——每个 RFC 的目录就是它的索引。 + +这个理由是实测的,不是风格偏好。本节过去是一个共享的追加簇:每个新增条目的分支 +都插在同一位置,所以并发工作在这里**必然**冲突——#4447 的修复轮里一个下午撞了 +八次,每次解法都是"两侧不相交、保留双方"。这是机械劳动,而它的失败模式是静默 +的:一次粗心的解决就会丢掉一条没人会发现的记录。一条一个文件消除了这行共享, +命名与镜像配对由 `examples/docs-governance-smoke.py` 校验,约定不会退化回去。 + +下面的条目早于这次拆分,原地保留。它们是只追加的历史、从不编辑,所以从来不是 +产生冲突的那一部分。 + ### 2026-09-17 — B3:退休指标把读者与提及分开 六个旧 should-run 字段此前按 token 计数计入预算:文本里出现该独立字段名的模块 diff --git a/examples/docs-governance-smoke.py b/examples/docs-governance-smoke.py index b79788be8d..557e960c57 100644 --- a/examples/docs-governance-smoke.py +++ b/examples/docs-governance-smoke.py @@ -202,6 +202,54 @@ def check_rfc_language_mirrors() -> None: assert "语义镜像" in chinese_text, chinese.name +LEDGER_ENTRY_NAME = re.compile(r"^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*$") + + +def check_rfc_ledger_entries() -> None: + """Validate the per-file RFC execution ledger. + + The ledger exists so that two branches adding an entry on the same day touch + two different files instead of the same append cluster. That only holds while + every entry is its own file with a sortable name, so the naming is checked + rather than described. Nothing enumerates the entries: an index line would + reintroduce exactly the shared line this directory removes. + """ + ledger = DOCS / "architecture" / "rfcs" / "ledger" + if not ledger.is_dir(): + return + assert (ledger / "README.md").exists(), "ledger README missing" + assert (ledger / "README.zh-CN.md").exists(), "ledger README missing its Chinese mirror" + # Six RFCs carry an execution-ledger appendix, so entries are shared and + # must say which one they belong to. The RFC slug is a directory, and the + # directory has to name a real RFC: an entry cannot claim an RFC that does + # not exist, and the per-RFC listing stays the index. + scopes = sorted(path for path in ledger.iterdir() if path.is_dir()) + assert scopes, "ledger has no per-RFC directories" + for scope in scopes: + appendix_a = DOCS / "architecture" / "rfcs" / f"{scope.name}.md" + assert appendix_a.is_file(), ( + f"ledger directory {scope.name}/ does not name an RFC: " + f"{appendix_a.relative_to(DOCS.parent)} does not exist" + ) + assert "Appendix A: Execution ledger" in appendix_a.read_text(encoding="utf-8"), ( + f"{scope.name} has a ledger directory but no execution-ledger appendix" + ) + for entry in sorted(scope.glob("*.md")): + if entry.name.endswith(".zh-CN.md"): + english = entry.with_name(entry.name[: -len(".zh-CN.md")] + ".md") + assert english.exists(), ( + f"ledger entry has a Chinese mirror with no English original: {entry.name}" + ) + continue + assert LEDGER_ENTRY_NAME.match(entry.stem), ( + f"ledger entry must be named YYYY-MM-DD-slug.md, got: {entry.name}" + ) + chinese = entry.with_name(f"{entry.stem}.zh-CN.md") + assert chinese.exists(), f"ledger entry missing required Chinese mirror: {entry.name}" + assert entry.read_text(encoding="utf-8").strip(), f"empty ledger entry: {entry.name}" + assert chinese.read_text(encoding="utf-8").strip(), f"empty ledger entry: {chinese.name}" + + def mkdocs_nav_paths(mkdocs_text: str) -> set[str]: assert "\nnav:\n" in mkdocs_text or mkdocs_text.startswith("nav:\n"), mkdocs_text nav_body = mkdocs_text.split("nav:", 1)[1] @@ -853,6 +901,7 @@ def main() -> int: assert required in compact_multi_agent_product_recipe, required check_rfc_language_mirrors() + check_rfc_ledger_entries() print("docs-governance-smoke ok") return 0