fix(rest): the served openapi.json publishes the artifact's info.version (#11646) - #12958
Conversation
Delete the serve-time override that rewrote `info.version` with the deployment's declared `api.version`. The `info` block is produced and owned by `packages/spec`; this route's own twin has asserted that serve-time enrichment must not touch it, with the assertion narrowed to `info.title` alone precisely because `version` was overridden — the invariant stated and then excepted in one file. The twin's assertion is widened to the whole `info` block, and the four cases that pinned the old meaning are inverted to pin the new one: the served version is the artifact's, a custom `api.version` moves the MOUNT and not the field, and the runtime version still never reaches it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjujZN219uFzBhSYfMykCd
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f9e91a24d14ad8db8042c24c0f32065c8b3cc8f7 && git checkout f9e91a24d14ad8db8042c24c0f32065c8b3cc8f7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2b4178aa53ca62089f43e2cfae0b7838cf340dd1 50e0fd1128ac2c693e0d54165a63550b8c90db43 && git checkout -B drift-repro 2b4178aa53ca62089f43e2cfae0b7838cf340dd1 && git merge --no-ff 50e0fd1128ac2c693e0d54165a63550b8c90db43
node scripts/docs-audit/affected-docs.mjs --json 2b4178aa53ca62089f43e2cfae0b7838cf340dd1 |
Fixes #11646
Maintainer ruling, 2026-08-25 (option B per the card's own lettering, verbatim 「全部同意」):
delete the serve-time override so the served document's
info.versionpasses through fromthe artifact. Options A (leave it) and C (a config key) are dead.
What changed — 3 files, net -27 lines of source
packages/rest/src/rest-server.ts— theGET {basePath}/openapi.jsonhandler no longerwrites to
info. The deleted block was:FROM the deployment's declared API version identifier (
api.version, default'v1')TO the published artifact's own version (
17.2.0today, set frompackages/spec'spackage version by
build-openapi.tsand pinned byopenapi-self-consistency.test.ts).enriched.infonow has zero writers in this package.packages/rest/src/rest-openapi-route.test.ts— the sharp point of the card. The twintest named "passes the half of the document
packages/specowns through serve untouched" has alwaysstated the invariant that "serve-time enrichment must not touch" the
infoblock — with itsassertion narrowed to
info.titlealone, precisely becauseversionwas overridden. Theinvariant was stated and then excepted, in one file. It is now:
The whole block, all five keys. An anti-vacuity guard above it requires
artifact.info.versionto be truthy, so a
toEqualover aninfowith noversioncannot pass without reaching thefield this card is about. The four
#11546cases that pinned the old meaning are inverted to pinthe new one, including a custom
api.versionnow proven to move the mount and not thefield (the mount move is asserted first, so a v9 that quietly stopped taking effect cannot make
the
info.versionhalf pass for the wrong reason)..changeset/openapi-served-info-version-passes-through.md—minoron@objectstack/rest,stating FROM/TO explicitly and that the measured consumer pull is zero.
Ablation — the widened assertion goes RED and names itself
Predicted direction: RED. Restored the deleted override on top of the commit; confirmed the
mutation on disk before reading any verdict (anchored counts injected-text 1 / marker 1, blob
hash
f153673e…->1e48ae21…); restored undertrap … EXIT INT TERMviagit checkout HEAD --with an absolute path; proved the restore by an equal blob hash(
f153673e…) and an emptygit diff HEAD, not by an exit code. The subject resolves through asame-package relative source import, so vitest reads the mutated source itself — no
dist/hopon this leg, nothing to rebuild.
Result:
Tests 4 failed | 13 passed (17). The widened assertion failed first and named itself:Post-restore re-run:
Tests 17 passed (17).Verification — all at commit
728208826pnpm --filter '@objectstack/rest^...' buildVERDICT command-exit 0pnpm --filter @objectstack/rest testTest Files 154 passed (154)/Tests 2543 passed (2543)pnpm --filter @objectstack/rest typecheckVERDICT command-exit 0;check:test-typecheck: OK — 3 file(s) / 6 error(s) held (shrink-only)tsc -p tsconfig.test.json --listFilespnpm --filter @objectstack/spec exec vitest run scripts/openapi-self-consistency.test.tsTests 21 passed (21)— untouched, see belowGate families were derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackat commit
728208826(change set: 3 paths vs merge base8beb3deaf). All 24 path-matchedfamilies plus the convention-triggered ones ran green, including
check:route-envelope,check:authz-resolver,check:dispatcher-error-vocabulary,check:cross-package-test-inputs,check:test-source-alias,check:type-source-resolution,check:engine-double-contract,check:where-matcher,check:nul-bytes, andcheck:type-check-debt --re-measureafter buildingthe full workspace closure (
OK — 31 ledger entr(ies) re-measured, none above its recorded number).NOT MEASURED, declared rather than counted as green:
node scripts/pm/check-half-states.mjsreturned its own
PREREQUISITE NOT MET(exit 3) — this container'sGITHUB_TOKENis the proxyplaceholder, so it swept nothing. That is no reading, neither clean nor dirty. CI runs it with a
real credential. Repo-wide
pnpm lintwas not run here and is left to CI.Not a cross-lane change — measured, not assumed
The card's routing note flagged that this might read as a
domain:specchange becausepackages/specowns the field. It does not:packages/spec/scripts/openapi-self-consistency.test.tspins
doc.info.versionagainstpackage.json, but it reads the artifact(
readArtifact(runGenerator())), never the served document, so deleting the serve-time overrideleaves it green untouched. Verified both ways — the suite is green above, and the diff touches
zero files under
packages/spec.Consumer survey — zero, with its limitation declared
Re-run at this base rather than inherited. Instrument: a multiline-aware proximity regex over
info/version, walked with a filesystem walk rather thangit grep. Two controls of differentkinds: (1) an instrument control — a synthetic file with 8 planted shapes including one broken
across three lines, 8/8 caught; (2) a corpus-reachability control that is not a substring of
the term under test —
MetadataTypeSchema, 96 hits across the 8708-file walk. Thegit grepfalse-zero was demonstrated live in this container on the same untracked control file:
git grep0 files,grep -r1 file.Result: 21 hits, hand-classified, zero are a consumer reading the served document's
info.versionby value. 12 of the 21 are the two files under change; the rest are a differentinfo(package manifests, SBOM, aServiceInfoSchemafixture), the artifact-side pin, or prose.A separate probe for the equality dependence the ruling named — a client deriving its route
prefix from the served value — is also zero: the repo's one route-prefix derivation
(
packages/core/src/qa/http-adapter.ts) readsapi.basePath/api.versionfrom config.objectui,cloudandcloud-v1are absent from this container (measured: no checkout at any of the three paths). The zero across
those three is the card's 2026-08-24 reading carried forward, not re-measured here. Stated so a
single-repo sweep does not stand in for the four-repo zero.
Fence
Re-derived at
2026-08-28T10:13:31Z, base8beb3deaf, over the union of 20 open PRs / 631changed-file rows read from one named
refs/pull/NNN/headref per PR number.packages/rest/src/rest-server.ts,rest-openapi-route.test.tsanderror-response.tsall return zero holders. Positive controlfor that zero (not a substring of the term under test):
packages/types/src/thrown-http-error.tsreturns its expected hit from #12946.
packages/rest/package.jsonandCHANGELOG.mdare held by#11336 and are not touched — the changeset is a new per-file
.changeset/*.md.Note for the release compiler, not an edit
The unreleased
.changeset/openapi-info-version-is-the-api-version.md(from #11641, same cycle)states that the served
info.versioncarries the API version identifier. That half is supersededhere; its fallback-removal half stands. My changeset says so. I did not edit that file.
Generated by Claude Code