Skip to content

docs(adr): generated README index, one status-line spelling, shared numbers cited by stem - #26503

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-rgjbqg
Oct 27, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-rgjbqg

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #26076
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:新增脚本生成的 docs/adr/README.md 索引(状态、编号、标题、日期、取代/被取代),143 条记录统一为一种状态行写法(**Status**: ,改了 17 个文件的那一行,内容不动);四个重号(0006 三个版本,0010/0019/0057 各两条无关决策)在各自文件头互相标注、在索引里并列,不改号不挪文件;check-adr-anchors.mjs 支持 ADR-0057-system-data-lifecycle-and-retention 这种按文件名引用,只指向一条记录;ui/docs/adr 以 ui- 前缀登记进根索引,文件不动;退役的 PRIORITIZATION.md 删除,其"落地即翻状态"规则并入索引约定。
  • 为什么改:143 条决策没有目录,四个编号各有两份文件,状态行四种写法让两种统计口径对同一批文件给出不同数字;裁决 A(评论 6417460375):做索引、并列重号、门禁不加。
  • 风险与回滚:只动文档与仓库脚本,不动运行时与发布面;lint 多一个独立步骤 pnpm check:adr-index(= 生成脚本自身的 --self-test,只跑 fixture、不断言任何记录的内容,再接永远 exit 0 的 --report);记录写错状态行只出 warning,索引过期不判红,不新增必过门禁;回滚 = revert。
  • 先看(按风险):1) scripts/check-adr-anchors.mjs 新增的 resolveQualified / auditQualifiedCitations(裸号语义不变,带 stem 的引用必须指到一条真记录,否则红);2) .github/workflows/lint.yml 新增的 ADR index is current (report-only) 步骤与 scripts/adr-index.mjs 的 Live tree battery(只证明脚本能读真目录,不断言记录);3) docs/adr/README.md 的 Shared numbers 节。
  • 两分钟验证:pnpm adr:index && git diff --quiet docs/adr/README.md && pnpm check:adr-index && pnpm check:adr-anchors —— 预期 README 无 diff,index 打印 "--report: OK",anchors 打印 OK 且含 "37 of them stem-qualified to one record"。
  • 需要你做的:docs/adr/** 与 .github/workflows/** 是受管面(Tier H),需 governed approver 审阅后合并;PR 保持 draft,席位不 ready、不排队。

What changes

The index — docs/adr/README.md is new and generated (node scripts/adr-index.mjs, pnpm adr:index). It carries a visible generated banner, a Conventions block (the one status spelling, the keyword vocabulary, the status-hygiene rule that PRIORITIZATION.md used to hold, how shared numbers and the ObjectUI registry are cited), a summary count per status, a Shared numbers section listing each of the four numbers side by side with the id to cite, the records table (number, title, status, date, supersedes, superseded by), and the ObjectUI registry table under the ui- prefix.

One status-line spelling — the generator reads exactly one line shape, **Status**: <keyword> …, and lists a record whose line is missing or names no keyword as Unparsed rather than guessing from a heading or a bullet. Today 143/143 root records and 11/11 ui records parse. The 12 files the card's grep found matching none of the four spellings, before normalization:

File What it had instead
0009-execution-pinned-metadata.md no status line at all — status stated nowhere in the header
0010-metadata-protection-model.md no status line at all — status stated nowhere in the header
0014-record-form-field-type.md no status line at all — status stated nowhere in the header
0091-grant-lifecycle-and-recertification.md no status line at all — status stated nowhere in the header
0092-sys-user-profile-field-delegation.md no status line at all — status stated nowhere in the header
0093-tenancy-mode-and-membership-lifecycle.md no status line at all — status stated nowhere in the header
0098-pinyin-search-companion-column.md - **Status:** (bullet, colon inside the bold)
0100-credential-field-channels.md - **Status:** (bullet, colon inside the bold)
0103-managedby-write-policy-and-engine-write-guard.md - **Status:** (bullet, colon inside the bold)
0104-field-runtime-value-shape-contract.md - **Status:** (bullet, colon inside the bold)
0132-multi-organization-runtime-is-open-core.md - **Status:** (bullet, colon inside the bold)
0133-org-management-open-basics.md - **Status:** (bullet, colon inside the bold)

Five more records had a spelling the card's grep accepted but the one-instrument rule does not (## Status heading in 0071; - **状态**: in 0117, 0118, 0121; - **Status**: in 0079). All 17 normalized files, with the spelling each had:

File Previous spelling
0009-execution-pinned-metadata.md no status line at all
0010-metadata-protection-model.md no status line at all
0014-record-form-field-type.md no status line at all
0071-dataset-semantic-layer-depth.md ## Status heading with the value on the next paragraph
0079-record-display-name.md - **Status:** (bullet, colon inside the bold)
0091-grant-lifecycle-and-recertification.md no status line at all
0092-sys-user-profile-field-delegation.md no status line at all
0093-tenancy-mode-and-membership-lifecycle.md no status line at all
0098-pinyin-search-companion-column.md - **Status:** (bullet, colon inside the bold)
0100-credential-field-channels.md - **Status:** (bullet, colon inside the bold)
0103-managedby-write-policy-and-engine-write-guard.md - **Status:** (bullet, colon inside the bold)
0104-field-runtime-value-shape-contract.md - **Status:** (bullet, colon inside the bold)
0117-owning-business-unit-record-stamp.md - **状态**: / Chinese key
0118-non-user-actor-contract.md - **状态**: / Chinese key
0121-declarative-endpoint-routing-namespace-and-channel-split.md - **状态**: / Chinese key
0132-multi-organization-runtime-is-open-core.md - **Status:** (bullet, colon inside the bold)
0133-org-management-open-basics.md - **Status:** (bullet, colon inside the bold)

Only the status line changed in those files; the value after it is byte-identical.

Shared numbers, cross-referenced and never renumbered (Prime Directive 13) — each of the nine files under 0006/0010/0019/0057 gains one header line directly under its status line. The 0010/0019/0057 pairs get **Shares its number with**: [sibling](./sibling.md) (title) — an unrelated decision under the same number … cite this record unambiguously as ADR-<stem>; the three 0006 files get **Versions of this number**: v2 · v3 · v4 (current) — one decision revised twice. The index lists all four side by side.

check-adr-anchors.mjs is unambiguous on those four — a citation may now be stem-qualified, ADR-NNNN-<slug>[.vN] (ADR-0057-system-data-lifecycle-and-retention, ADR-0006-project-environment-split.v4), and such a citation resolves to exactly one record; a stem that names no record is an error (so a typo cannot pass as a bare number), and a bare ADR-NNNN keeps its existing meaning (resolves against the number; the 4 bare ADR-0057 anchors stay on the existing ambiguity note, unchanged). Decision-letter citations on a stem-qualified id are checked against that one record's decisions. The grammar is the ADR filename grammar, so nothing new is invented. A new battery, Stem-qualified citations name ONE record (#26076), pins 21 cases; the battery floor moves from 6 to 7. 36 stem-qualified citations exist in the tree after this PR (the nine new header lines, the README, and the scripts' own literals) and all resolve.

Report-only in CI — a new lint step, ADR index is current (report-only — warnings, never a red on a record), runs the root alias pnpm check:adr-index (= node scripts/adr-index.mjs --self-test && node scripts/adr-index.mjs --report), in the alias form the GATE INVOCATION IDIOM note (c91544a) asks of a new gate step; the anchors step is unchanged. The self-test runs with the flag because check-self-test-wired reds a self-test no workflow executes, and it exercises fixtures only: its live-tree battery proves the generator reads and renders the real registry and never asserts on what a record says (the respin moved the two assertions that did — one spelling everywhere, ui records parse — out; they were already --report warnings). --report checks the committed index against a fresh rendering and every record's status line, prints ::warning file=…:: annotations (and a $GITHUB_STEP_SUMMARY block) for anything off, and exits 0 whatever it finds — so the only red this step can show is a broken generator, never a record or a stale index. --check is the gate's spelling (exit 1 on drift or an unparsed record) for the day a maintainer names one; nothing calls it in CI. No new required check, no branch-protection change.

ObjectUI registry (#26080, ruling 6417494426) — the 11 records under ui/docs/adr/ are listed in the root index under ui-NNNN with their files untouched. Their numbers overlap the root sequence (ui 0053, 0057 …) and mean different decisions, so an index row never reads ADR-NNNN for them, and a citation keeps the ObjectUI ADR-NNNN spelling the anchor checker already recognises as another registry's. Rows link to ../../ui/docs/adr/…, which check-adr-links resolves.

docs/adr/PRIORITIZATION.md removed — it declared itself retired; the one live rule it still carried (a PR landing an ADR's implementation flips the status line to Accepted — implemented with one line of evidence) is now the Status hygiene bullet in the README conventions, and ADR-0110's pointer to it names the README. Non-ADR notes stay out of the index by construction: a filename that is not NNNN-slug[.vN].md is reported as foreign, never listed.

Respin after the contract review (record 6434963378) — (1) the self-test no longer asserts on live records (above); (2) merged origin/main as a merge commit to clear the package.json conflict with c91544a and took its alias form; (3) README: a versions group now says the bare ADR-0006 names the decision whose current text is v4 and cites the superseded unversioned file by its stem; a **Decided**: line is a date line, so ADR-0079 dates to 2026-06-28; ADR-0071's status paragraph is folded onto its one line.

Stale Proposed — listed, not relabelled. 18 records carry Proposed with a status date before 2026-07-26 (the card counted 19; ADR-0025 is the 19th, see row 19). Each has an implementation reading from the tree (anchor files that the gate pins to it; files under packages/ naming the number). None of these status lines changed; the reading is for the records' authors and the maintainer, not a verdict.

19 stale Proposed records with an implementation reading each
# Record Status line as written Anchored source files Files under packages/ naming the bare number Implementation reading
1 ADR-0010 docs/adr/0010-metadata-protection-model.md Proposed 0 138 src / 77 test (bare number — counts cover both ADR-0010 records) partly realized, undeclared — 138 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
2 ADR-0019 docs/adr/0019-app-as-consumer-unit.md Proposed (v3 — narrowed to the consumer-surface decision) 0 90 src / 32 test (bare number — counts cover both ADR-0019 records) partly realized, undeclared — 90 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
3 ADR-0022 docs/adr/0022-connectors-vs-messaging-channels.md Proposed (2026-05-31) — routing decision recorded; both substrates now exist independently (`co… 0 21 src / 2 test partly realized, undeclared — 21 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
4 ADR-0026 docs/adr/0026-client-ui-plugin-distribution.md Proposed 0 0 src / 0 test not started — nothing under packages/ cites it. Still a proposal; Withdrawn is a one-line edit if it is no longer wanted.
5 ADR-0027 docs/adr/0027-metadata-authoring-lifecycle.md Proposed (2026-06-01) 0 6 src / 2 test partly realized, undeclared — 6 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
6 ADR-0038 docs/adr/0038-build-verification-loop.md Proposed (2026-06-11) — cloud-owned; framework slice partially implemented (2026-07-16 audit): … 0 7 src / 4 test partly realized, undeclared — 7 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
7 ADR-0046 docs/adr/0046-package-docs-as-metadata.md Proposed (2026-06-12, revised — simplified from the original directory/docSet design) 2 52 src / 29 test implemented, status never followed — the anchor gate pins 2 source file(s) to it (packages/rest/src/meta-item-read-gate.ts, packages/rest/src/meta-item-read-routes.ts). Candidate for Accepted — implemented + one line of evidence, by its author.
8 ADR-0050 docs/adr/0050-form-layout-vs-presentation.md Proposed (2026-06-15) · Amended (2026-10-20, letter B′ on [#24692](https://github.com/objec… 0 0 src / 0 test not started — nothing under packages/ cites it. Still a proposal; Withdrawn is a one-line edit if it is no longer wanted.
9 ADR-0051 docs/adr/0051-inline-metadata-views-in-docs.md Proposed (2026-06-16) — P1 authoring surface implemented (2026-07-16 audit): `ElementMetadataVi… 0 3 src / 1 test partly realized, undeclared — 3 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
10 ADR-0052 docs/adr/0052-audit-is-not-the-activity-feed.md Proposed (2026-06-16) — partially implemented (2026-07-16 audit): §5b declarative activity (`tr… 0 47 src / 16 test partly realized, undeclared — 47 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
11 ADR-0063 docs/adr/0063-two-kernel-agents-skills-are-the-extension-primitive.md Proposed (2026-06-22) — cloud-owned (2026-07-16 audit): the framework spec dependencies shipped… 1 33 src / 11 test implemented, status never followed — the anchor gate pins 1 source file(s) to it (packages/cli/src/commands/generate.ts). Candidate for Accepted — implemented + one line of evidence, by its author.
12 ADR-0064 docs/adr/0064-tool-scoping-to-agent.md Proposed (2026-06-22) — cloud-owned (2026-07-16 audit): the only framework dependency (`skill.s… 0 16 src / 5 test partly realized, undeclared — 16 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
13 ADR-0071 docs/adr/0071-dataset-semantic-layer-depth.md Proposed. (Refocused — see Correction below. The first merged draft also 0 12 src / 5 test partly realized, undeclared — 12 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
14 ADR-0072 docs/adr/0072-reference-scope-and-resolvability.md Proposed (2026-06-25) 0 31 src / 12 test partly realized, undeclared — 31 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
15 ADR-0073 docs/adr/0073-automation-execution-identity.md Proposed (2026-06-25) 0 23 src / 5 test partly realized, undeclared — 23 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
16 ADR-0077 docs/adr/0077-authoring-surface-boundary-hook-flow-validation.md Proposed (2026-06-28) 0 2 src / 1 test touched, not landed — 2 source file(s) and 1 test(s) mention it; the decision is still open.
17 ADR-0080 docs/adr/0080-ai-authored-ui-jsx-source.md Proposed (2026-06-29) — partially implemented (2026-07-16 audit): sdui-parser package (parse≠… 1 38 src / 7 test implemented, status never followed — the anchor gate pins 1 source file(s) to it (packages/sdui-parser/src/parse.ts). Candidate for Accepted — implemented + one line of evidence, by its author.
18 ADR-0096 docs/adr/0096-execution-surface-identity-admission.md Proposed (2026-07-15) 0 58 src / 40 test partly realized, undeclared — 58 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.
19 ADR-0025 docs/adr/0025-plugin-package-distribution.md Proposed — partially implemented (2026-09-12 audit, superseding 2026-07-16): the .osplugin ar… 0 51 src / 11 test the card's 19th: its status line carries a 2026-09-12 audit stamp, so the generated index dates it after the cut; by first proposal date it is stale. partly realized, undeclared — 51 non-test source files name it, no anchor pins any. Author to decide: accept with evidence, or narrow the record to what landed.

Counts read from git grep -l '\bADR-NNNN\b' -- packages (non-test vs __tests__/*.test.*) and from scripts/adr-anchors/*.json at this PR's head.

One row in scripts/pm/dispatch-gates.data.mjs — COMPOUND_ANCHOR_LEDGER classifies every compound self-test name the dispatch-gates anchor matches; the generator's runSelfTest is a new one, so check:pm-dispatch-gates reddened until it was classified (genuine battery, fixtures masked as hints) and the docblock's pinned counts moved 34→35 / 23→24. The ledger forbids dodging the anchor by renaming, so the row is the fix.

Out of scope, on purpose: no record is relabelled, renumbered or moved; no citation in the tree is rewritten to the stem form (bare citations keep working); no new required gate.

Tests

  • node scripts/adr-index.mjs --self-test — 46 assertions over 7 pinned batteries (record parsing incl. **Decided**: as a date line; the one spelling with every other spelling Unparsed; supersession refs extracted not invented; shared-number grouping and the versions cite-as; deterministic rendering of both registries; --check reds / --report never does; live tree: the generator reads and renders the real registry — no assertion on a record's content). Self-test shape copied from a landed one (battery roster, pinned floors, verdict handshake).
  • node scripts/check-adr-anchors.mjs --self-test — 133 assertions (was 112), new battery of 21 for stem-qualified citations: resolves to one record, malformed stem is red, versioned stem checks the .vN file, bare number unchanged, collision pair cited by stem is unambiguous, decision letters on a qualified id check that record only. The live run on the merged head: 37 stem-qualified citations resolve (the README's versions note is the 37th).
  • pnpm adr:index then git diff --quiet docs/adr/README.md — the committed index is byte-equal to a fresh rendering.
  • node scripts/check-self-test-wired.mjs and node scripts/check-self-test-workflow-commands.mjs — exit 0 with the self-test wired through the check:adr-index alias (the first pass of the gates caught the generator's self-test as unrun; fixed by wiring, not by an allowlist row).
  • pnpm check:adr-index — exit 0 (self-test, then --report: OK); --check — exit 0.

Acceptance notes

Verification

Head: 9e254cb3 (merge of origin/main 00874b9 + the respin commit). Commands below were run from the repository root on that head; exit codes captured before any pipe.

Scripts and index
node scripts/adr-index.mjs --self-test   → exit 0 · ✓ 46 assertions over the real parseRecord() / renderIndex() / verdict() paths
node scripts/adr-index.mjs               → exit 0 · unchanged docs/adr/README.md (231 lines)
node scripts/adr-index.mjs --check       → exit 0 · OK — docs/adr/README.md is current and every record parsed
pnpm check:adr-index                     → exit 0 · self-test green, then --report: OK — current, every record parsed. Report-only
node scripts/check-adr-anchors.mjs --self-test → exit 0 · ✓ 133 assertions
node scripts/check-adr-anchors.mjs       → exit 0 · OK (91 anchored file(s) … 138 decision number(s) … 59770 citation(s) across 8329 file(s) resolve, 37 of them stem-qualified to one record; …)
pnpm exec eslint --no-inline-config scripts/adr-index.mjs scripts/check-adr-anchors.mjs scripts/pm/dispatch-gates.data.mjs → exit 0
pnpm check:adr-links                     → exit 0 (README links incl. ../../ui/docs/adr/… resolve)
Dispatch gates — every command from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, reconciled with `--ran`

Pass 3 on the pre-merge head d1a0cfe5 ran all 76 derived commands green (--ran: 76 derived / 76 run / 0 unrun). On this head 9e254cb3 (merge of origin/main + the respin) the full pass 4 (73 derived commands) was still running when this body was written; the quick checks named above plus pnpm check:pm-dispatch-gates (1935 cases) are exit 0 on it, and the complete --ran record of pass 4 is posted as the os-dev-report comment on #26076 when it completes. Changed lines 1542 vs the human-merge threshold 3000: under.


Generated by Claude Code

…umbers cited by stem

docs/adr/README.md is now generated by scripts/adr-index.mjs from the records
themselves: status keyword, number, title, date, supersedes / superseded-by,
read from ONE status-line spelling (`**Status**: `). 17 records that spelled
the line another way (`- **Status:**`, `## Status`, `- **状态**:`) are
normalized to it; nothing else in them changes. The four numbers carried by
more than one file (0006 by three versions of one decision; 0010, 0019, 0057
by two unrelated decisions each) are listed side by side in the index and
cross-referenced in each record's header. Nothing is renumbered or moved
(AGENTS.md Prime Directive 13).

check-adr-anchors.mjs becomes unambiguous on those four: a citation may name
the record by its filename stem (`ADR-0057-system-data-lifecycle-and-retention`,
`ADR-0006-project-environment-split.v4`), which resolves to exactly one record;
a stem that names none is an error, a bare number keeps its existing meaning.
The 36 stem-qualified citations in the tree all resolve.

The index script carries a --self-test (46 assertions, 7 pinned batteries) and
is called REPORT-ONLY from the existing lint ADR step via `pnpm adr:index:report`:
a stale index or an unreadable record is a warning annotation, never a red
(maintainer ruling A: 门禁不加). `pnpm adr:index` regenerates.

ui/docs/adr (objectui's own sequence, #26080) is registered in the root index
under the `ui-` prefix with its files untouched; a citation keeps the
`ObjectUI ADR-NNNN` spelling. docs/adr/PRIORITIZATION.md, self-declared retired,
is removed; the one rule it still carried (status hygiene on landing an
implementation) lives in the index's Conventions block, and ADR-0110's pointer
to it now names the README.

Claude-Session: https://claude.ai/code/session_01WGufzgAZv5vcb4H6qvqBAM
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d1a0cfe518f162ef6a0e311e26236286232a7139
Local-runs: none
Scope: docs/adr/** (28 files: 26 records edited, PRIORITIZATION.md removed, README.md added), .github/workflows/lint.yml + scripts/adr-index.mjs, scripts/check-adr-anchors.mjs, scripts/pm/dispatch-gates.data.mjs (COMPOUND_ANCHOR_LEDGER), package.json, docs/adr/README.md as generated, scripts/ci/lane-verdict.mjs and scripts/check-self-test-wired.mjs (read, for the step's verdict wiring), and origin/main 98c02577 (c91544a4, the GATE INVOCATION IDIOM and the package.json hunk the PR collides with)

Inputs: card #26076 body and all 7 comments (ruling 6417460375 letter A, 门禁不加; os-dev report 6434816739), PR #26503 body and file list, git diff 07586f50..d1a0cfe5, and the head's check-runs read at 05:52Z, 05:56Z and 05:57Z (2026-10-27).

Check-runs on the head

ZERO check-runs and zero workflow runs exist on d1a0cfe5 at all three readings (commits/.../check-runs total_count 0; actions/runs?head_sha= 0; actions/runs?branch=claude/project-thread-rgjbqg 0; commit status pending with no contexts). Cause, read from the PR: mergeable_state: dirty at 05:56Z, unknown at 05:57Z. A read-only git merge-tree --write-tree origin/main d1a0cfe5 (a git object read; no worktree, no build, no gate) names ONE conflicted path, package.json: main's c91544a4 (#26482, merged 04:55Z) rewrote the three check:*-symbol-anchors lines directly under check:adr-links, where this PR inserts adr:index and adr:index:report. GitHub builds no merge ref for a conflicted PR, so no pull_request workflow ran. No gate family has a verdict for this head; every "exit 0" in the PR body is the dev's local reading only.

① Derived judgments

  1. Status-line normalisation changes no value — right. All 17 normalised files (0009, 0010-metadata-protection-model, 0014, 0071, 0079, 0091, 0092, 0093, 0098, 0100, 0103, 0104, 0117, 0118, 0121, 0132, 0133) change only the key spelling (**Status:**, - **Status:**, - **Status**:, ## Status + paragraph, - **状态**: → **Status**: ); the value after the colon is byte-identical in every hunk. Tree scan: 17 records lacked the one spelling at the merge-base, 0 at the head, 143/143 root and 11/11 ui records carry it. The 9 cross-reference lines (0006 ×3 "Versions of this number", 0010/0019/0057 ×2 "Shares its number with") are additions under the status line, naming the sibling and the stem-qualified id; the ADR-0110 edit replaces a pointer (PRIORITIZATION → README) inside its implementation plan, not a decision. No decision's meaning or status changes.
  2. The index is report-only in --report, but the step's --self-test line is a RECORDS gate — wrong. modeReport returns 0 unconditionally (verified: warnings only, exceptions caught). But the lint step also runs node scripts/adr-index.mjs --self-test, whose battery 7 (Live tree: every record parses and the registry is not empty) asserts on the live tree: live-every-root-record-carries-the-one-status-spelling (zero Unparsed under docs/adr/), live-ui-registry-parses (every ui/docs/adr record names a keyword), live-root-registry-has-no-foreign-files. selfTest() exits 1 on any failure; the step carries continue-on-error + id, so scripts/ci/lane-verdict.mjs reads outcome: failure and reds the lane, and Lint & Repo Gates is a REQUIRED context. Consequence: the next ADR (root or ui) written with any other status spelling, or with a value outside STATUS_KEYWORDS, reds a required check. That is the gate the ruling declined (letter A, 门禁不加; the card's own fallback said a named gate would check only index↔file consistency, never content), and the PR body, the lint.yml comment ("verifies the script, not the records") and deviation 1 of the dev report ("a stale index or an unparsed record still never reds the step") all state the opposite. Only the stale-index verdict is truly report-only. Remedy: keep the --self-test wiring (check-self-test-wired requires it) but take the two live-parse assertions out of --self-test (battery 7 may assert population and rendering on the live tree, and parse-ability on fixtures); --report already warns on them. (live-root-registry-has-no-foreign-files duplicates check-adr-anchors' existing filename red and adds no new accept-set change.)
  3. Self-test wiring in lint.yml — mechanism right, placement dated. pnpm adr:index:report is named by the step, so check-self-test-wired puts scripts/adr-index.mjs in its population and demands a --self-test run with the flag; the step's second line is that run, and adr:index was reduced to the plain generate so no self-testing alias is unwired. The step is run: | under the lane's bash -e default, so pnpm check:adr-anchors red still fails the step as before. On main since c91544a4, the GATE INVOCATION IDIOM says a gate step runs a root check:* alias and a new gate gets a key; the direct node scripts/adr-index.mjs --self-test line predates that rule (prose, not swept, so not mechanically red) and should become an alias when the step is re-authored on the rebase this PR needs anyway.
  4. check-adr-anchors.mjs stem-qualified resolution — right. resolveQualified keeps every bare citation bare (no suffix, or a one-word suffix that is no stem); auditCitedNumbers is untouched and still receives every row. Tree-wide at the head, the 8 distinct glued ids with two or more words are exactly the 8 stems this PR introduces (0057 ×2, 0019 ×2, 0010 ×2, 0006 .v2/.v4) and the 31 distinct one-word glued prose forms (ADR-0104-conforming, ADR-0112-shaped …) stay bare — 0 MALFORMED. The four shared numbers resolve by stem to one record each; version pins check the .vN file exists; anchor shards accept a qualified id only when it resolves; the ambiguity note lists bare anchors only; decision letters on a qualified id are checked against that stem's index alone (versions of one stem union — documented). Accept-set change: a two-or-more-word glued suffix that names no stem is a NEW red class tree-wide, with a message naming the real stems — right, and the tree is clean today.
  5. PRIORITIZATION.md rule carried without loss — right. Base text: "a PR that lands an ADR's implementation flips that ADR's Status: line to Accepted — implemented with one line of evidence (key file + test)". README Conventions "Status hygiene": same rule, same evidence clause, plus "in the same PR"; ADR-0110's pointer now names the README; the only other mention (ADR-0096, prose "PRIORITIZATION review should treat …") is not a link. check-adr-anchors' NON_RECORD_FILES moves PRIORITIZATION.md → README.md, so a re-added PRIORITIZATION.md would red there (right).
  6. Nothing renumbered, ui untouched — right. The docs/adr file set at base vs head differs by exactly −PRIORITIZATION.md +README.md; git diff -M --name-status shows no rename; git diff -- ui/ is empty (0 lines); the 11 ui records are listed under ui- with ../../ui/docs/adr/ links, which check-adr-links resolves relatively.
  7. README content notes (cosmetic, non-blocking). The Shared-numbers row "Cite as ADR-0006" points at the SUPERSEDED v3 file; a bare ADR-0006 names the decision whose current version is v4, so that cell misleads (suggest ADR-0006 v3 / ADR-0006-project-environment-split). ADR-0071's status cell is cut at the first line of a multi-line value and its Date is —; ADR-0079's Date is the 2026-08-08 reconstruction date (key is Decided, not Date); ADR-0046 carries a second **Status**: line at 287 but the generator reads line 3, the real one.

② Semver level

skip-changeset matches the diff: root package.json is private: true; scripts/, .github/, docs/adr/ ship in no package's files[]; no .changeset/*.md added. Clause-②: no and Metadata-protocol: none on the PR body are right — no spec, schema or metadata-author surface moves. Changed lines 1520 (+1436/−84, generated README included), under the 3,000 human-merge threshold; Tier H (docs/adr/, .github/workflows/) governs the landing regardless.

③ Boundary flags

  • Dev deviation 1 (self-test flag in the lint step): wiring is the only remedy check-self-test-wired allows — accepted as mechanism; the claim "an unparsed record still never reds the step" is FALSE (① item 2) — escalated, blocking.
  • Dev deviation 2 (model-free trailer pair Claude-Session: + Co-authored-by: Claude, session-URL footer in the PR body): matches AGENTS.md; the harness reminder yields to it — right.
  • Dev deviation 3 (ADR-0025 dated 2026-09-12 by its status-line audit stamp, listed as the 19th anyway, not relabelled): right; no status line of the 19 changed.
  • Dev deviation 4 (no (#26076) suffix in subject/title; Fixes #26076 in the body): acceptable.
  • Dev deviation 5 (pnpm lint not run whole; eslint on the two scripts only): CI was to be the reading and CI never ran — see check-runs; answered by the rebase.
  • COMPOUND_ANCHOR_LEDGER row ['scripts/adr-index.mjs', 'runSelfTest', false]: third column is accidental; runSelfTest is a genuine battery, so false is the right classification and the docblock counts 34→35 / 23→24 move by exactly one genuine row — right.
  • open_questions: [] — nothing to answer. out_of_scope_findings (4 bare ADR-0057 anchor files; 12 stale Proposed with sources naming them and no anchor; ADR-0046/0080 anchored while Proposed): noted, carried by the PR body for the director seat's ruling batches — right not to relabel (letter A).
  • New flag: the head conflicts with origin/main on package.json and has no check-runs; the next push must merge main (and then take the check:*-alias step form of c91544a4's idiom).

Implemented-by: claude/project-thread-rgjbqg
Reviewed-by: session_01WGufzfhobWEgUUrjz4rrbh

VERDICT: FAIL

… alias; versions cite-as; 0071 status on one line

Contract-review respin of the ADR index (seat 1, record 6434963378):

- scripts/adr-index.mjs --self-test no longer asserts on what the live
  records say (the one-spelling and ui-parses assertions moved out; they
  were already --report's warnings). The live-tree battery now proves only
  that the generator reads and renders the real registry. A record with
  another status spelling is a warning, never a red — ruling A, 门禁不加.
- The lint step is its own, report-only step running the root alias
  `pnpm check:adr-index` (= --self-test then --report), per the GATE
  INVOCATION IDIOM that landed in c91544a; the anchors step is back to
  `pnpm check:adr-anchors` alone. `adr:index` stays the regeneration alias.
- Shared numbers: a versions group states that the bare number names the
  decision and its current text is the highest version; the superseded
  unversioned file is cited by its stem, never offered as `ADR-0006`.
- A `**Decided**:` header line is a date line (ADR-0079 now dates to its
  decision, 2026-06-28, not its 2026-08-08 reconstruction).
- ADR-0071's status paragraph folded onto its one `**Status**:` line.

Claude-Session: https://claude.ai/code/session_01WGufzgAZv5vcb4H6qvqBAM
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang marked this pull request as ready for review October 27, 2026 06:09
@os-zhuang
os-zhuang enabled auto-merge October 27, 2026 06:09
@github-actions github-actions Bot added size/xl ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

VERDICT: PASS — addendum to the FAIL record 6434963378 (head d1a0cfe5); both blocking items are resolved on this head. Required lint lanes were still in progress at the last read (06:20:43Z); their conclusions remain the gate verdicts.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9e254cb312170de7eb087923de27b3e0610bc1ac
Local-runs: none
Scope: docs/adr/** (28 files: 26 records edited, PRIORITIZATION.md removed, README.md added), .github/workflows/lint.yml + scripts/adr-index.mjs, scripts/check-adr-anchors.mjs, scripts/pm/dispatch-gates.data.mjs (COMPOUND_ANCHOR_LEDGER), package.json, docs/adr/README.md as generated, scripts/ci/lane-verdict.mjs (read, for the step's verdict wiring), and origin/main 00874b9e (the merge base; c91544a4's GATE INVOCATION IDIOM and package.json hunk)

Inputs: card #26076 body and all 7 comments (ruling 6417460375 letter A, 门禁不加; the os-dev report 6434816739 is still the 05:49Z one, written on d1a0cfe5 before the respin — no post-respin report yet at 06:20Z), PR #26503 body (re-read at 06:20Z, 21,968 bytes) and file list (33 files), git diff 00874b9e..9e254cb3 (the merge base is 00874b9e, so the net diff is the PR alone; main's own changes in the merge — lint.yml, package.json, ADR-0087, the queue-membership record — are outside it and outside the file list), the delta d1a0cfe5..9e254cb3 limited to PR files, and the head's check-runs read at 06:18:39Z, 06:20:07Z and 06:20:43Z (2026-10-27).

Check-runs on the head

This head HAS check-runs (the package.json conflict is gone; mergeable_state: blocked now means only that required contexts are pending). At 06:20:43Z, completed and green: Governed Surface Queue Guard, Check PR Size, Check Documentation Links, Validate Package Dependencies, Type Check · workspace, Type Check · debt ledger, Dogfood Regression Gate, Build UI Deps, Plan Test Core shards, Auto Label, filter, and the four card-claim guards (each twice, from two trigger events). Still in progress at that read: every lint lane (Lint · repo & tooling gates, Lint · docs & code guards, Lint · scoped · tooling self-tests, Lint · PM dispatch-gates, Lint · CI, release & engine gates, Lint · ESLint, both other scoped lanes), Type Check · source gates, Type Check · consumer gates, Build Core, Test Core 1–3/8, Lint UI, Type Check UI, Build & E2E UI, Test UI 1–16/16. Nothing had failed. The lane that carries the new step (Lint · repo & tooling gates) and the lane that judges its wiring (Lint · scoped · tooling self-tests, check-self-test-wired / check-self-test-workflow-commands) therefore have no conclusion in this record; their conclusions are the gate verdicts on those two points, and this PASS is a reading of the diff. The maintainer's APPROVED review (os-zhuang, 06:09:04Z) is stamped on d1a0cfe5, the prior head; the PR is no longer draft and auto-merge is on, so a green head merges on the lanes alone.

① Derived judgments

  1. Blocking item (1) resolved — the self-test no longer asserts on live records. Battery 7 is renamed (Live tree: the generator reads the real registry (the records themselves are --report's business), 4 assertions, roster 6→4, floor stays 7) and now holds exactly: live-root-registry-is-read (≥1 record under docs/adr), live-ui-registry-is-read (≥1 under ui/docs/adr), live-index-renders (rendering longer than 10,000 chars) and live-verdict-is-a-reading-not-a-throw (verdict() returns {stale: boolean, problems: array, expected: string}). The three content assertions of the prior head — live-every-root-record-carries-the-one-status-spelling, live-ui-registry-parses, live-root-registry-has-no-foreign-files — and the 100-record population floor are gone. None of the four can red on a new record: parseRecord is pure string work over the filename and text (regex, split, find, slice), returns null for a non-record filename (collected as foreign, which no assertion reads) and never throws on content; renderIndex, statusCell, cell, refsCell, sharedNumbers and verdict are likewise pure string/array code with no throw path reachable from a record's text, status spelling or keyword — an Unparsed record renders as an **Unparsed** row and lands in verdict().problems, which the battery only type-checks. The one throw left in the live read is readFileSync on a DIRECTORY named *.md under either registry (EISDIR inside the battery's try/catch) — not record content, and check-adr-anchors already reds a non-record name there. The other live-tree read in the self-test (report-mode-is-exit-zero-by-construction) reads the script's own source, not records. So a --self-test red is reachable only from a broken generator, a run outside the repository root, or an emptied registry — exactly what the lint.yml comment and the PR body now claim.
  2. The step is report-only for record content; lane-verdict wiring is right. The step (id: adr-index-current-report-only, continue-on-error: true, run: pnpm check:adr-index) runs --self-test && --report. modeReport is unchanged since the prior head (verified then: warnings only, every exception caught, return 0; pinned by the report-mode-is-exit-zero-by-construction assertion). scripts/ci/lane-verdict.mjs reads outcome (never conclusion) of every continue-on-error step with an id and reds the lane on failure — so a self-test failure still reds Lint · repo & tooling gates, which is correct for a broken script and, per item 1, is no longer reachable from any record's content or status spelling. A stale README, a record with another status spelling or a keyword outside STATUS_KEYWORDS, and a foreign file all end as ::warning:: annotations at exit 0. The ruling's 门禁不加 now holds for both lines of the step.
  3. Blocking item (2) resolved — conflict cleared, alias idiom followed. ae90b7c7 merges origin/main 00874b9e as a merge commit (no rebase, no history rewrite); the net diff of package.json is two inserted lines under check:adr-links — adr:index (plain generate, no self-test, so nothing for check-self-test-wired to demand) and check:adr-index (--self-test && --report), the check:NAME = --self-test && RUN shape every neighbouring alias uses after c91544a4. The previous adr:index:report alias and the direct node scripts/adr-index.mjs --self-test line are gone; the lint step invokes the root alias only, as the GATE INVOCATION IDIOM note asks. lint.yml's net diff against the merge base is +15/−0: the new step and its comment, nothing else. The anchors step (ADR anchors + number uniqueness, id: adr-anchors-number-uniqueness-governed-code, pnpm check:adr-anchors) is byte-identical to main's.
  4. README cosmetic fixes landed, all three. (a) The ADR-0006 versions group now opens with "A bare ADR-0006 names the decision; its current text is [0006-project-environment-split.v4.md]"; the superseded unversioned file is cited as ADR-0006-project-environment-split "(unversioned file, superseded)" and v4 as "ADR-0006 (current) / ADR-0006 v4 / …" — the generator change is in renderIndex (current = last record of a versions group) and pinned by the new assertion a-versions-group-cites-the-bare-number-as-the-highest-version. (b) parseRecord now treats a **Decided**: header field as a date line (/^(date|decided|日期)$/i, pinned by a-decided-line-is-a-date-line), so ADR-0079 dates to 2026-06-28 (the decision) instead of the 2026-08-08 reconstruction stamp; the same rule gives ADR-0135 a date (2026-06-25, its **Decided**: line) where it had — — right, and the only other row that moved. (c) ADR-0071's status value is folded onto one line: the words are identical to the base's ## Status paragraph (two hard line breaks became spaces; nothing added or dropped), and the index cell now reads Proposed — Refocused — see Correction below… instead of being cut at a line break. No other record changed between the two heads.
  5. Prior RIGHT items hold on the merged head. (a) Status values: for all 26 edited records the first status line's value at 00874b9e and at the head is identical, the 0071 line-fold being the one whitespace-only exception; the 17 normalised files still change only the key spelling; the 9 cross-reference lines (0006 ×3 **Versions of this number**, 0010/0019/0057 ×2 **Shares its number with**) are additions under the status line. (c) PRIORITIZATION.md rule carried: the README CONVENTIONS "Status hygiene" bullet carries the Accepted — implemented + evidence rule "in the same PR"; ADR-0110's pointer names docs/adr/README.md; NON_RECORD_FILES lists README.md only, so a re-added PRIORITIZATION.md is foreign (a --report warning here; check-adr-anchors' own filename red stands). (d) scripts/check-adr-anchors.mjs is unchanged since d1a0cfe5 (resolveQualified/auditQualifiedCitations exactly as reviewed; the PR body's 36→37 stem-qualified count is the README's new versions note). (e) git diff -M --name-status over docs/adr and ui between the merge base and the head shows only D docs/adr/PRIORITIZATION.md and A docs/adr/README.md; no rename; git diff -- ui is 0 lines; the 11 ui records are listed under ui- with files untouched.
  6. COMPOUND_ANCHOR_LEDGER row and counts — unchanged and right. ['scripts/adr-index.mjs', 'runSelfTest', false] with the docblock 34→35 / 23→24 move; runSelfTest is also called from modeReport (warn, never exit), which the row's comment states.
  7. Not verified by this read (report-only consequences, non-blocking). Whether the committed README is byte-equal to a fresh rendering on the merged head (the dev's --check exit 0 claim) — a mismatch is a ::warning:: and --check is called nowhere in CI. Whether check-self-test-wired / check-self-test-workflow-commands accept check:adr-index as the wiring and leave adr:index unflagged — Lint · scoped · tooling self-tests was in progress at 06:20:43Z; its conclusion is the verdict.

② Semver level

skip-changeset still matches the diff: root package.json is private: true; scripts/, .github/, docs/adr/ ship in no package's files[]; no .changeset/*.md. Clause-②: no and Metadata-protocol: none on the PR body are right — no spec, schema or metadata-author surface moves. Changed lines 1542 (PR body) / 33 files, under the 3,000 human-merge threshold; Tier H (docs/adr/, .github/workflows/) governs the landing and a governed approver has approved (on the prior head).

③ Boundary flags

  • Prior round's blocking flag (self-test asserting on live records) — closed by ① item 1; prior round's conflict/no-check-runs flag — closed by ① item 3 and the check-runs section.
  • The maintainer's APPROVED review (06:09:04Z) is stamped on d1a0cfe5, the head the FAIL was rendered on, not on 9e254cb3; with auto-merge on and draft off, this head merges on green lanes without a second human read of the respin. The respin is the two blocking fixes plus the three README cosmetics and the merge of main — nothing outside what the FAIL asked for — so the approval's intent carries; noted, not escalated.
  • The PR body still says "PR 保持 draft,席位不 ready、不排队" and "Tier H: this PR stays draft"; the PR is draft: false with auto-merge enabled — a maintainer's flip, not the dev's. Stale sentence, harmless.
  • The os-dev report on [Decision] docs/adr: 143 ADRs with no index, four numbers used twice, 19 Proposed older than three months, three status-line spellings — how the registry gets put in order #26076 (6434816739) predates the respin: its tests/gates/deviations describe d1a0cfe5 (the adr:index:report alias and the direct --self-test line that no longer exist); the PR body says the pass-4 --ran record is posted "when it completes". At 06:20Z no post-respin report comment exists; the seat should expect one or carry the body's Verification block as the record.
  • Dev deviations 2–6 of the prior record: unchanged, still acceptable. Deviation 1 (self-test wiring) is now true as written on the head: the self-test verifies the generator, not the records.
  • No new dev flags on the card; open_questions remains []; out_of_scope_findings (bare ADR-0057 anchors; stale Proposed with implementations) remain carried by the PR body for the director's ruling batches — right not to relabel (letter A).

Implemented-by: claude/project-thread-rgjbqg
Reviewed-by: session_01WGufzfhobWEgUUrjz4rrbh

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-27T06:23Z

Head 9e254cb312170de7eb087923de27b3e0610bc1ac. Card #26076 (ruling 6417460375, letter A, 门禁不加), claim 6433585719, dev report 6434816739.

  • Contract review: owed at CONTRACT_REVIEW_TIER (governed: docs/adr/**, .github/workflows/lint.yml). FAIL 6434963378 at d1a0cfe5 found two blocking items: the --self-test asserted on live record content, and package.json conflicted with main. The addendum PASS 6435290273 names this head and finds both resolved. The self-test now has no assertion that record content can reach. The new check:adr-index step is report-only for records and reds only on a broken generator. package.json follows main's root check:* alias idiom. The anchors step is byte-identical to main. Status values, numbering and ui/docs/adr are unchanged.
  • Shape: Fixes #26076, skip-changeset, Clause-②: no, Metadata-protocol: none. 1,520 changed lines, which is under the human-merge threshold.
  • Landing: Tier H, governed. Maintainer os-zhuang approved at 06:09Z and enabled auto-merge. That approval is stamped on d1a0cfe5, the FAIL head. The respin after it is the two fixes, three README cosmetics and a merge of main, all read in the addendum record. This seat adds no landing write. The queue merges on green lanes. If branch protection dismisses the stale approval, the PR waits for the maintainer's re-approval.
  • Gates: lanes were still in progress at the addendum's read (06:20Z), and none had failed. The lane conclusions are the gate verdicts.

@os-zhuang
os-zhuang added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 79cd9ef Oct 27, 2026
65 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-rgjbqg branch October 27, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants