Skip to content

fix(spec): stop gen:docs warning about the one schema directory declared absent - #16509

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-15870-gen-docs-absent-schema-dir-warning
Sep 7, 2026
Merged

fix(spec): stop gen:docs warning about the one schema directory declared absent#16509
huangyiirene merged 1 commit into
mainfrom
claude/issue-15870-gen-docs-absent-schema-dir-warning

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #15870

pnpm --filter @objectstack/spec gen:docs printed three
Warning: Schema directory ... does not exist lines on every run. This silences
one 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 tracked
files" 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:

$ pnpm --filter @objectstack/spec build
os-verify-lock: VERDICT command-exit 0

$ ls -d packages/spec/json-schema/conversions \
        packages/spec/json-schema/meta-spelling \
        packages/spec/json-schema/migrations 2>&1
ls: cannot access 'packages/spec/json-schema/conversions': No such file or directory
ls: cannot access 'packages/spec/json-schema/meta-spelling': No such file or directory
ls: cannot access 'packages/spec/json-schema/migrations': No such file or directory

$ pnpm --filter @objectstack/spec gen:docs 2>&1 | grep -n "does not exist"
15:Warning: Schema directory .../packages/spec/json-schema/conversions does not exist
16:Warning: Schema directory .../packages/spec/json-schema/meta-spelling does not exist
17:Warning: Schema directory .../packages/spec/json-schema/migrations does not exist

Positive control that ls was reading a populated tree and not an empty one: the same
build 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.ts walks the 18 module directories under packages/spec/src/
(CATEGORY_TITLES holds that list total in both directions). gen:schema creates one
json-schema/CATEGORY/ per entry of the hard-coded 15-entry Protocol namespace map
in build-schemas.ts. 18 minus 15 is exactly the three that warn.

contracts is the counter-example worth stating, because it is the one people reach for:
it is on the Protocol map, so it gets json-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/ and CHANGELOG.md:

git grep -n "schema closure"
git grep -n -i -E "schema[- ]free|schemaless|no schema closure|no JSON Schema|without the schema machinery|not a Protocol" -- packages/spec

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 zero
below is a reading, not a broken probe.

directory declared? what the search found
meta-spelling yes scripts/build-meta-url-spelling.ts:20 — "/meta-spelling entry 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:81 titles it "Meta-Spelling Vocabulary", not "Protocol", because the entry "folds without the schema machinery every Protocol category links"; and browser-reachable-entries.json declares ./meta-spelling browser-reachable under the standing schema-free principle.
conversions no Zero hits. The only text that mentions its missing directory at all is a comment in build-docs.ts section 2, which records it as an asymmetry that comment's guard deliberately does not act on. CATEGORY_TITLES calls it "Conversions Protocol" — the same word every category with a schema closure carries.
migrations no Zero hits, and it is the further one from an exemption, not the nearer: src/migrations/spec-changes.ts exports 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 the Protocol map — a fact about a generator's input list, not a declaration about the module.

So this PR silences meta-spelling only. conversions and migrations keep 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 Protocol map: a category
    dropped 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 by
    schemaClosureAbsenceIsDeclared(category); the coverage check runs on the same walk and
    stops the build if a declared exemption grows a json-schema/ directory or loses its
    module directory.
  • packages/spec/scripts/schema-closure.test.ts (new) — 13 pins: the declared list and
    its 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 stashed
copy still holds 166 files. pnpm check:docs then printed:

Warning: Schema directory .../json-schema/conversions does not exist
Warning: Schema directory .../json-schema/data does not exist      (still fires)
Warning: Schema directory .../json-schema/migrations does not exist

Restored; digest and file count both match the baseline exactly.

Leg B — the exemption expires. An empty json-schema/meta-spelling/ created (mutation
proof: the path exists), so the declared exemption no longer describes the tree:

check:docs EXIT=1
CATEGORIES_WITHOUT_SCHEMA_CLOSURE in scripts/lib/schema-closure.ts no longer describes this tree:
    - meta-spelling (declared to ship no schema closure, but json-schema/meta-spelling/ now exists — delete the entry so the category is checked like every other one)

Removed; the path is gone again.

Control — unmutated tree, same command: check:docs EXIT=0, and only conversions
and migrations warn. meta-spelling is silent.

Reverse verification of the caller pin. Predicted direction: red. With the guard
deleted from build-docs.ts (anchor count 1 to 0, blob 010cdaea to f6f4b003),
schema-closure.test.ts went 1 failed | 12 passed, failing exactly the pin that claims
the guard is there. Restored with git checkout HEAD --, blob back to 010cdaea and
git diff HEAD empty 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.

what verdict
pnpm --filter @objectstack/spec test exit 0Test Files 483 passed (483), Tests 13115 passed (13115)
pnpm --filter @objectstack/spec typecheck exit 0tsc --noEmit, check:scripts-typecheck, check:test-typecheck all green
coverage of the new files measured, not assumed: tsc -p tsconfig.scripts.json --listFiles lists both new files, and that is the project check:scripts-typecheck runs
derived gate families (node scripts/pm/dispatch-gates.mjs --commands) 56 derived, 56 run; --ran reconciliation printed 56 derived famil(ies) accounted for
of those 56 54 exit 0. 2 exit 3 = PREREQUISITE 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:docs exit 0 — the generated docs are byte-identical; this change alters console output only
pnpm lint (eslint . --no-inline-config, whole repo, not narrowed) exit 0
pnpm check:nul-bytes plus a hand scan with the control-byte grep over the three touched files exit 0 / no hits

One note for the reviewer: dispatch-gates flagged that scripts/engine-double-contract.pinned.json
moved on origin/main after this branch was cut. This diff does not touch it and
check:engine-double-contract is green here; CI will run the newer roster.

Changeset: none owed

skip-changeset applies, and the reasoning is mechanical rather than a judgement call:
the whole diff is under packages/spec/scripts/, which is not in the package's
files[], is not an exports subpath, and is not a tsup entry — nothing in it
reaches a published artifact. check:docs exits 0 on the unmutated tree, so the generated
content/docs/references/ output is unchanged too. No consumer can observe this release,
so it releases nothing.

enhancement, not bug — the type the measurement supports

The build does not drop directories: gen:schema emits exactly the 15 categories its
Protocol map names, gen:docs exits 0, and the reference docs are byte-identical before
and 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 --tier
prints "no path-derived mandate: the surface hits none of the 3 declared glob(s)"
SUSPECT_TIER_GLOBS is packages/spec/src/** and this diff is entirely under
packages/spec/scripts/**. Content limb: no published schema surface (not in files[],
no exports subpath, no tsup entry, and nothing outside packages/spec/scripts/
imports the new module), and no accept-set movement — check:docs exit 0 means no
generated byte moved.


Generated by Claude Code

…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
@github-actions github-actions Bot added the size/m label Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c383352cb752245899b6ca7e2dc7d233405113eepackageMentionDocs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: gen:docs warns on three schema directories that are absent by design, on every run

2 participants