Skip to content

@objectstack/rest overwrites discovery version with config.api.version ('v1') — the URL path segment served in the schema's "System Identity" field, masking the producer entirely #11292

Description

@os-zhuang

Found while implementing #11235 (deriving getDiscovery()'s version instead of the '1.0' literal). Filed rather than fixed: #11235's dispatch bounded that card to the producer, and this is a different package with a real design question attached, so it is not a mechanical in-place fix.

What was measured

packages/rest/src/rest-server.ts calls the producer and then unconditionally replaces the field one line later:

constdiscovery=awaitprotocol.getDiscovery();// Override discovery information with actual server configurationdiscovery.version=this.config.api.version;

config.api.version is the API version identifier, not an artifact identity:

  • normalizeConfig() in the same file defaults it to 'v1' (api.version ?? 'v1').
  • The same value builds the mounted URL: api.apiPath ?? \${api.basePath}/${api.version}`/api/v1`.
  • packages/spec/src/api/plugin-rest-api.zod.ts declares it version: z.string().default('v1').describe('API version identifier').

But DiscoverySchema (packages/spec/src/api/discovery.zod.ts) declares that field under /** System Identity */, grouped with name and environment — the "what server is this" question. #10993's ruling and its evidence (the SDK's connect() logging data.version next to apiName and services under "Connected to ObjectStack server") settled that reading.

So on any REST-served host, GET /api/v1/discovery answers version: "v1" — the path segment the caller already typed into the URL to get there.

Two consequences worth separating

  1. The field carries no identity information on the REST surface./health and the runtime dispatcher's /discovery both report a real derived artifact version after [finding] /api/v1/health reports a hardcoded version: '1.0.0' — a field that exists and lies, so no consumer can use it for artifact identity #10993; the REST /discovery reports "v1" on every build of every release forever. A consumer reading one document cannot tell which producer answered, and gets a useless answer from the one that most clients hit.
  2. It masks the producer.[finding] a THIRD hardcoded discovery version literal — getDiscovery() in packages/metadata-protocol/src/protocol.ts, identical defect to #10993, different producer/package #11235 fixed getDiscovery()'s hardcoded '1.0', but that value is overwritten before it reaches the wire — the producer fix is correct and pins the class at the producer level, and is observable to embedders calling ObjectStackProtocol.getDiscovery() directly, but it changes nothing on the REST endpoint while this line stands. The two fixes are independent and this one is where the wire-visible behaviour lives.

Why this is a decision, not a codemod

Deleting the line is not obviously right on its own. api.version is real information a client may want (which mounted API version answered), and the schema has no field for it — DiscoverySchema declares name / version / environment / routes / locale / services / capabilities / schemaDiscovery / scoping and nothing that means "API version". Options, roughly:

Recommend A unless someone can name a consumer of the API-version fact that routes does not already serve; B only if such a consumer exists. Either way it is a triage call, not a dev call.

Consumer survey (same one run for #11235)

No consumer reads discovery versionby value — no version comparison, feature gate, or cache key — in objectstack, objectui or cloud. Controls: an org-wide code search for a known-present string (x-objectstack-build-sha) returned 10 hits across objectstack and cloud, so the queries were live in both repos. The only by-value assertions found are packages/adapters/hono/src/hono.test.ts (pinning its own mock of the dispatcher producer), packages/client/tests/integration/01-discovery.test.ts (toMatch(/^v?\d+/) — which passes on 'v1' today and would also pass on a semver), and packages/spec/src/api/discovery.test.ts (a schema-parse fixture). "I found no consumer" is not "there is no consumer": embedder code outside these three repos is not visible from here.

Not in scope of this card

Neither producer is at fault here — packages/runtime/src/http-dispatcher.ts (#10993) and packages/metadata-protocol/src/protocol.ts (#11235) both derive the value correctly. This is the serving seam in @objectstack/rest only.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions