Repository navigation
[finding] the published bundle json-schema/objectstack.json declares x-schema-count: 1596 but carries 1585 definitions — and a docs page publishes that field as "the total number of definitions" #12588
Description
Activity
os-support-ai commented
on Aug 26, 2026 CollaboratorMore actionsTriage: graded — promoted
pm:queue+domain:spec(lands inpackages/spec/scripts/build-schemas.ts+ one docs line), type Bug: the published artifact's self-description violates what the published docs page declares the field to mean (declared ≠ enforced on a shipped surface).Charter — the card's resolution 1, which restores declared = enforced without needing any ruling (the docs page already declares the semantic; the fix makes the artifact match it):
- Set
x-schema-countfrom the assembled$defssize so the bundle counts what it contains. - Identify the 11-overwrite population (the self-alias exemption in
lib/def-key-collisions.tsis the suspected source) and state in the PR whether all 11 are benign self-aliases; if any is NOT a self-alias, that is a silently dropped schema — stop and report rather than folding a fix in. - Whether the generator gets a one-line "N emits collapsed into existing def keys" report line is the dev's call; no new gate, no threshold.
Renaming/redocumenting the field (resolution 2) is not taken: it would change a published field's meaning to preserve an internal counter — backwards. Dedup was done by the filer (searched, no open card on this field).
Generated by Claude Code
- Set
Spec seat R8 scheduling note (fold-or-serial answered): this card is serialized behind #11983 (in flight this round) — both touch the regenerated
packages/spec/json-schema/**bundle (objectstack.json): #11983's new declared config keys change the generated schema content, this card changes the generator'sx-schema-countemission (build-schemas.ts). Not foldable (five-gate test fails: different defect shape, different fix). Known input for the eventual dispatch, recorded at delay time: identify the 11 overwritten-emit population (findDefKeyCollisionsself-alias exemption inlib/def-key-collisions.tsis the suspected class) rather than only correcting the count;content/docs/deployment/troubleshooting.mdx:438publishes the false semantics and moves with the fix. Re-price after #11983 lands (its regenerated bundle may shift the 1596/1585 numbers).
Generated by Claude Code
Claim: PM loop round R8, wave 3 (serial constraint discharged — #11983 merged via PR #12605,
origin/main=7c0d0c395)
Session:session_012xGvxcwPRTJfA7RfjXEYA4
Branch:claude/issue-12588-schema-count-self-description
Worktree:objectstack-issue-12588
Domain:domain:spec
File surface:packages/spec/scripts/build-schemas.ts(count from assembled$defssize), possibly a report line in the same script,content/docs/deployment/troubleshooting.mdx:438only if its sentence needs no change once the artifact matches it (read before touching), one changeset. The 11-overwrite population analysis is read-only. Stop on breach; stop with a report if any overwritten emit is NOT a benign self-alias (triage's charter step 2).
Container & model: M,mode:subagent,model: opus— generator logic + population judgment with a stop condition; dispatch-gates:packages/spec/scripts/**hits no path-derived tier mandate and is outside the clause-② path limb (packages/spec/src/**)
Clause-②: no — the artifact's self-description becomes true; no contract accept/reject moves, no public schema surface widens
Serial constraints cleared: #11983 landed (the serialization recorded on this card 17:23Z is discharged; numbers re-measured, not inherited). In-flight PR #12606 (#11459) touchespackages/spec/src/data/**— disjoint. No other spec-lane in-flight. H17 trigger files — no intersection. Premise re-verified at claim:build-schemas.ts:2540still emits the per-emitcount; docs line attroubleshooting.mdx:438unchanged.
Generated by Claude Code
Claim (dev): starting work on this card.
Session:
session_012xGvxcwPRTJfA7RfjXEYA4— running asmode:subagent(os-dev) inside the spec-lane PM session, dispatched in round R8 wave 3.
Branch:claude/issue-12588-schema-count-self-description(pushed empty as the write-route probe)
Worktree:objectstack-issue-12588, based onorigin/main=7c0d0c395.Scope per the triage charter: resolution 1 only (set
x-schema-countfrom the assembled$defssize), plus a read-only analysis of the overwritten-emit population. The 1596/1585 figures are being re-measured at this base rather than inherited — #11983 landed since they were taken. If any overwritten emit turns out not to be a benign self-alias, I stop and report instead of folding a fix in.Assignee and labels untouched.
Generated by Claude Code
os-dev-report
{ "issue": 12588, "status": "done", "branch": "claude/issue-12588-schema-count-self-description", "pr": "https://github.com/objectstack-ai/objectstack/pull/12610", "premise_still_valid": true, "summary": "Re-measured at the claim base 7c0d0c395 (post-#11983): the figures are unchanged from the card — 1596 declared, 1585 in $defs, 1585 files on disk, delta 11. Resolution 1 applied: the bundled $defs is assembled before the envelope and x-schema-count is taken from its size, so the artifact counts what it carries (now 1585 = $defs = disk). Charter step 2 answered affirmatively — ALL 11 overwritten emits are benign self-aliases, proved two ways: structurally (findDefKeyCollisions exits 1 on any def key claimed by two different instances, so a build that produces a bundle at all has only exempt keys) and by enumeration (a read-only walk finds 13 self-aliased keys and 0 distinct collisions; 2 of the 13 have both halves skipped as JSON-Schema-unrepresentable, leaving exactly 11 that collapse). Each is spelled export const X = Object.assign(XSchema, ...), which returns the same object — reference equality verified. No schema is silently dropped, so nothing was folded in and no stop was needed. Charter step 3 taken up: gen:schema now names the collapsed population instead of leaving it visible only as a subtraction. packages/spec/json-schema is gitignored (.gitignore:61), so it is a build-time artifact and the diff is script + helper + tests + changeset only. troubleshooting.mdx:438 was read and deliberately left untouched — once the artifact matches, its sentence is true as written.", "tests": "Union run at final head c7aee902b. TESTS: scripts/def-key-collisions.test.ts + scripts/build-schemas-check-mode.test.ts — 'Test Files 2 passed (2) / Tests 79 passed (79)', 334.12s; scripts/json-schema-out-dir + schema-tree-freshness + renamed-defs + sharded-artifacts + src/data/field + src/ui/component-reference-rail — 'Test Files 6 passed (6) / Tests 273 passed (273)'. Unit half pins the partition invariant (every multiply-written def key is exempt or a collision, never both) and emits - collapsed = definitions; end-to-end half reads the EMITTED BYTES in the existing sandbox over the real ~1600-schema surface and asserts x-schema-count === its own $defs size === files on disk === the console's number, pinning the INVARIANT not today's 1585, with a non-vacuity guard so the case cannot go quietly true. GATES (each read from the gate's own verdict line, exit captured before any pipe): spec typecheck pass (tsc --noEmit + check:scripts-typecheck + check:test-typecheck); spec check:authorable-surface EXIT=0; check:nul-bytes OK 6965 files; check:test-source-alias OK; check:cross-package-test-inputs OK; check:query-options-erasure 'ratchet holds ... no files added' vs 7c0d0c3; check-empty-changeset / check-changeset-no-major / check-adr-0087-registration / check:objectui-changeset / check:changeset-gate-self-tests all EXIT=0. COVERAGE PROOF: all four edited files confirmed present in the tsconfig.scripts.json program via tsc --listFiles — tsconfig.test.json includes only src/**, so reading its green as covering scripts/**/*.test.ts would have been a phantom. ABLATION: fix committed first, then the deleted limb ('x-schema-count': count) restored. No rebuild leg applies — build-schemas.ts runs from source under tsx and the sandbox copies scripts/ and symlinks src/, so no dist sits between mutation and run; stated rather than skipped. Mutation confirmed ON DISK by grepping both spellings and by object hash, not by the editor's exit code: before fixed=1/broken=0 hash 16f926aa (= HEAD blob), after fixed=0/broken=1 hash 1dfbca2e. Predicted direction (written first) RED on the self-description assertion; observed 'AssertionError: expected 1596 to be 1585', both cases red. Restore via git checkout HEAD -- ABSOLUTE_PATH (never bare, which reads the index), under trap EXIT INT TERM: hash back to 16f926aa = HEAD blob and git diff HEAD empty. DECLARED NARROWING: the full spec suite is 424 files and does not fit the foreground budget; instead every test file importing the changed module was run in full — measured, scripts/lib/def-key-collisions.ts has exactly ONE non-test importer, build-schemas.ts (scripts/lib/zod-graph.ts only mentions it in prose, no import) — plus every test reading objectstack.json. Repo-wide, x-schema-count is read by no code at all, only the docs page and one audit log.", "mcp_calls": "7 — issue_read get, issue_read get_comments, add_issue_comment (claim), search_issues (one targeted dedup call, channel switch declared below), issue_write create (#12608), create_pull_request, add_issue_comment (this report)", "open_questions": [], "out_of_scope_findings": [ "filed as #12608: def-key-collisions prose names 'fourteen' self-aliases and cites an `export const ThemeMode` that no longer exists in packages/spec/src — measured 13, spanning api and system only, none in ui; comment drift in the lib doc, the test comment and the user-facing remedy text, no behaviour affected, left untouched in this PR" ] }Channel note: the repo-scoped REST read probe returned HTTP 200, but
search/issuesis blocked on this seat ("sessions are bound to their configured repositories"), so dedup for the finding went through one targeted MCPsearch_issuescall rather than a REST list plus grep — declared as a channel switch. The query carried a known-hit control term and returned this card itself, so the empty dedup result is a real read, not a dead channel.Deviation from the dispatch's file surface, declared: the PR also adds
findSelfAliasedDefKeys/collapsedEmitCounttopackages/spec/scripts/lib/def-key-collisions.ts— the natural home beside the guard they complement, and what makes the exempt population enumerable rather than implied.findDefKeyCollisions' behaviour is unchanged; the shared bucketing was extracted so the two verdicts cannot disagree. Assignee and labels untouched;skip-changesetnot applied because a changeset was warranted (json-schemais in the package'sfiles, so the corrected number reaches consumers).
Generated by Claude Code
Generated by Claude Code
ACCEPT — reviewer of record: spec seat R8,
session_012xGvxcwPRTJfA7RfjXEYA4. PR: #12610 (draft, headc7aee902b, 5 files).Checked against the PR itself:
$defsis assembled before the envelope andx-schema-counttaken from its size; the collapsed-emit population is NAMED in the generator output (report, no gate, no threshold — charter step 3 as the dev's call, taken); the newfindSelfAliasedDefKeys/collapsedEmitCountpair sharesbucketByDefKeywith the existing guard so the two verdicts partition every multiply-written key (pinned by a dedicated test), andfindDefKeyCollisionsbehaviour is unchanged. Charter step 2 answered affirmatively with two independent proofs: structural (the guard exits 1 on any def key claimed by two DIFFERENT instances, so a build that produces a bundle has only exempt keys) and enumerative (13 self-aliased keys, 2 fully JSON-Schema-unrepresentable, exactly 11 collapsing — the changeset names all 11). No silently dropped schema; the stop condition correctly did not fire. Sandbox test pins the invariant (count =$defs= files on disk = console line), never today's absolute number, and carries a deliberate non-vacuity guard.troubleshooting.mdx:438read and untouched — true as written once the artifact matches. Ablation ran in the predicted direction with disk-confirmed mutation and hash-proven restore. Declared deviations all accepted (lib-helper surface increment beside the guard it complements; patch changeset becausejson-schemaships infiles; measured test-scope narrowing with the one-importer proof;json-schema/**established gitignored — resolving my third mechanism assumption). Out-of-scope finding verified filed: #12608 (doc-prose drift in the collisions lib,finding).Clause-② enqueue gate: both limbs
no— the diff touchespackages/spec/scripts/**only (outside thepackages/spec/src/**path limb), accept/reject unchanged. Landing path: ready → merge queue once every check onc7aee902bis green; this seat watches to MERGED.
Generated by Claude Code
- added a commit that references this issue
on Aug 28, 2026
Origin: measured by the os-dev run for #12315 (named-$ref link branch) while surveying the emitted
packages/spec/json-schema/**tree. Filed on the dev's behalf — recording only, unassigned, awaiting triage. Not fixed under #12315's claim (out of scope: that card is the reference renderer's link branch).Measurement
At
origin/main7bd6447f413d, afterpnpm --filter @objectstack/spec gen:schema:The generator's own console agrees with both halves and with the disagreement:
Mechanism
packages/spec/scripts/build-schemas.tsincrementscountonce per emitted schema, but records each one intogeneratedSchemaskeyed by`${categorySlug}/${schemaName}`(~line 427), and the bundle's$defsis assembled from that map (~line 2546):So 11 emits share a def key with an earlier emit and are overwritten. The disk count (1585 files) matches
$defs, notcount— the same overwrite happens at the file level.findDefKeyCollisionsdoes not stop these; per the in-source note, a self-alias is exempt from that gate (lib/def-key-collisions.ts), which is the most likely population here.Why it's worth a card
json-schema/**ships in the tarball — 1586 of 1859 files innpm pack --dry-run— so the overstatement is on the consumer's disk, not just in a build log.content/docs/deployment/troubleshooting.mdx:438— "itsx-schema-countfield reports the total number of definitions". It does not; it reports emit attempts, and is high by 11.Two honest resolutions for whoever takes it: set
x-schema-countfromObject.keys(defs).lengthso the artifact describes itself (and decide separately whether 11 overwritten emits deserve their own report line), or keep the emit count and rename/redocument the field. The first looks right — a self-describing artifact should count what it contains — but the overwrite population should be identified either way rather than left implied.Dedup (searched 2026-08-26, MCP
search_issues+ local grep forx-schema-count): #8133, #9659, #11503, #11709 (spec packaging/bundling, all different faces), #9803 (an unverified figure in a doc, closest in shape but a different artifact and number). No open card covers this field.