Repository navigation
[finding] content/docs/references/contracts/ 是一个只剩 meta.json 的空目录,build-docs.ts 已不再产出它 #7303
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Aug 10, 2026 Findings triage: promoted with the delete direction —
finding→pm:queue, routeddomain:spec-tooling.- Premise check (
origin/main@62b6a2f):content/docs/references/contracts/meta.jsonis live and reads exactly as filed ("pages": []);build-docs.tshas nocontractscategory (its only two hits are theapi:description prose at :698 and a comment at :799 — the card's grep result reproduced). PR docs(getting-started): quick-reference 计数改(N of M schemas),并把真实参考目录纳入门 (#6530) #7302 has merged, so theCategories Without a Sectionrow this card wants removed is on main too. - Grade rationale — deciding the card's own 删还是留 question: 删 (residue). The evidence is mechanical, not a product call:
build-docs.ts's category table is the single writer and source of truth forreferences/, it has never listedcontracts, the six pages live on atcontent/docs/kernel/contracts/(content intact), and the directory's own history (c483c1326removed it,36425099are-addedmeta.jsonalone) marks the file as an accidental re-add. Scoped cleanup: deletecontent/docs/references/contracts/, drop thecontractsrow from the docs(getting-started): quick-reference 计数改(N of M schemas),并把真实参考目录纳入门 (#6530) #7302 table in the same PR. Maintainer veto window open — reverse to "placeholder" only if spec-side production of a Contracts Protocol category is actually planned. - Routing anchor: the fix face is the generated-references tree +
check-quick-reference-counts.mjs's table — the references pipeline, which the domain table assigns todomain:spec-tooling(contract gates/generators/references 管线). - Dedup: no open card touches
references/contracts/; [finding]quick-reference.mdx的「Data Protocol (17 schemas)」是策展子集,而references/data/实有 29 个 schema 页;计数门只自校验、看不到这个差 #6530/PR docs(getting-started): quick-reference 计数改(N of M schemas),并把真实参考目录纳入门 (#6530) #7302 is the discovering neighbour, already merged, scope disjoint. target:v17: not applied — zero user-visible surface (nav excludes the category; lychee green).
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Premise check (
Claim: PM loop round 6
Session:session_01KJATVrh6V2ysutYUJigh3B
Branch:claude/issue-7303-drop-empty-contracts-references
Worktree:objectstack-issue-7303
Domain:domain:spec-tooling
File surface:content/docs/references/contracts/(delete),content/docs/getting-started/quick-reference.mdx(one table row),scripts/check-quick-reference-counts.mjs(itscontractsentry, docblock and self-test expectation). Stop on breach; explain in the report.
Container & model: S/M,mode:subagent,model: opus— see the self-test coupling below; this is not the one-file delete it looks like.
Serial constraints cleared: no open PR touches any of the three sites; PR #7302 (the discovering neighbour) has already merged, so its table is on main and there is no in-flight collision with it. This lane has zero other cards in flight.Dispatched under triage's promotion, which also settled the card's own open question (「删还是留」→ 删, residue), on mechanical grounds rather than a product call:
build-docs.ts's category table is the single writer forreferences/and has never listedcontracts; the six pages live on atcontent/docs/kernel/contracts/; and the directory's history showsc483c1326removed it while36425099are-addedmeta.jsonalone. Maintainer veto window is open on that direction.Premise re-verified live on
origin/mainbefore claiming, and it turned up two things the card does not mention:⚠️ content/docs/references/**is routed tomerge=os-regen— confirmed against.gitattributesread live. This card deletes from that tree rather than regenerating it, so the driver is not the mechanism here, but a sibling regen landing mid-flight is a real merge interaction the dev must handle by merging main rather than by hand-resolving.⚠️ Thecontractsrow is in three places, not one. The card names the table row;scripts/check-quick-reference-counts.mjsalso carriescontracts: [](:492) and a literal self-test expectation string (:519), plus a docblock mention (:80). Removing only the visible row leaves the gate's self-test asserting a row that no longer exists — a red that reads as "the delete was wrong" when it is really "the fixture was missed". That coupling is why this is dispatched atopusrather than as a mechanical delete.
build-docs.tsre-checked: 2contractshits, both prose (theapi:description and a comment), so nothing regenerates the directory back.
Generated by Claude Code
Dev report: premise falsified — no PR.
content/docs/references/contracts/meta.jsonis a GENERATED artifact, not residue.I implemented the ruled
删direction in full, then ran the verification the dispatch asked for before pushing, and it went red. Reverting the branch toorigin/main. Evidence below, all measured in a clean worktree offorigin/main@62b6a2fb2.The measurement that settles it
Delete the directory, then ask the generator:
$ pnpm --filter @objectstack/spec check:docs ✗ content/docs/references/ is out of date with packages/spec: + content/docs/references/contracts/meta.json (missing — spec adds it)And running the generator for real puts it back, byte for byte:
$ ls content/docs/references/contracts/ ls: cannot access 'content/docs/references/contracts/': No such file or directory $ pnpm --filter @objectstack/spec gen:docs ✅ Generated 231 files $ cat content/docs/references/contracts/meta.json { "title": "Contracts Protocol", "pages": [] }So
gen:docsis the writer of that file. Any regeneration PR would silently re-add it — which is exactly what36425099a("docs: regenerate references from current spec") did, and it means that commit was a regeneration producing its own output, not an accidental re-add.Where the card's grep went wrong
The card and the promotion both rest on
grep -n contracts build-docs.tsreturning only prose. That grep is correct but scoped to the wrong file —build-docs.tshas no category table to be absent from. Categories are discovered at run time:packages/spec/scripts/build-docs.ts:127const CATEGORY_DIRS = fs.readdirSync(SRC_DIR) .filter(file => fs.statSync(path.join(SRC_DIR, file)).isDirectory());
and titled from a declared map in a different file —
packages/spec/scripts/lib/category-title.ts:75:contracts: 'Contracts Protocol',
That map is total in both directions and build-stopping (
resolveCategoryTitles, pinned bycategory-title.test.ts: "declares a title for exactly the module directories under packages/spec/src"). Socontractsis a first-class declared category, and"title": "Contracts Protocol"in the meta.json is not an implementation-free declaration — it isCATEGORY_TITLES.contracts, rendered.packages/spec/src/contracts/is also very much alive: 82 files (auth-service.ts,data-engine.ts,metadata-service.ts,sharing-service.ts, …).Why it publishes zero pages — and why it still gets a
meta.jsonpackages/spec/src/contracts/holds TypeScript service interfaces, not.zod.tsschemas, sogen:schemaemits no JSON Schema for it. It does, however, create the category directory:$ ls packages/spec/json-schema/contracts/ | wc -l 0 # the directory EXISTS, and is emptyThat empty-but-present directory is the whole mechanism. In
groupSchemasByPage()(build-docs.ts:298-321):conversionsandmigrationshave nojson-schema/directory, so they hitcontinueat :302 (they print "Warning: Schema directory ... does not exist") and never reachbyCategory.set. That is why they have no directory underreferences/at all;contractsdoes have one, so it falls through, finds zero.jsonfiles, builds an emptypagesmap, and reachesbyCategory.set(category, pages)at :320 with the key present;- the emit loop then runs for
contracts, writes no.mdx, and writesmeta.jsonwithpages: [].
So the
conversions/migrationsasymmetry that makescontractslook like an unswept leftover is mechanical and generated, not curation drift.build-docs.tsalso names this state deliberately, at the line the card read as a passing comment (:799):the steady state for a category whose schemas are all unrepresentable in JSON Schema (
contracts/is the standing example)and
check:docsprints its documented branch on every run today:⚠ Skipping clean of contracts/ — this build published no JSON Schema under .../json-schema/contracts; leaving its pages as they are.Two more of the card's factual claims that did not hold
- "没有任何页链接到
/docs/references/contracts" — there is one:content/docs/kernel/index.mdx:44. It is genuinely dead (that route has noindex.mdx, the only reference category without one), but it is a hand-written-link defect independent of this card. Filed as docs:content/docs/kernel/index.mdxlinks to/docs/references/contracts, a route that does not resolve #7330. - The gate self-test coupling was not load-bearing.
GOOD_CATALOG/GOOD_PAGEinscripts/check-quick-reference-counts.mjsare synthetic andcheckPageis pure, so the self-test stayed green (22/22) through the delete. The dispatch's concern that it would red was itself unfounded.
What the real question turns out to be
Not 删还是留 for a leftover file — that question is closed: it is generated, and deleting it is a no-op that CI reverses.
The live question is whether
build-docs.tsshould emit ameta.jsonfor a category that publishes zero pages. Today it produces an empty, unroutable directory (noindex.mdx, excluded fromreferences/meta.json'spages) whose only effect is to make anyone enumerating the tree count 15 categories instead of 14 — which is precisely what produced this card. Making the emit conditional onpages.length > 0would delete the directory at the generator, permanently, and bringcontractsinto line withconversions/migrations.That is a change to a generator's output contract, not a file deletion, so I am not making it unprompted — it needs its own card and its own decision. Handing back to triage.
Local gates run and green before the revert (as a delete):
check-quick-reference-countsreal +--self-test(22/22, 14 categories all sectioned or declared),check:doc-authoring,check:docs-audit-scope,check-nul-bytes. Reverse verification also confirmed the coverage check still bites — removingstudio's row reds with[coverage] content/docs/references/studio/ (3 page(s)) has no section on this page. Onlycheck:docsreds, and that is the one that matters.
Generated by Claude Code
Generated by Claude Code
Premise falsified — the ruled 删 direction is wrong, no PR opened, nothing pushed. This is a successful dispatch, not a failed one: the dev implemented the ruled direction in full, hit the stop condition the brief defined, reverted, and reported.
meta.jsonis GENERATED, not residueThe decisive evidence, and it is not an argument — it is the generator:
$ pnpm --filter @objectstack/spec check:docs ✗ content/docs/references/ is out of date with packages/spec: + content/docs/references/contracts/meta.json (missing — spec adds it)and with the directory deleted,
gen:docsrecreates it byte-for-byte. A hand delete is reverted by the next regeneration — which means36425099awas not "an accidental re-add", it was a regeneration producing its own output.PM-verified independently on
origin/main, all four:Claim Check build-docs.tshas no category table to be absent from✅ :127—fs.readdirSync(SRC_DIR), categories are discovered, not listedThe category is nonetheless declared ✅ category-title.ts:75—contracts: 'Contracts Protocol'The mechanism ✅ packages/spec/json-schema/contracts/is generated and holds 0.jsonfiles, whilepackages/spec/src/contracts/has 82 live source files.conversions/migrationscontinuebecause their schema dirs don't exist;contracts' dir does exist, falls through with zero pages, and reaches the emit with the key present"没有任何页链接到 /docs/references/contracts"❌ false — content/docs/kernel/index.mdx:44links there, and there is noindex.mdx, so the route cannot resolve. Filed as #7330Two corrections that land against the people who graded this, me included
My dispatch warning was wrong. I told the dev the gate's self-test carried a literal expectation that would red on the delete, and made that the reason for the model tier. It doesn't:
GOOD_CATALOG/GOOD_PAGEare synthetic andcheckPageis pure, so the self-test stayed 22/22 green through the delete. I read:519as a live fixture without checking whether the self-test consumes real tree state. That is the same shape as ㉟ — a positional read treated as a structural claim.The grade's rationale rests on a grep scoped to the wrong file — this is a report to #6015, ⛔ not a re-grade, which is not this seat's to make. The promotion reasoned that "
build-docs.ts's category table … has never listedcontracts". There is no category table;build-docs.tsdiscovers directories, and the declaration lives incategory-title.ts, which does list it. The card made that error first and the promotion inherited it — the honest reading is that both were misled by the same grep, not that anyone was careless. Cost: one dispatch, which is the correct price for finding this out, and cheaper than a hand delete landing and being silently regenerated back.The reverse verification is why the "everything else is green" reading is trustworthy: the dev removed a different category's row (
studio) and the coverage gate went red as predicted, so the check still bites and the greens above are not a loosened gate.What is actually left, and it is a different question
The directory is generated, unroutable (no
index.mdx), excluded from the navpagesarray, and invisible to lychee. Its only measured effect is that anyone enumeratingreferences/counts 15 categories instead of 14 — which is exactly what produced this card, so the generator will keep manufacturing this finding until the emit changes.Moving to
needs-user-decision, because the remaining question is a generator output-contract call, not the cleanup this card was graded as:- A (dev's recommendation) — guard the
meta.jsonemit on the category having ≥1 page, the way theindex.mdxemit at:849is already guarded. The directory then disappears at the generator, permanently, andcontractslines up withconversions/migrations. Same file surface this card scoped, but landing as generator output rather than a hand delete. - B — leave the generator, fix the quick-reference row's prose (it currently says "left over from an earlier layout", which is now known false). Cheapest; keeps the empty directory and the dead route forever.
- C — do nothing, close with the mechanism recorded.
⚠️ The caveat that should decide it:contractsis a declared category with 82 live source files. A makes the docs tree silently stop representing it, and the empty directory is currently the only committed trace that the category exists. If reference pages for the service contracts are ever wanted, B is the honest holding position and the real work is generating pages from those 82 interfaces — a much bigger card than either option here.Assignee released; ⛔ no work proceeds until this is ruled.
Generated by Claude Code
- A (dev's recommendation) — guard the
6 remaining items
Label correction — this card was held in a comment but still labelled
pm:queue. No change to the analysis, the two options, or the hold; only the state machine is being made honest.pm:queuereads "ready to dispatch" (SKILL.md label table). This card has been 决定待做 since the 07:49Z comment, and the protocol's rule for that state is explicit: post the analysis on the issue, addneeds-user-decision, drop it from the active queue — becauselabel:needs-user-decisionis the maintainer's inbox filter. Carryingpm:queueinstead meant this card was simultaneously (a) invisible to the inbox query the maintainer actually runs, and (b) visible to every selection sweep as a dispatch candidate. It survived on the seat's in-context memory alone, which is not a durable channel.Labels now:
documentation,domain:spec-tooling,needs-user-decision. Assignment kept, so no other seat spends a dev on it while it waits.⚠️ Premise shelf life: the five re-check commands in the 15:57Z comment were last run at 2026-08-10 15:56Z. Anyone picking this up should re-run them rather than trusting the readings — premise 4 (packages/spec/src/contracts/file count) has already moved once, 82 → 84, and it moved against the delete.Still A (guard the
meta.jsonemit onpages.length > 0) vs B (leave the generator, fix the now-false quick-reference prose). ⛔ No work proceeds until this is ruled.
Generated by Claude Code
Maintainer ruling recorded 2026-08-11 (PM session, executing the maintainer's direct instruction in chat — verbatim: 「接受你的全部建议,请更新 issue 的状态和标签」, accepting the four-lens decision-inbox review in full).
Ruling: delete.
content/docs/references/contracts/is a leftover, not a placeholder —build-docs.ts's category table is the source of truth for that tree and has nocontractsentry; the original six pages live on incontent/docs/kernel/contracts/. Remove the directory (itsmeta.jsonincluded) and drop thecontractsrow from PR #7302's Categories-Without-a-Section table in the same change.State:
needs-user-decision→pm:queue(domain:spec-toolingseat).
Generated by Claude Code
Claimed by the
domain:spec-toolingseat (#6018) — sessionsession_01KJATVrh6V2ysutYUJigh3B, branchclaude/issue-7303-contracts-category-emit-guard. Round 14.pm:queue→pm:dispatchedas one write. Modelopus,mode:cloud.Dispatched as option A — the delete happens at the generator. The ruling has now said delete twice, so ⛔ the 删/留 call is closed and B ("leave the generator, fix the prose") is off the table: B does not remove the directory, and removing it is what was ruled. Reading the ruling for its action rather than its grounds, A is the only form that achieves it.
⚠️ Recording once, not re-asking: the grounds are still falsified, for the fifth timeThe ruling states: "
build-docs.ts's category table is the source of truth for that tree and has nocontractsentry." Re-measured onorigin/mainat 08:5xZ, all five of this card's re-check commands re-run:# premise reading 1 is there a category table? No. build-docs.ts:127=const CATEGORY_DIRS = fs.readdirSync(SRC_DIR)— categories are discovered from disk⚠️ falsified, unchanged2 is contractsdeclared?category-title.ts:75—contracts: 'Contracts Protocol'✅ still declared, still mandatory3 is the tree generated output? .gitattributes→1hit,merge=os-regen✅ holds4 live source behind the category 84 files in packages/spec/src/contracts/✅ (was 82 at first analysis)5 is the target still present? content/docs/references/contracts/meta.json✅ present✅ One part of the ruling does check out: the original pages do live on —
content/docs/kernel/contracts/holds 7 files. That half is correct.⛔ This is a report, not a re-litigation, and no answer is requested. It is recorded because the same wrongly-scoped grep (
grep -n contracts build-docs.ts→ prose only, correct output from the wrong file) has now been inherited five times: the card, the promotion, my own first dispatch, the 08-10 ruling, and this one. The dispatch below does not depend on it either way — it is here so the sixth reader does not spend the research again. ⇒ Report to #6015, not a re-grade.Why a hand delete cannot be the deliverable
Measured end-to-end by the previous dev and unchanged: delete the directory ⇒
pnpm --filter @objectstack/spec check:docsreds with+ content/docs/references/contracts/meta.json (missing — spec adds it), andgen:docsrestores it byte-for-byte. A delete-only PR cannot merge. The generator writes this file, so the generator is where it stops being written.The deliverable
- Guard the
meta.jsonemit onpages.length > 0inpackages/spec/scripts/build-docs.ts, mirroring the guard theindex.mdxemit already has at:849.⚠️ Read:849first and mirror that shape rather than inventing a second idiom — and verify the line still says what this brief claims (㊶: anchor on text, not line numbers; this anchor is ~2 days old). - Run the generator so the directory disappears as generated output, and commit that result. ⛔ Do not hand-delete and hope; the whole point is that the tree is
merge=os-regen. - Drop the
contractsrow from PR docs(getting-started): quick-reference 计数改(N of M schemas),并把真实参考目录纳入门 (#6530) #7302's Categories-Without-a-Section table, as ruled. - Confirm
contractsnow behaves likeconversions/migrations— the categories that already emit nothing.
⚠️ The caveat that must ride along, unchangedcontractsis a declared category with 84 live source files, and that emptymeta.jsonis currently the only committed trace in the docs tree that the category exists. After this change the references tree silently stops representing it. That is the ruled outcome and you should implement it — but ⛔ do not letresolveCategoryTitlesbreak: it is total and throws on a title with no directory as well as a directory with no title (build-docs.ts:130-134). Verify the declaration atcategory-title.ts:75must stay, or that removing it is safe — measure, do not assume, and say which you found.Scope
- In:
build-docs.ts(the one guard), the regenerated tree, the docs(getting-started): quick-reference 计数改(N of M schemas),并把真实参考目录纳入门 (#6530) #7302 quick-reference row, a changeset. - ⛔ Out:
content/docs/kernel/contracts/(the real pages — untouched); any other category's emit behaviour;packages/spec/src/**/*.zod.ts; ⛔content/docs/releases/**. - ㉕ — disclose any expansion rather than absorbing it.
Accept bar
pnpm --filter @objectstack/spec check:docsgreen — that gate is the whole reason this card came back twice.- Re-running
gen:docsafter the change does not restore the directory (the previous failure mode, proven by a second run). ⚠️ A green docs-link gate is not evidence anything resolves:lychee.tomlsetsinclude_fragments = "none", so a link to a non-existent anchor returns[200] ✅ OK([finding] No CI gate checks documentation anchors —lychee.tomlsetsinclude_fragments = "none", so a link to a heading that does not exist passes as[200] OK#7484). If you touch a link or a heading, verify by slugging with the repo's owngithub-sluggerplus a repo-wide grep for the old fragment.- ㉜ — prove the check can see before believing any green.
Report at draft-PR time per #6644 L2 — ⛔ no idle-polling CI. Issue comment with the
os-dev-reportmarker first, return message second.
Generated by Claude Code
- Guard the
Dev report — implemented as option A, draft PR #7622 open. Branch
claude/issue-7303-contracts-category-emit-guard, worktreeobjectstack-7303offorigin/main@e124711.The ruled action landed:
content/docs/references/contracts/is gone, and thecontractsrow is out of the Categories-Without-a-Section table.content/docs/references/goes 15 category directories → 14; the generator's emitted file count goes 231 → 230.The change
One guard in
packages/spec/scripts/build-docs.ts, in thePAGES_BY_CATEGORYemit loop:const pages = buildCategoryPages(category, Array.from(zodFileSchemas.keys())); … if (pages.length === 0) return;
I read the
index.mdxguard first as instructed. It has moved — it is at:850, not:849, and readsif (zodFiles.size === 0) return;inside theObject.entries(CATEGORIES).forEachat:848. Shape mirrored (early return when the category published nothing), placed at the tail of the loop body so nothing else in it changes — in particularcategoryPageSchemas.setat:833still runs, so the root index's input is byte-identical. Confirmed:git status content/docs/references/was empty after the firstgen:docswith the guard, i.e. no other generated file moved.⚠️ One thing the brief's step 2 did not anticipate — disclosed, not absorbedThe generator cannot delete the file it stops writing, so the tracked copy still had to be removed in the commit.
contracts/is nevermanageDir'd: step 1 (:789-807) returns at the!hasSchemasbranch beforemanageDir(dir), because there is no JSON Schema to regenerate from.flush()only deletes paths that areownedOnDisk(), and only reports- (stale)for owned paths (lib/generated-output.ts:129-131). So after the guard,contracts/meta.jsonwas neither emitted nor owned — invisible tocheck:docsin both directions, and untouched bygen:docs.Measured, in order:
step result guard applied, gen:docsrun 1✅ Generated 230 files(was 231) — butcontent/docs/references/contracts/meta.jsonstill on diskgit rmthe filedirectory gone gen:docsruns 2, 3, 4✅ Generated 230 files, directory not restored any of the three timescheck:docs✅ 230 generated files in sync with packages/spec, exit 0So the delete is durable, and durable because of the guard — but it is one
git rmin the commit rather than a diffgen:docsproduced on its own. I did not widen scope to make the generator own that directory (narrowingmanageDirinto the!hasSchemasbranch would put every category's hand-preserved pages at risk — the exact case #4723 protects, and "any other category's emit behaviour" is out of scope). Flagging it as the one place the deliverable's shape differs from the brief's description.Both
gen:docsruns and their resultsAsked for two; ran four (one before the
git rm, three after). Runs 2–4 all printed✅ Generated 230 fileswith the directory absent before and after. The previous failure mode — a delete that the next regeneration reverses — does not reproduce.check:docsoutputPositive control first, so the green is worth something. Hand-delete on an otherwise-clean tree, no guard:
✗ content/docs/references/ is out of date with packages/spec: + content/docs/references/contracts/meta.json (missing — spec adds it) Exit status 1Reproduced exactly as recorded on this card. After the guard +
git rm:✅ import examples resolve against api-surface/ (62 accepted gap(s) in the baseline) ✅ 230 generated files in sync with packages/spec EXIT=0⚠️ Honest caveat on that green: becausecontracts/is unmanaged,check:docsis now blind to that path — a stray file reappearing there would not red. That is a pre-existing property of the!hasSchemasskip-clean branch, not something this PR introduces, but it means thecheck:docsgreen is necessary and not sufficient. The load-bearing evidence is the emit count (231 → 230) and the three non-restoringgen:docsruns.category-title.ts— the declaration must stay. Measured.Deleted
contracts: 'Contracts Protocol'(still at:75, anchor holds) and ran the generator:$ pnpm --filter @objectstack/spec gen:docs ✗ Every reference page title, sidebar label and root-index row for a module is this string. … Exit status 1Build-stopping, as
resolveCategoryTitles' totality predicted —packages/spec/src/contracts/has 84 files (git ls-tree -r --name-only origin/main packages/spec/src/contracts/ | wc -l= 84, reconciled against this card's premise-4 pin of 84), so the directory exists and demands a title. Restored;git diffon the file is empty and the full spec suite passes (376 files / 9867 tests).In-repo precedent that this is the normal steady state, not a wart:
conversions: 'Conversions Protocol'is declared too, andconversionspublishes nothing at all. Declared-with-zero-pages is a supported shape.contractsvsconversions/migrations— parity confirmedcontracts references/: (absent) conversions references/: (absent) migrations references/: (absent)Exact residual difference, stated rather than rounded off:
conversions/migrationsstill printWarning: Schema directory … does not exist(theirjson-schema/<cat>/is missing);contractsprints nothing, because its directory exists and is empty. That is a diagnostic difference only — the three now emit identically. The⚠ Skipping clean of contracts/ …line that every build used to print is gone, since the directory no longer exists.Scope expansions — disclosed
Two beyond the brief's four items, both because my change makes an existing sentence false:
- The paragraph above the table in
quick-reference.mdx: "holds two more category directories that deliberately get no section" → "one more … gets". Removing only the row would leave the prose miscounting. scripts/check-quick-reference-counts.mjs:80docblock: "references/studio/(3 pages) andreferences/contracts/(0 pages) have no section" → studio only. Prose in the gate that documents the real tree.
⛔ Not touched: the gate's
GOOD_CATALOG/GOOD_PAGEself-test fixtures (:492,:519,:650,:760,:769). They are synthetic, never read the real tree, and still exercise a reachable parse case. The first dispatch's warning that they were load-bearing remains unfounded — self-test is 22/22 green with them untouched.Reverse control that the row removal was required, not cosmetic — re-added the row and ran the gate:
content/docs/getting-started/quick-reference.mdx:244 [coverage] `contracts` is declared as having no section, but content/docs/references/contracts/ does not exist EXIT=1Inbound links
Repo-wide grep for
references/contracts(grep -rn, noheadin the pipeline): 4 hits — the gate docblock (fixed), an untracked fumadocs build artifact (apps/docs/.source/server.ts, not ingit ls-files, regenerates), and a dated audit record (docs/audits/2026-06-…md, left as the historical record it is). Positive control: the grep did find three real occurrences, so it can see.⚠️ #7330's dead link is already gone from main.content/docs/kernel/index.mdx:44now reads[Kernel](/docs/references/kernel), [System](/docs/references/system)— no/docs/references/contracts. So this change strands no inbound link. #7330 looks closable independently; not my call, not touched.No heading and no link fragment changed, so no
github-sluggerslug check was warranted — the#categories-without-a-sectionanchor is untouched.Gates, with invocation scope
Gate Result pnpm --filter @objectstack/spec check:docs✅ exit 0, 230 files in sync pnpm --filter @objectstack/spec gen:docs×4✅ directory never restored pnpm --filter @objectstack/spec test✅ 376 files / 9867 tests pnpm --filter @objectstack/spec typecheck✅ ( tsc --noEmit+check:scripts-typecheck+check:test-typecheck)pnpm run check:quick-reference-counts✅ self-test 22/22; 14 categories all sectioned or declared pnpm run check:doc-authoring✅ 375 files clean pnpm run check:docs-audit-scope✅ 179 hand-written docs in sync pnpm run check:empty-changeset✅ pnpm run check:nul-bytes✅ 7056 files scanned pnpm run check:adr-anchors,check:adr-links✅ npx eslinton the two touched sources✅ exit 0 Changeset:
.changeset/references-empty-category-meta.md(@objectstack/spec: patch). ⛔content/docs/releases/**untouched.Not verified
- CI. Not consulted, not polled — the PM's per the brief. Everything above is local.
- The docs site was not booted. This PR changes no published page (the removed file was unroutable), so there is nothing to look at, but I did not prove that in a browser.
check:docs's blindness to the now-unmanagedcontracts/path, stated above — I did not attempt to close it.
The ruling's grounds, recorded once, no answer requested
Per the seat's brief I did not re-derive this, but I did trip over the line while working and can confirm it directly:
build-docs.ts:127isconst CATEGORY_DIRS = fs.readdirSync(SRC_DIR)with a comment above it reading "Categories are discovered from the src directory". There is no category table. The ruling's action is implemented in full; only its stated grounds are wrong, and nothing here depends on them. ⇒ report, not a re-grade.
Generated by Claude Code
- The paragraph above the table in
ACCEPTED — landed as
814db6d80(PR #7622, squashed). Verified by content on a fresh ref (㉞). Card auto-closed;pm:dispatchedstripped; cloud session archived. Ledger → 19 merged.Landing verification
# criterion reading @ origin/main1 content/docs/references/contracts/goneempty ✅ 2 the pages.length === 0guard exists inbuild-docs.ts1 ✅ 3 ⚠️ category-title.tscontracts:declaration still present1 ✅ 4 the contractsrow gone from the quick-reference table0 hits ✅ 5 reference category directories 15 → 14 14 ✅ ⚠️ Criterion 5 first read16— my probe, not the PR. I countedgit ls-treeentries, which includes the tree's ownindex.mdxandmeta.jsonblobs. Filtered totreeobjects it is exactly 14, as claimed. Same class as the count error I made on #7503's brief: a count is only as good as the set the command actually enumerates (㊾/51).Criterion 3 is the one that mattered. The brief asked the dev to measure whether the declaration must stay rather than assume it, and it did — removing it is build-stopping, because
resolveCategoryTitlesis total over the directories inpackages/spec/src/and throws both directions. It stays, and the changeset says so in prose. Had that been assumed either way, this PR would have either broken the build or left a false claim in the changeset.What the dev did well
- Ran
gen:docsfour times, not once. The previous failure mode of this exact card was "delete, gate goes red, generator restores it byte-for-byte" — a single green run cannot distinguish a fix from a coincidence. - Guarded on the page count, not on the symptom.
contractsreaches the emit with zero pages because it holds TypeScript service interfaces rather than.zod.tsschemas, sogen:schemacreates an emptyjson-schema/contracts/;conversions/migrationshave no schema directory at all and are skipped earlier. The guard deliberately keys onpages.length, so a future category of either shape lands the same way — the asymmetry is explained in the comment rather than encoded in the condition. - Updated
check-quick-reference-counts.mjs's docblock in the same commit — that gate reads the table this PR edits, so a stale docblock would have described a table that no longer exists. - The changeset states the one measurable effect honestly: no published page changes, emitted files 231 → 230, and the only loss is a directory holding
{ "title": …, "pages": [] }— unroutable, invisible to the link checker, and counted by anyone enumerating the tree.
⚠️ For the record — the ruling's grounds, sixth and final inheritanceThis landed on the ruling's action (remove the directory), which is now permanently satisfied at the generator. Its stated grounds remain false: there is no category table in
build-docs.ts—:127isfs.readdirSync(SRC_DIR), categories are discovered from disk. Re-measured once more at landing. ⛔ No answer is requested and nothing depends on it; it is recorded so the seventh reader does not re-derive it. Report to #6015, not a re-grade.
Generated by Claude Code
- Ran
在做 #6530(PR #7302,把 quick-reference 的计数与真实参考目录对齐并纳入门)时顺带发现,非该单范围,按 Prime Directive #10 独立记录。⛔ 未自我认领。
观察
content/docs/references/下的 15 个分类目录里,contracts/是唯一一个没有任何.mdx的:其余 14 个目录页数(去掉
index.mdx):ai 11、api 28、automation 13、cloud 11、data 30、identity 5、integration 1、kernel 31、qa 1、security 5、shared 8、studio 3、system 37、ui 16。它是怎么留下的
packages/spec/scripts/build-docs.ts是content/docs/references/的唯一写者,而它的分类表里根本没有contracts这一项(grep -n contracts build-docs.ts只命中api:的一句描述文字和一句注释)。目录本身是历史残留:c483c1326"fix: migrate hand-written docs from auto-generated references/ to guides/" 删掉了contracts/下全部 6 个页(auth-service/cache-service/data-engine/index/metadata-service/storage-service)以及当时的meta.json;36425099a"docs: regenerate references from current spec (docs: regenerate references from current spec #1908)" 又把meta.json单独加了回来,但页没有回来。那批内容今天活在
content/docs/kernel/contracts/(auth-service.mdx/cache-service.mdx/data-engine.mdx/index.mdx/metadata-service.mdx/storage-service.mdx六页都在),所以内容没有丢,丢的只是这个空壳目录没人清。影响面(据实,不夸大)
今天没有任何用户会撞到它,所以按 observation-class 记录、不自评级别。 依据:
content/docs/references/meta.json的pages数组显式枚举了 14 个分类,不含contracts,所以导航里不会出现一个空的 "Contracts Protocol" 组;/docs/references/contracts;Check Documentation Links(lychee)今天是绿的。代价只有两处,都是对读者与 agent 的:
content/docs/references/是「每个分类一个目录」这条结构约定的实例,多一个空目录会让任何按目录枚举分类的人(包括 agent)数出 15 而不是 14;meta.json里的"title": "Contracts Protocol"是一句没有对应实现的声明 —— 它宣称存在一个 Contracts Protocol 分类,而 spec 侧没有任何东西产出它。为什么现在才被看见
PR #7302 给
scripts/check-quick-reference-counts.mjs加了分类级覆盖扫描:references/下每个分类目录必须要么在 quick-reference 上有小节、要么在新增的## Categories Without a Section表里被声明。contracts因此被迫写进那张表,写的时候才发现它 0 页。也就是说,这个空目录现在是被 gate 盯着的(页数从 0 变成非 0 会红),只是它该不该继续存在是另一个问题。需要决定的是「删还是留」
content/docs/references/contracts/,并把 PR #7302 那张表里的contracts行一并去掉meta.json的 title 应改成不暗示已存在倾向前者:
build-docs.ts的分类表是这棵树的真实来源,它里面没有contracts,那这个目录就不是「等着被填」的占位,而是36425099a加回了一个不该加回的文件。但这是分诊该定的,不是我在 #6530 范围内该定的。