Skip to content

Commit b2fe6ed

Browse files
committed
docs(spec): name only the OpenAPI document where the retired docs viewer was named
Claude-Session: https://claude.ai/code/session_01B2EeooonSXrANpkpgUSfFZ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2354058 commit b2fe6ed

4 files changed

Lines changed: 15 additions & 18 deletions

File tree

‎packages/spec/liveness/rest_api.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@
6868
},
6969
"enableOpenApi": {
7070
"status": "live",
71-
"verifiedAt": "2026-09-21",
71+
"verifiedAt": "2026-10-22",
7272
"evidenceScope": "in-repo",
73-
"evidence": "packages/rest/src/rest-server.ts#registerRoutes (`if (this.config.api.enableOpenApi)` gates registerOpenApiEndpoints, which mounts both `/openapi.json` and the `/docs` viewer)",
73+
"evidence": "packages/rest/src/rest-server.ts#registerRoutes (`if (this.config.api.enableOpenApi)` gates registerOpenApiEndpoints, which mounts `/openapi.json`, the OpenAPI document)",
7474
"producer": "packages/rest/src/rest-server.ts#normalizeConfig (parses `config.api` through `parseDeclaredApiConfig` and lists the key into `this.config.api`, the object every consumer below reads; the parsed schema's own `.default()`s supply the value when the host omits the key)",
7575
"note": "One consumer, and it is the mount. Worth reading beside `documentation.*` below: this flag decides WHETHER the OpenAPI document is served at all, and it is the only key on this block that the served document's existence depends on — nothing on this block decides what the document SAYS. REACHABILITY (#15543, standing reading): embedder-only. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and the one door is programmatic — createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts). No shipped boot path opens it with a config of its own: packages/cli/src/commands/serve.ts forwards exactly two keys out of the stack's own top-level `api:` block (enableProjectScoping, projectResolution) and plugin-dev calls createRestApiPlugin() with no config at all, so a CLI-started deployment always gets the schema default for every key here. `live` answers who READS the key; reachability answers who can SET it, and the two never substitute for each other."
7676
},

‎packages/spec/scripts/lib/openapi-self-consistency.ts‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@
3030
*
3131
* 1. **Every local `$ref` resolves.** Any `$ref` beginning with `#/` is a JSON
3232
* Pointer into this same document; if it does not resolve, the document is
33-
* broken for every consumer that parses it (Scalar's viewer at
34-
* `GET /api/v1/docs`, and any client generator pointed at
35-
* `GET /api/v1/openapi.json`). Resolution is by pointer rather than by a
36-
* `#/components/schemas/` prefix match so that a future `#/$defs/…` ref
37-
* is covered without touching this file.
33+
* broken for every consumer that parses it (any client generator or
34+
* API tool pointed at `GET /api/v1/openapi.json`). Resolution is by
35+
* pointer rather than by a `#/components/schemas/` prefix match so that a
36+
* future `#/$defs/…` ref is covered without touching this file.
3837
* 2. **No schema is silently degraded.** See `assertNoDegradedSchemas`.
3938
*
4039
* ## Rule 1 after #5744 — vacuous today, retained for `$defs`
@@ -153,9 +152,9 @@ export function assertRefsResolve(doc: unknown): void {
153152
` defined components.schemas: [${defined.join(', ') || '<none>'}]\n` +
154153
`\n` +
155154
` A $ref that resolves to nothing breaks every consumer of the published\n` +
156-
` document (the Scalar viewer at GET /api/v1/docs renders an empty schema\n` +
157-
` panel; client generators fail at parse time). If components.schemas is\n` +
158-
` empty, the collector in build-openapi.ts skipped its inputs — note that\n` +
155+
` document at GET /api/v1/openapi.json (client generators fail at parse\n` +
156+
` time). If components.schemas is empty, the collector in\n` +
157+
` build-openapi.ts skipped its inputs — note that\n` +
159158
` lazySchema() returns a Proxy whose typeof is 'function', not 'object'\n` +
160159
` (#5168).`,
161160
);

‎packages/spec/src/api/rest-server.zod.ts‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,13 @@ export const RestApiConfigSchema = lazySchema(() => z.object({
134134
enableDiscovery: z.boolean().default(true).describe('Enable API discovery endpoint'),
135135

136136
/**
137-
* Enable OpenAPI 3.1 spec endpoints — `GET <basePath>/openapi.json`
138-
* for the machine-readable contract and `GET <basePath>/docs` for a
139-
* zero-dep Scalar-rendered HTML viewer. The spec ships pre-generated
137+
* Enable the OpenAPI 3.1 document endpoint — `GET <basePath>/openapi.json`,
138+
* the machine-readable contract. The spec ships pre-generated
140139
* from @objectstack/spec and is enriched at request time with the
141140
* actual server URL + the runtime-registered objects (so `{object}`
142141
* placeholders expand into one path per concrete table).
143142
*/
144-
enableOpenApi: z.boolean().default(true).describe('Enable OpenAPI 3.1 spec & docs viewer endpoints'),
143+
enableOpenApi: z.boolean().default(true).describe('Enable the OpenAPI 3.1 document endpoint (`GET <basePath>/openapi.json`)'),
145144

146145
/**
147146
* Server-wide switch for the structured-search surface — and, like every key
@@ -232,8 +231,7 @@ export const RestApiConfigSchema = lazySchema(() => z.object({
232231
'`api.documentation.enabled` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — '
233232
+ 'nothing ever read it: whether the server publishes its OpenAPI document is decided by the sibling '
234233
+ '`api.enableOpenApi` at the mount, so `enabled: false` turned nothing off. Delete the key; '
235-
+ '`api.enableOpenApi: false` is the switch that leaves the `/openapi.json` document and its `/docs` '
236-
+ 'viewer unmounted.',
234+
+ '`api.enableOpenApi: false` is the switch that leaves the `/openapi.json` document unmounted.',
237235
),
238236
// [#20294] `.optional()`, no longer `.default('ObjectStack API')`: that
239237
// default was materialized into every present block and never served

‎packages/spec/src/migrations/entries/semantic/18.rest-api-config-dead-keys-retired.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const entry: SemanticMigration = {
1717
surface: 'restServer.api.responseFormat / restServer.api.documentation.enabled',
1818
replacement:
1919
'(removed — delete each key; neither had an effect to preserve. Whether the server publishes its '
20-
+ 'OpenAPI document and the docs viewer is `api.enableOpenApi`, the switch the mount already reads. '
20+
+ 'OpenAPI document is `api.enableOpenApi`, the switch the mount already reads. '
2121
+ 'Response shapes are fixed — each route answers in the response schema `@objectstack/spec/api` '
2222
+ 'declares for it — and are not a server-wide option, so there is no replacement for `responseFormat`.)',
2323
reason:
@@ -43,7 +43,7 @@ export const entry: SemanticMigration = {
4343
+ 'the REST plugin\'s `start`) with the retirement prescription, naming the key and '
4444
+ '`RestApiConfigSchema`, instead of being accepted and ignored; `tsc` refuses the key at the '
4545
+ 'authoring site (`never`). A host that meant "serve no OpenAPI document" sets `api.enableOpenApi: '
46-
+ 'false` and sees `GET /openapi.json` and `GET /docs` unmounted. Every client that parses REST '
46+
+ 'false` and sees `GET /openapi.json` unmounted. Every client that parses REST '
4747
+ 'responses reads each route\'s declared response shape. Every LIVE key of the `api` block — '
4848
+ 'including `documentation`\'s other members — parses byte-identically to before, and the mounted '
4949
+ 'REST surface is unchanged: '

0 commit comments

Comments
 (0)