fix(spec): stop gen:docs warning about the one schema directory declared absent - #16509
Merged
huangyiirene merged 1 commit intoSep 7, 2026
Merged
Conversation
…red absent `gen:docs` printed three `Warning: Schema directory ... does not exist` lines on every run. The condition is real: `build-docs.ts` walks the 18 module directories under `packages/spec/src/` while `gen:schema` creates one `json-schema/<category>/` per entry of the hard-coded 15-entry `Protocol` map in `build-schemas.ts`, so `conversions`, `meta-spelling` and `migrations` have no directory at all. Only `meta-spelling` is DECLARED to have no schema closure, so only its warning is silenced, through a hand-signed map that carries the citation and expires: `schemaClosureExemptionCoverage` fails the build if a declared category grows a `json-schema/` directory or loses its module directory. `conversions` and `migrations` carry no equivalent declaration and keep warning. The exemption is deliberately NOT read off the `Protocol` map: a category dropped from that map by accident would exempt itself from the very check that would have caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 7, 2026
Closed
huangyiirene
marked this pull request as ready for review
September 7, 2026 06:57
huangyiirene
enabled auto-merge
September 7, 2026 06:57
huangyiirene
deleted the
claude/issue-15870-gen-docs-absent-schema-dir-warning
branch
September 7, 2026 07:30
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 #15870
pnpm --filter @objectstack/spec gen:docsprinted threeWarning: Schema directory ... does not existlines on every run. This silencesone of the three — the only one the tree declares — and deliberately leaves
the other two firing.
The first step, verbatim (the card's explanation was on trial, not its observation)
The card argued the condition is "permanent and pre-existing" from all three
directories hold 0 tracked files. Triage corrected that:
packages/spec/json-schema/is entirely gitignored (
.gitignore:63) because it is a build artifact, so "0 trackedfiles" is true of every directory under it and distinguishes nothing. The mandated
first step was to measure the built tree instead. All three readings, verbatim:
Positive control that
lswas reading a populated tree and not an empty one: the samebuild leaves 15 other category directories in place holding 1576 files. All three
absent after a build, so the card's conclusion stands.
The mechanism, which nothing had written down
build-docs.tswalks the 18 module directories underpackages/spec/src/(
CATEGORY_TITLESholds that list total in both directions).gen:schemacreates onejson-schema/CATEGORY/per entry of the hard-coded 15-entryProtocolnamespace mapin
build-schemas.ts. 18 minus 15 is exactly the three that warn.contractsis the counter-example worth stating, because it is the one people reach for:it is on the
Protocolmap, so it getsjson-schema/contracts/— empty, 0 files,because its service interfaces are plain TypeScript rather than Zod. Present-and-empty and
absent-entirely are different states and only the second warns.
Per-directory declaration search — and its lit control
Searched for a statement that the category ships no schema closure by design, over the
whole repo excluding
dist/andCHANGELOG.md:Control that the searches could return a hit: the second returns ~100 lines, the first
returns 8, and both include
packages/spec/scripts/build-meta-url-spelling.ts:20. A zerobelow is a reading, not a broken probe.
meta-spellingscripts/build-meta-url-spelling.ts:20— "/meta-spellingentry ships vocabulary with no schema closure." Corroborated three more times:src/meta-spelling/manifest-collection-spelling.ts:21("no schema closure on the vocabulary path");scripts/lib/category-title.ts:81titles it "Meta-Spelling Vocabulary", not "Protocol", because the entry "folds without the schema machinery every Protocol category links"; andbrowser-reachable-entries.jsondeclares./meta-spellingbrowser-reachable under the standing schema-free principle.conversionsbuild-docs.tssection 2, which records it as an asymmetry that comment's guard deliberately does not act on.CATEGORY_TITLEScalls it "Conversions Protocol" — the same word every category with a schema closure carries.migrationssrc/migrations/spec-changes.tsexports five real Zod schemas (SpecChangesSchema,SpecConvertedSchema,SpecMigratedSchema,SpecSurfaceAddSchema,SpecSurfaceRemoveSchema), so "no schema closure" is not even factually true of it. What is true is that it is not on theProtocolmap — a fact about a generator's input list, not a declaration about the module.So this PR silences
meta-spellingonly.conversionsandmigrationskeep warning,which is the deliberate outcome: whether either is intentional is an open question this
change does not answer, and a silenced warning would close it by default in the wrong
direction.
What changed
packages/spec/scripts/lib/schema-closure.ts(new) —CATEGORIES_WITHOUT_SCHEMA_CLOSURE,a hand-signed map of one entry carrying its citation, plus a both-directions coverage
check. The exemption is deliberately not read off the
Protocolmap: a categorydropped from that map by accident would exempt itself from the very check that would
have caught it.
packages/spec/scripts/build-docs.ts— the warning is guarded byschemaClosureAbsenceIsDeclared(category); the coverage check runs on the same walk andstops the build if a declared exemption grows a
json-schema/directory or loses itsmodule directory.
packages/spec/scripts/schema-closure.test.ts(new) — 13 pins: the declared list andits boundary, the citations grepped out of the files they cite, the coverage in both
directions plus the direction it deliberately does not report, and the caller wiring.
No empty directory was created to silence anything — the card forbids it and triage
doubled down; an empty directory claims a closure it does not have.
Ablation — the warning still works for a genuinely missing directory
Each leg proves its mutation landed on disk before reading any result, and each restore is
proven by observing state, never by an exit code.
Leg A — a directory that should exist, undeclared.
json-schema/data/(166 files,sha256 digest
6df3c80c...) moved aside; mutation proof: the path is gone and the stashedcopy still holds 166 files.
pnpm check:docsthen printed:Restored; digest and file count both match the baseline exactly.
Leg B — the exemption expires. An empty
json-schema/meta-spelling/created (mutationproof: the path exists), so the declared exemption no longer describes the tree:
Removed; the path is gone again.
Control — unmutated tree, same command:
check:docsEXIT=0, and onlyconversionsand
migrationswarn.meta-spellingis silent.Reverse verification of the caller pin. Predicted direction: red. With the guard
deleted from
build-docs.ts(anchor count 1 to 0, blob010cdaeatof6f4b003),schema-closure.test.tswent1 failed | 12 passed, failing exactly the pin that claimsthe guard is there. Restored with
git checkout HEAD --, blob back to010cdaeaandgit diff HEADempty for the path; control run 13 passed.Verification
Every verdict below is the command's own printed line with its exit code captured before
any pipe. Head at the time of the runs:
d70a3c0929.pnpm --filter @objectstack/spec testTest Files 483 passed (483),Tests 13115 passed (13115)pnpm --filter @objectstack/spec typechecktsc --noEmit,check:scripts-typecheck,check:test-typecheckall greentsc -p tsconfig.scripts.json --listFileslists both new files, and that is the projectcheck:scripts-typecheckrunsnode scripts/pm/dispatch-gates.mjs --commands)--ranreconciliation printed56 derived famil(ies) accounted forPREREQUISITE NOT MET(check:dual-build-cjs-loads,check:type-check-debt) — both need the whole workspace built and both say in their own words "this is NOT a pass: nothing was measured". Reported as NOT MEASURED, left to CI, not as failures.pnpm --filter @objectstack/spec check:docspnpm lint(eslint . --no-inline-config, whole repo, not narrowed)pnpm check:nul-bytesplus a hand scan with the control-byte grep over the three touched filesOne note for the reviewer:
dispatch-gatesflagged thatscripts/engine-double-contract.pinned.jsonmoved on
origin/mainafter this branch was cut. This diff does not touch it andcheck:engine-double-contractis green here; CI will run the newer roster.Changeset: none owed
skip-changesetapplies, and the reasoning is mechanical rather than a judgement call:the whole diff is under
packages/spec/scripts/, which is not in the package'sfiles[], is not anexportssubpath, and is not atsupentry — nothing in itreaches a published artifact.
check:docsexits 0 on the unmutated tree, so the generatedcontent/docs/references/output is unchanged too. No consumer can observe this release,so it releases nothing.
enhancement, notbug— the type the measurement supportsThe build does not drop directories:
gen:schemaemits exactly the 15 categories itsProtocolmap names,gen:docsexits 0, and the reference docs are byte-identical beforeand after. Nothing is broken, so this is not a repair. What it removes is an
unconditionally-true line in a diagnostic channel — in the card's own words, a warning
that fires unconditionally for an intended condition is a warning readers learn to skim,
which is how a real one later gets skimmed too. That is an
enhancement.Clause 2: no
Confirmed mechanically, not recalled. Path limb:
node scripts/pm/dispatch-gates.mjs --tierprints "no path-derived mandate: the surface hits none of the 3 declared glob(s)" —
SUSPECT_TIER_GLOBSispackages/spec/src/**and this diff is entirely underpackages/spec/scripts/**. Content limb: no published schema surface (not infiles[],no
exportssubpath, notsupentry, and nothing outsidepackages/spec/scripts/imports the new module), and no accept-set movement —
check:docsexit 0 means nogenerated byte moved.
Generated by Claude Code