Skip to content

fix(rest): the served openapi.json publishes the artifact's info.version (#11646) - #12958

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-11646-served-info-version-spec-owned
Aug 28, 2026
Merged

fix(rest): the served openapi.json publishes the artifact's info.version (#11646)#12958
os-litant merged 2 commits into
mainfrom
claude/issue-11646-served-info-version-spec-owned

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

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.version passes through from
the 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 — the GET {basePath}/openapi.json handler no longer
writes to info. The deleted block was:

if (enriched.info) {
    enriched.info = {
        ...enriched.info,
        version: this.config.api.version,
    };
}

FROM the deployment's declared API version identifier (api.version, default 'v1')
TO the published artifact's own version (17.2.0 today, set from packages/spec's
package version by build-openapi.ts and pinned by openapi-self-consistency.test.ts).
enriched.info now has zero writers in this package.

packages/rest/src/rest-openapi-route.test.ts — the sharp point of the card. The twin
test named "passes the half of the document packages/spec owns through serve untouched" has always
stated the invariant that "serve-time enrichment must not touch" the info block — with its
assertion narrowed to info.title alone, precisely because version was overridden. The
invariant was stated and then excepted, in one file. It is now:

expect(
  body.info,
  'serve-time enrichment must not touch `info` — the WHOLE block is the artifact\'s, `version` included (#11646). A serve-time override of any `info` field lands here.',
).toEqual(artifact.info);

The whole block, all five keys. An anti-vacuity guard above it requires artifact.info.version
to be truthy, so a toEqual over an info with no version cannot pass without reaching the
field this card is about. The four #11546 cases that pinned the old meaning are inverted to pin
the new one, including a custom api.version now proven to move the mount and not the
field (the mount move is asserted first, so a v9 that quietly stopped taking effect cannot make
the info.version half pass for the wrong reason).

.changeset/openapi-served-info-version-passes-through.mdminor on @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 under trap … EXIT INT TERM via
git checkout HEAD -- with an absolute path; proved the restore by an equal blob hash
(f153673e…) and an empty git diff HEAD, not by an exit code. The subject resolves through a
same-package relative source import, so vitest reads the mutated source itself — no dist/ hop
on this leg, nothing to rebuild.

Result: Tests 4 failed | 13 passed (17). The widened assertion failed first and named itself:

AssertionError: serve-time enrichment must not touch `info` — the WHOLE block is the
artifact's, `version` included (#11646). …
    "title": "ObjectStack REST API",
-   "version": "17.2.0",
+   "version": "v1",

Post-restore re-run: Tests 17 passed (17).

Verification — all at commit 728208826

what result
pnpm --filter '@objectstack/rest^...' build VERDICT command-exit 0
pnpm --filter @objectstack/rest test Test Files 154 passed (154) / Tests 2543 passed (2543)
pnpm --filter @objectstack/rest typecheck VERDICT command-exit 0; check:test-typecheck: OK — 3 file(s) / 6 error(s) held (shrink-only)
tsc -p tsconfig.test.json --listFiles both edited files ARE in the program (2/2), so "typecheck green" covers them
pnpm --filter @objectstack/spec exec vitest run scripts/openapi-self-consistency.test.ts Tests 21 passed (21) — untouched, see below
gate union (28 families, derived after the last edit) green; two NOT MEASURED, named below

Gate families were derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
at commit 728208826 (change set: 3 paths vs merge base 8beb3deaf). All 24 path-matched
families 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, and check:type-check-debt --re-measure after building
the 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.mjs
returned its own PREREQUISITE NOT MET (exit 3) — this container's GITHUB_TOKEN is the proxy
placeholder, so it swept nothing. That is no reading, neither clean nor dirty. CI runs it with a
real credential. Repo-wide pnpm lint was 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:spec change because
packages/spec owns the field. It does not: packages/spec/scripts/openapi-self-consistency.test.ts
pins doc.info.version against package.json, but it reads the artifact
(readArtifact(runGenerator())), never the served document, so deleting the serve-time override
leaves 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 than git grep. Two controls of different
kinds: (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. The git grep
false-zero was demonstrated live in this container on the same untracked control file:
git grep 0 files, grep -r 1 file.

Result: 21 hits, hand-classified, zero are a consumer reading the served document's
info.version by value. 12 of the 21 are the two files under change; the rest are a different
info (package manifests, SBOM, a ServiceInfoSchema fixture), 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) reads api.basePath/api.version from config.

⚠️ This sweep covers one of the card's four repositories. objectui, cloud and cloud-v1
are 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, base 8beb3deaf, over the union of 20 open PRs / 631
changed-file rows read from one named refs/pull/NNN/head ref per PR number. packages/rest/src/rest-server.ts,
rest-openapi-route.test.ts and error-response.ts all return zero holders. Positive control
for that zero (not a substring of the term under test): packages/types/src/thrown-http-error.ts
returns its expected hit from #12946. packages/rest/package.json and CHANGELOG.md are 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.version carries the API version identifier. That half is superseded
here; its fallback-removal half stands. My changeset says so. I did not edit that file.


Generated by Claude Code

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
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 28, 2026
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • 1 anchor(s) matched too much of the corpus to be a work list: /api/v1 (route, 81 pages)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 13 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 2b4178aa53ca62089f43e2cfae0b7838cf340dd1packageMentionDocs.

Which tree this was computed on

This run read content/docs from f9e91a24d14ad8db8042c24c0f32065c8b3cc8f7 — the merge of head 50e0fd1128ac2c693e0d54165a63550b8c90db43 into base 2b4178aa53ca62089f43e2cfae0b7838cf340dd1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants