docs-gates: bring docs/adr/** and docs/audits/** into the doc-snippet walk, ledger-first (objectui#7856 card 2) - #8357
Merged
Conversation
… walk, ledger-first objectui#7856 card 2. Card 1 brought the top level of the root `docs/` tree into `check-doc-snippet-types`' walk and stopped there deliberately: the two subdirectories were a different review route. This lands them, as two exported constants (`ADR_DOCS`, `AUDIT_DOCS`) plus one `listDocuments()` call site each, in the `ROOT_DOCS` pattern card 1 established. `main()` refuses a verdict when either directory is missing from a real run, exactly as it does for `docs/`. LEDGER-FIRST, and that is the delivery rather than a step toward one. The 2026-09-07 triage ruling on the card is a statement about the documents, not about cost: an ADR states what was decided on a date and an audit states what was true on a date, so repairing a code block inside one falsifies the record. Every page in the two subtrees that carries a ts/tsx block is therefore named in `UNGATED_DOCS` with its measured count, and nothing inside either subtree is edited. Measured on `fedfa3e4` with the gate's own analyzer against the closure `--build-filter` names: four records, five `ts` blocks, 29 diagnostics — 21 syntax-phase and 8 semantic — split 26 under `docs/adr/**` and 3 under `docs/audits/**`. Four of the five blocks do not parse, so their semantic half is unmeasured rather than clean, and each entry says so. No fragment marker was written. An ungated document is never compiled, so a marker inside one of these records would declare a block this gate already does not read — debt with nothing to prompt its removal — while being an edit inside a dated record. The ledger alone keeps the block accounted for. The scan population moves 229 -> 244. Nothing from either subtree reaches the compiled tier, so the build filter is byte-identical and this widening costs the gate's job no build time. `check-doc-fence-languages` and `check-doc-component-types` do not gain the legs; the walk-equality pin extends its by-import subtraction with the two new enumerators rather than a filename list. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dxq7YqsLDMeZDZ5AzsgJX
This was referenced Sep 7, 2026
os-musk
marked this pull request as ready for review
September 7, 2026 14:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7856
Card 2, the remainder the
Release:line (5562488329) named and triage routed and shaped (5570217599). Card 1 landed as PR #8158 (9bfd6184) and is not reopened here.Base
fedfa3e4· head4e341a5b· four files · no edit insidedocs/adr/**ordocs/audits/**.What lands
scripts/check-doc-snippet-types.mjsgains two legs, in theROOT_DOCSpattern card 1 established — one exported constant plus one exported enumerator plus onelistDocuments()call site each:ADR_DOCS = { dir: 'docs/adr', recursive: true }+adrDocsPages()AUDIT_DOCS = { dir: 'docs/audits', recursive: true }+auditDocsPages()main()refuses a verdict (exit 2, "could not run") when either directory is missing from a real run, exactly as it does forROOT_DOCS. The two are checked as a pair, because a rename that took only one of them out would leave the other's count looking healthy.recursive: truehere againstROOT_DOCS'falseis the point rather than an inconsistency. Card 1's leg is non-recursive because its subdirectories were a different review route; these two legs are that route, so inside them there is nothing left to stop above, and a page filed underdocs/adr/superseded/tomorrow travels into the walk by itself.recursiveis now read by the shared walk rather than being a comment on a constant.Where the surface still stops is stated rather than left to be read off the collector: a subdirectory of
docs/that is neither of these two is in no leg.docs/screenshots/is today's only other one and holds images, so the gap is currently empty — written down now rather than discovered later, and asserted in the pin.Scan population 229 → 244.
LEDGER-FIRST — and it is the delivery, not a step toward one
Triage's ruling is a statement about the documents, not about cost:
So every page in the two subtrees that carries a
ts/tsxblock is named inUNGATED_DOCSwith its measured count, and nothing inside either subtree is edited. What the widening buys is objectui#5174's distinction and only that: a document inside the walk and named on the ledger is a KNOWN debt with a number, where a document outside the walk is "neither covered NOR declared ungated" — invisible to the gate's own accounting, which is strictly worse. Paying it down is a per-record decision for whoever owns the record.Census — measured on
fedfa3e4, with the gate's own analyzerInstrument:
analyze({ ungated: {} })for the population andcompileSnippets()for the phases, against the closure--build-filternames (pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2— 35/35 tasks successful). Not a hand count of fences.docs/adr/0001-master-detail-subform.mddocs/adr/0036-field-conditional-rules.mddocs/adr/0057-console-ai-chat-one-conversation-docked.mddocs/audits/2026-07-objectview-detailview-schema.mdFour records, five blocks, 29 diagnostics — 21 syntax-phase and 8 semantic — split 26 under
docs/adr/**and 3 underdocs/audits/**. TheRelease:line's 26/3 from8507a2283reproduces exactly on the current tree,docs/adr/0001included at 15. The remaining eleven pages in the two subtrees carry nots/tsxblock: they are covered, contribute nothing, and may not be ledgered (thestale-ungated-entrycheck refuses an entry on a block-less page — proven in control A2 below).0, it is UNMEASURED — and making such a block parse is the only way to learn what else is wrong with it. Each entry says so in its own words.docs/adr/0057is the one block in either subtree that parses, so it is the only entry whose number is complete.Per-block decision: ledger, no markers, no edit
All five blocks:
UNGATED_DOCSentry, no fragment marker, no edit inside the record.The marker was considered per Zone 1 item 3 and refused on a mechanical ground, not a stylistic one. An ungated document is never compiled at all (
analyzefilterscovered = documents.filter((d) => !(d in ungated))before any block is collected). AFRAGMENT_MARKERinside one of these records would therefore declare a block this gate already does not read — which is precisely the debt this file's own header names: "a marker on a block the gate no longer collects is debt nothing would ever fail to prompt the removal of". It would also be an edit inside a dated record, bought for nothing.Stated the way the brief asks: a marker would not falsify the record's claims — it declares a block a fragment and changes no statement the record makes — but it is not needed, because the ledger entry alone already keeps the block accounted for. Where the brief offered "prefer NO edit inside the record at all and say so", that is the branch taken, and it is why this PR's diff touches no
docs/file.A pin now holds that decision in place:
no fragment marker was written inside either record subtreefails on the first marker anyone adds, so a later marker has to argue for itself.Strictness region
Definition: the
Fence scanningbanner to EOF ofscripts/check-doc-snippet-types.mjs.fedfa3e4:2749d53ae3a8df033a53b8d7a354fa7e22ee2d1a17f6ad0c3d61122e904e084b(unchanged from card 1's landed value, so the README batches had not moved it)4e341a5b:029535f916466b29820a2c6eaf7c8027cdf0c4621fa15997fc2e3c1b71f16c04The scan population grew; no strictness rule moved. Proven by the region diff, which is 22 added lines and 1 replaced comment line, in exactly three places:
listDocuments(), plus their comment;main();Fence languages, the fragment-marker regex and its attachment walk, the verification-claim list, the ledger's re-derivation rules, the compiler options, the bound, the four controls and every diagnostic filter are byte-identical. The ledger itself (
UNGATED_DOCSand its docblock) sits ABOVE the banner and is outside this region by construction.Pins
scripts/__tests__/check-doc-snippet-types.test.ts— a newobjectui#7856 card 2block carrying the widening's own proof, in the shape this card admits rather than card 1's. Card 1 could prove its widening by showing its blocks in the COMPILED tier; card 2 may not make that claim and must not fake it, so the proof is the opposite shape and stricter: membership by name from the legs themselves; every block-bearing page in them is on the ledger; nothing from either leg reaches the compiled tier (asserted, not inferred — the day one is repaired, this test is what asks whether the record survived it); each leg entry carries a measured count, a diagnostic code, a phase and the word record, so "declared" cannot decay into an adjective; no marker inside either subtree; the recursion, the file/extension filter, the third-subdirectory stop, and the missing-directory refusal.stops at the top leveltest kept its leg assertion (rootDocsPagesis unchanged and still the only place non-recursion is decided) and gained the honest half: the walk now collects the two named subtrees, and a third subdirectory (docs/rfcs/in the fixture) is collected by nothing.scripts/__tests__/check-doc-fence-languages.test.ts— the by-import subtraction extends withadrDocsPages()andauditDocsPages(); it was never a filename list, which is exactly why card 2 costs it three enumerators instead of a rewrite. Non-emptiness is now asserted per leg, because a union that is non-empty overall stays green with one member returning nothing. Its boundary test could no longer say "no nested page reaches either walk" — card 2 makes that false on purpose — so it says the stronger thing it still can: the snippet gate's nested pages are exactly the two subtree legs, and the fence guard's are still none.scripts/__tests__/check-doc-expression-carriage.test.ts— run as a control, untouched, green.Sibling gates
check-doc-fence-languagesandcheck-doc-component-typesdo not gain the legs.Controls — by state, with on-disk proof and restore proven by blob sha
The brief's control (a) is inapplicable as written: no block in either subtree compiles, because ledger-first means all four records are ungated. Its fallback ("use
stale-ungated-entry… an entry whose count is wrong must red") is falsified on the instrument — that check verifies existence and block-presence, never a count; a stale number in an entry fails nothing, which is why the ledger's own header refuses to write counts and why this PR's entries say their numbers are dated measurements. Three controls were run instead, each stronger than a count test.Every leg: mutate → prove it reached disk by anchored
grep -cand a changedgit hash-object→ run → restore under atrap … EXIT INT TERMwith absolute paths → prove restore bygit diff HEADempty and the blob back to the HEAD blob2e506a998a38dd56d6cb00656a77f0d5d476ae4f. All three ran against the committed tree.A1 — the widening judges the records; only the ledger holds them out. Dropped the
docs/adr/0057entry (key hits 1 → 0, blob →af7a4d94). GateEXIT=1, naming the record byfile:line:col:all 8 measured diagnostics, and the counters moved with it:
4 ungated → 3,240 covered → 241,Semantic phase: 632 of 632 judged, 1 failed.A2 — the ledger contract is live on these paths. Parked an entry on
docs/adr/0034-unified-runtime-metadata-persistence.md, a leg page with no block (injected text hits 0 → 1, blob →624992fd). GateEXIT=1:docs/adr/0034-… [stale-ungated-entry] document holds no ts/tsx fenced block.B — the silent skip is impossible. Removed the two call sites (call-site hits 1 → 0, blob →
49ad9b5c).Scanned 244 document(s)→Scanned 229 document(s)— a drop of exactly 15, the pages the legs add (docs/adr10 +docs/audits5). The gate additionallyEXIT=1with fourstale-ungated-entry — no such document in the scan set: the ledger cannot survive its legs being removed, so a future narrowing cannot be quiet.Gates — hand-derived from
.github/workflows/*.yml(objectui has no dispatch-gates derivation)Exit codes captured by redirect before any pipe.
pnpm check:doc-snippetsScanned 244 document(s): 240 covered (126 of them hold a ts/tsx block), 4 ungated·Covered blocks: 790 — 631 to compile, 159 declared fragment(s)·Semantic phase: 631 of 631 block(s) judged, 0 failed·Every covered documentation snippet compiles against the built types.pnpm exec vitest runon the three sibling pinsTest Files 3 passed (3)·Tests 167 passed (167)(160 before this PR)pnpm exec vitest run scripts/__tests__/Test Files 119 passed (119)·Tests 3544 passed (3544)pnpm check:doc-fencespnpm check:doc-typespnpm check:doc-examplespnpm type-check:scriptspnpm lint:root32 problems (0 errors, 32 warnings), all pre-existingpnpm check:control-bytesgrep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over all four touched files: no matchpnpm check:entry-guardnode scripts/check-doc-links.mjsnode scripts/check-changeset-presence.mjs --base fedfa3e4aNo source or published contract of a released package changed in this range, so no changeset is owed.— none writtennode scripts/check-governed-queue-guard.mjs --self-testnode scripts/check-governed-queue-guard.mjs --test(4 touched paths)NOT GOVERNED — 4 path(s) checked against 5 governed surface(s); none matched.node scripts/check-governed-queue-guard.mjs --test docs/adr/0001-…GOVERNED — docs/adr/** x1 — architecture decision recordsgit merge-tree --write-tree --name-only origin/main HEADorigin/mainat289d1468, no overlap with this file surfacedocs/adr/**. The control leg above proves that is a real reading and not a broken instrument. This PR is nevertheless parked as a draft with no ready flip, no auto-merge, no enqueue, no review and no reviewers: the endgame is the PM seat's stroke, and the classification change is reported rather than acted on.The judgement exercised most, on the four axes
Whether the four records get fragment markers as well as ledger entries (Zone 1 item 3's fork). A = ledger only, no edit inside either subtree (taken). B = ledger plus
FRAGMENT_MARKERon the four non-parsing blocks. C = repair the blocks.covered,一个 marker 在这里没有任何读者 —— 门禁不读它,渲染器不显示它,ADR 的读者看不见它。B 的收益经实测为零(A1 控制反证了这一点:只有把 entry 拿掉,块才被判)。真实业务场景是"这些记录里的代码块从来没有仪器看过",A 用一个带数字的 debt 条目回答了它。--build-filter逐字节不变(没有块进入 compiled tier,所以这次加宽不给 CI 增加任何构建时间)。B 要新增四处声明面而无拉动;C 要一轮文档重写。过渡也从紧:A 不设分阶段窗口,它就是终态。⇒ A,四轴一致,没有需要呈报的权衡。
验收备注
Noted, not filed — real teaching defects inside these records are proposals for the PM to open, never filed by this seat.
docs/adr/0057's resolver sketch namesisBuildAgent,resolveAgentParamandresolveDefaultAgentNameand defines none of them, and its parameters are unannotated. That is now measured (8 diagnostics) rather than suspected, but it is the record of what was agreed on 2026-07-13 — whether the shipped resolver still matches it is a question about the console, not about this ADR, and answering it belongs to whoever owns ADR-0057. Successor: the seat that next touches the console AI dock.?:on values,[...]elisions,'create' | 'edit'where a value goes). A reader copying one gets something that does not parse. Whether that matters depends on whether an ADR is meant to be copied from — a question for the ADR owners, and deliberately not answered by a gate. Successor: none today; no open PR touches these files.scripts/check-doc-fence-languages.mjs's header prose is stale in the same way objectui#8162 records for card 1 (it promises exact surface equality with this gate). Card 2 widens the divergence from one leg to three. objectui#8162 is already open and owns it; nothing new is filed. That header is outside this card's file surface and was not edited.维护者速读(草稿)
改了什么
给文档代码块的类型门禁增加了两条扫描腿:
docs/adr/**(架构决策记录)和docs/audits/**(审计快照)。这两棵树以前没有任何门禁读过 —— 不是"读了放过",是根本不在扫描范围里,连"有多少债"都答不出来。现在它们在范围内了,每一页带 TypeScript 代码块的记录都以带实测数字的条目登记在账上:四份记录、五个代码块、29 个诊断(21 个连语法都过不去,8 个是语义)。没有改动这两棵树里的任何一个字节。为什么改
triage 的裁定是关于文档性质的,不是关于成本:ADR 记的是"某一天决定了什么",审计记的是"某一天测到了什么"。把里面的代码块改到能编译,就是在改写历史记录。所以这次交付物就是这本账本身 —— 一棵没人点名的树是完全隐形的,一份带名字带数字的欠债是可管理的,后者严格地好。什么时候还这笔债,留给记录的所有者按记录逐份决定,不由一个门禁替他们定。
风险与代价(含回滚)
风险很低。CI 成本零增加:这两棵树没有一个块进入编译层,所以门禁要构建的包一个字节都没变。对其他两个文档门禁(
check:doc-fences、check:doc-types)没有任何影响,已作为对照跑绿。三条控制实验证明了这套机制真的在工作:把某条账目撤掉,门禁立刻按文件:行点名报错;把腿撤掉,扫描计数从 244 掉回 229、并且四条账目同时变红(所以以后没人能悄悄把它撤回去)。回滚就是 revert 这一个 commit,四个文件,不牵涉任何已发布的包,也不需要 changeset。席位意见
(留空,待席位定稿)
你要做的
看一眼上面表格里那 29 个诊断的分布,确认「这两棵历史记录里的代码块暂时只登记、不修」是你要的处置 —— 如果是,这个 PR 就只等一次人工合并。
Generated by Claude Code