Uh oh!
There was an error while loading. Please reload this page.
fix(spec): the bundled JSON Schema's x-schema-count counts the definitions it carries (#12588) - #12610
Merged
os-zhuang merged 3 commits intoAug 26, 2026
Merged
Conversation
The bundled objectstack.json took x-schema-count from the per-emit counter while its $defs is keyed by def key, so every self-aliased key inflated the published field: 1596 declared, 1585 shipped. Assemble $defs first and count what the artifact contains. Also name the exempt population the guard allows through, so the collapsed emits are reported rather than left implicit in a subtraction.
Unit half: the exempt self-alias population and the emits it absorbs. End-to-end half: the artifact the generator really writes, cross-checked against the files on disk and the run's own console.
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 Aug 26, 2026
os-zhuang
marked this pull request as ready for review
August 26, 2026 19:28
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 26, 2026
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 freeto 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#12588
json-schema/objectstack.jsonships in the npm tarball (json-schemais in the package'sfiles) andcontent/docs/deployment/troubleshooting.mdx:438publishes what itsx-schema-countfield means — "the total number of definitions". It did not report that. The generator took the number fromcount, a counter incremented once per emitted schema, while the bundle's$defsis assembled from a map keyed byCATEGORY/NAME. Every def key written more than once therefore widened a gap nothing reconciled.Re-measured on this branch's base (
7c0d0c395, which carries #11983's merge — the card's figures were taken at an older ref and are not inherited):The numbers are unchanged from the card.
$defsis now assembled before the envelope andx-schema-countis taken from its size, so the artifact describes itself:The disk count already agreed with
$defs— the same key collapses the per-schema file writes — so this brings the one disagreeing number into line with the two that already agreed. Emitted key order is unchanged ($defsstays last in the object literal).The overwritten-emit population — all 11 are benign self-aliases
Triage charter step 2, answered before any code was written. No schema is being silently dropped.
Two independent instruments agree.
1. Structural proof.
findDefKeyCollisionsruns before the bundle is assembled andprocess.exit(1)s on any def key claimed by two different schema instances. So a build that produces a bundle at all has only exempt keys by construction — the exemption is identity (bucket.every((e) => e.schema === bucket[0].schema)), not today's byte-equality.2. Enumeration. A read-only walk of the same namespace exports, bucketed by def key with the guard's own predicate:
Reconciling the walk with the real run: 24 exports are skipped as unrepresentable in JSON Schema. Four of those are both halves of
system/BatchTaskandsystem/WorkerConfig, so those two keys are never emitted at all; the other 20 each remove a key outright.1620 - 24 = 1596emits and1607 - 22 = 1585definitions — the arithmetic closes exactly, and13 - 2 = 11is the delta.The 11, now printed by
gen:schemaitself:Each is spelled
export const X = Object.assign(XSchema, { … })in source, which returns the same object — verified by reference equality in the walk above, so the second write provably cannot change what is published. Charter step 3 (the report line) taken up: it names the population rather than only counting it, since the card's third point is that the delta was the only visible trace of these writes.Docs line: read, deliberately untouched
troubleshooting.mdx:438says the field "reports the total number of definitions". Once the artifact matches the published semantics that sentence is true as written, so it is not edited. Resolution 2 (rename/redocument the field) was explicitly not taken by triage.Files
packages/spec/scripts/build-schemas.ts— assemble$defsfirst, count from it; report the exempt population.packages/spec/scripts/lib/def-key-collisions.ts—findSelfAliasedDefKeysandcollapsedEmitCount, the complement of the existing guard (same bucketing, same identity predicate, opposite verdict). Declared surface addition beyond the claim: the helper's natural home is beside the guard it complements, and it is what makes the population enumerable instead of implied.findDefKeyCollisions' behaviour is unchanged — the shared bucketing was extracted so the two verdicts cannot disagree about which entries belong to one key.packages/spec/scripts/def-key-collisions.test.ts,packages/spec/scripts/build-schemas-check-mode.test.ts— tests, below..changeset/schema-count-counts-the-definitions-shipped.md— patch. Notskip-changeset: the diff is scripts-only, butjson-schema/is in the package'sfiles, so a consumer's installed bundle carries a different number. The scripts-only precedent in this repo is mixed and splits on exactly that question.No schema content changes;
packages/spec/json-schema/**is gitignored (.gitignore:61), so it is a build-time artifact and not part of this diff.packages/spec/src/**is untouched — no intersection with in-flight #12606.Tests
Both halves, following this package's existing convention (a unit pin over the extracted helper plus an end-to-end pin that the caller really routes through it).
Unit —
scripts/def-key-collisions.test.ts: the two verdicts are complementary; they partition every multiply-written def key;collapsedEmitCountis N-1 per key; andemits - collapsed = definitions, the arithmetic the published field got wrong.End-to-end —
scripts/build-schemas-check-mode.test.ts, in the existing sandbox that runs the real generator over the real spec surface (src/is symlinked, so ~1600 schemas). It reads the emitted bytes, not a helper's return value, and assertsx-schema-countequals its own$defssize, equals the file count on disk, and equals the number the run's own console reports. The invariant is pinned, never today's 1585 — a test that unrelated PRs must edit gets edited without being read. A non-vacuity guard asserts this build still collapses emits, so the case cannot go quietly true.Reverse verification. Predicted direction, written before running: RED on the self-description assertion. The fix was committed first, then the deleted limb (
'x-schema-count': count) put back. No rebuild step applies —build-schemas.tsruns from source undertsxand the sandbox copiesscripts/and symlinkssrc/, so nodist/sits between the mutation and the run. The mutation was confirmed on disk by grepping both spellings and by object hash, not by the editor's exit code:Restore names
HEADexplicitly (a baregit checkout -- pathreads the index) and the script carries atrap … EXIT INT TERMwith absolute paths.Gates
Run locally at final head
c7aee902b, each result read from the gate's own verdict line with the exit code captured before any pipe:pnpm --filter @objectstack/spec typechecktsc --noEmit+check:scripts-typecheck+check:test-typecheckpnpm --filter @objectstack/spec check:authorable-surfaceauthorable-surface.base.jsontrailing notice is informational)pnpm check:nul-bytespnpm check:test-source-aliaspnpm check:cross-package-test-inputspnpm check:query-options-erasure7c0d0c3check-empty-changeset/check-changeset-no-major/check-adr-0087-registration/check:objectui-changeset/check:changeset-gate-self-testsAll four edited files were confirmed present in the
tsconfig.scripts.jsonprogram viatsc --listFiles, so the typecheck green actually covers them —tsconfig.test.jsonincludes onlysrc/**, and reading its green as coveringscripts/**/*.test.tswould have been a phantom.Gate families were re-derived from the real change set with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackrather than from the dispatch list.Declared narrowing. The full
@objectstack/specsuite is 424 test files and does not fit the foreground time budget. Instead: every test file that imports the changed module was run in full (measured —scripts/lib/def-key-collisions.tshas exactly one non-test importer,build-schemas.ts;scripts/lib/zod-graph.tsmentions it in prose only, with no import), plus every test that readsobjectstack.json. Repo-wide,x-schema-countis read by no code at all — only the docs page and one audit log — so no consumer can break on the value change. CI runs the farm either way.Out of scope, filed
#12608 — the same file's prose names "fourteen" self-aliases and a
ThemeModeexport that no longer exists; measured 13, spanningapiandsystemonly. Comment drift, no behaviour affected, left untouched here.Generated by Claude Code