From 46a37e7208f7fb1f9dad60a4c9b70444ab35d777 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 14:17:48 +0000 Subject: [PATCH 1/2] fix(rest): REST refusals, notes and the OpenAPI text state each decision in words instead of a tracker number (stage 2) The 34 ledgered tracker numbers in packages/rest leave runtime strings in form D: where a sentence leaned on the number it now says what was decided; where it already said so, only the citation goes. Text only: no code, status, field, route or control flow moves. - rest-route-ledger.ts: 29 occurrences across 19 notes - import-mapping.ts: the javascript-transform refusal - openapi-builtin-paths.ts: the two OpenAPI description notes - rest-api-plugin.ts: the retired api.requireAuth warning - rest-server.ts: the transactionalBatch capability description The doc-authoring ledger is recomputed with --census-ledger: the rest rows go to zero, no other row moves (584 -> 550 occurrences). Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude --- packages/rest/src/import-mapping.ts | 2 +- packages/rest/src/openapi-builtin-paths.ts | 8 ++--- packages/rest/src/rest-api-plugin.ts | 2 +- packages/rest/src/rest-route-ledger.ts | 38 ++++++++++---------- packages/rest/src/rest-server.ts | 2 +- scripts/doc-authoring-prose-id.baseline.json | 30 ---------------- 6 files changed, 26 insertions(+), 56 deletions(-) diff --git a/packages/rest/src/import-mapping.ts b/packages/rest/src/import-mapping.ts index 0b4387bef7c..5b340f56fc2 100644 --- a/packages/rest/src/import-mapping.ts +++ b/packages/rest/src/import-mapping.ts @@ -90,7 +90,7 @@ export async function resolveNamedMapping( if (entry?.transform === 'javascript') { return { ok: false, status: 400, code: 'UNSUPPORTED_TRANSFORM', - error: `Mapping "${mappingName}" uses transform "javascript", which the import path does not execute (no server-side sandbox; see framework#2611)`, + error: `Mapping "${mappingName}" uses transform "javascript", which the import path does not execute (there is no server-side sandbox), so the import is refused rather than run with that transform skipped`, }; } } diff --git a/packages/rest/src/openapi-builtin-paths.ts b/packages/rest/src/openapi-builtin-paths.ts index c920ddd8f25..0ed4213717d 100644 --- a/packages/rest/src/openapi-builtin-paths.ts +++ b/packages/rest/src/openapi-builtin-paths.ts @@ -149,12 +149,12 @@ const PUBLIC_TAG = 'public'; const BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']); const RESPONSE_NOTE = - 'Response envelope. This section describes the route surface — which method and path exist, ' + - 'and what each is for. Per-route payload schemas are not derived here and are deliberately not ' + - 'invented (#5588).'; + 'Response envelope. This section is built from the routes this server actually mounts — which method ' + + 'and path exist, and what each is for. Per-route payload schemas are not derived here and are ' + + 'deliberately not invented.'; const REQUEST_BODY_NOTE = - 'JSON request body. Its shape is route-specific and is not described by this document (#5588).'; + 'JSON request body. Its shape is route-specific; this document leaves it undescribed rather than invent one.'; /** * Is this wire path served by the document being built for `basePath`? diff --git a/packages/rest/src/rest-api-plugin.ts b/packages/rest/src/rest-api-plugin.ts index d2d87b19acf..bd7913dcb24 100644 --- a/packages/rest/src/rest-api-plugin.ts +++ b/packages/rest/src/rest-api-plugin.ts @@ -527,7 +527,7 @@ export function createRestApiPlugin(config: RestApiPluginConfig = {}): Plugin { if ((config.api as any)?.requireAuth !== undefined || (config.api as any)?.api?.requireAuth !== undefined) { ctx.logger.warn( - '[security] `api.requireAuth` was removed (#3963) and is IGNORED — anonymous access to ' + '[security] `api.requireAuth` was removed and is IGNORED — anonymous access to ' + 'object data is always denied. Publish public surfaces by declaration instead: a public ' + 'form view, a share link, or `book.audience: \'public\'`.', ); diff --git a/packages/rest/src/rest-route-ledger.ts b/packages/rest/src/rest-route-ledger.ts index 815b8f845bc..13924a4418f 100644 --- a/packages/rest/src/rest-route-ledger.ts +++ b/packages/rest/src/rest-route-ledger.ts @@ -175,7 +175,7 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ note: 'bare-base discovery alias; the SDK connects via /api/v1/discovery' }, { route: 'GET /api/v1/discovery', family: 'discovery', source: 'route-manager', disposition: 'sdk', client: 'connect', responseSchema: 'DiscoverySchema', - note: 'duplicate mount with the dispatcher /discovery branch — REST registers first and wins. Answers BARE (res.json(discovery), no envelope), so `DiscoverySchema` names the whole body here while the dispatcher row names its envelope `data`; discovery-schema-conformance.test.ts drives THIS handler through the #5682 double assertion' }, + note: 'duplicate mount with the dispatcher /discovery branch — REST registers first and wins. Answers BARE (res.json(discovery), no envelope), so `DiscoverySchema` names the whole body here while the dispatcher row names its envelope `data`; discovery-schema-conformance.test.ts drives THIS handler through the double assertion: the live body parses against `DiscoverySchema`, and it carries no key the protocol does not declare' }, // ── openapi / docs ──────────────────────────────────────────────────────── { route: 'GET /api/v1/openapi.json', family: 'openapi', source: 'route-manager', disposition: 'server-only', @@ -210,22 +210,22 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ note: 'richer types listing consumed by Studio tooling directly; the SDK reads the same body from GET /meta (meta.getTypes). Mirrors the `GET /meta/types` row in runtime/src/route-ledger.ts' }, { route: 'GET /api/v1/meta/diagnostics', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getDiagnostics', responseSchema: 'GetMetaDiagnosticsResponseSchema', - note: '[#12038] REST-only route; this server answers the payload BARE (`res.json(result)`, no envelope), so the named schema is the whole body on this surface. Describe-only transcription of `getMetaDiagnostics`\'s declared return; conformance: spec `api/protocol.test.ts`' }, + note: 'REST-only route; this server answers the payload BARE (`res.json(result)`, no envelope), so the named schema is the whole body on this surface. Describe-only transcription of `getMetaDiagnostics`\'s declared return; conformance: spec `api/protocol.test.ts`' }, { route: 'GET /api/v1/meta/_drafts', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.listDrafts', responseSchema: 'ListDraftsResponseSchema', - note: '[#12038] on THIS surface the payload is answered BARE (`res.json(result)`); the dispatcher twin (runtime ledger row) answers the same payload through the `{ success, data }` envelope — the named schema is the PAYLOAD, true on both surfaces. Describe-only transcription of `listDrafts`\'s declared return; conformance: spec `api/protocol.test.ts`' }, + note: 'On THIS surface the payload is answered BARE (`res.json(result)`); the dispatcher twin (runtime ledger row) answers the same payload through the `{ success, data }` envelope — the named schema is the PAYLOAD, true on both surfaces. Describe-only transcription of `listDrafts`\'s declared return; conformance: spec `api/protocol.test.ts`' }, // [2026-08-31] SEEDED — same rule as `GET /api/v1/meta` above; this door is // one of the five mutating /meta seams that proof drives by literal path. { route: 'POST /api/v1/meta/_migrate-stored', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.migrateStored', authz: 'anonymous-deny-meta', - note: 'ADR-0087 stored-row canonicalization (#4327); gated on `manage_metadata`, preview unless { apply: true }. DELIBERATELY UNBOUND (#12038 ruling 2C) — this row would name the schema, but the report\'s only named type, `StoredMigrationReport`, lives in `@objectstack/metadata-protocol` (unreachable from the spec/api namespace this field resolves against); a second declaration in spec would drift against the CLI rendering the same report. Answered BARE on this surface, enveloped on the dispatcher twin' }, + note: 'ADR-0087 stored-row canonicalization, the route form of `os migrate meta --stored`: it rewrites stored `sys_metadata` rows in place to their canonical form; gated on `manage_metadata`, preview unless { apply: true }. DELIBERATELY UNBOUND — this row would name the schema, but the report\'s only named type, `StoredMigrationReport`, lives in `@objectstack/metadata-protocol` (unreachable from the spec/api namespace this field resolves against); a second declaration in spec would drift against the CLI rendering the same report. Answered BARE on this surface, enveloped on the dispatcher twin' }, { route: 'GET /api/v1/meta/:type', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getItems' }, { route: 'GET /api/v1/meta/:type/:name/references', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getReferences', responseSchema: 'FindReferencesToMetaResponseSchema', - note: '[#12038] REST-only route; payload answered BARE, so the named schema is the whole body. Describe-only transcription of `findReferencesToMeta`\'s declared return; conformance: spec `api/protocol.test.ts`' }, + note: 'REST-only route; payload answered BARE, so the named schema is the whole body. Describe-only transcription of `findReferencesToMeta`\'s declared return; conformance: spec `api/protocol.test.ts`' }, { route: 'GET /api/v1/meta/book/:name/tree', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getBookTree', responseSchema: 'ResolvedBookSchema', - note: '[#12038] REST-only route; payload answered BARE — `resolveBookTree()`\'s `ResolvedBook`, verbatim. The schema is declared beside its interfaces in spec `system/book.zod.ts` and re-exported into `/api` (ruling 5A); conformance: spec `system/book.test.ts`' }, + note: 'REST-only route; payload answered BARE — `resolveBookTree()`\'s `ResolvedBook`, verbatim. The schema is declared beside its interfaces in spec `system/book.zod.ts` and re-exported into `/api`, never declared there a second time; conformance: spec `system/book.test.ts`' }, // [#5882] The three-layer diagnostic projection, promoted from the // `?layers=true` flag on the row below to a path of its own so that one path // answers one response shape. `responseSchema` is filled because this mount @@ -246,11 +246,11 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ note: 'three-layer diagnostic read (code / overlay / effective) powering the Studio editor comparison tabs; consumed by objectui over plain HTTP, and the SDK expressed no layered read under the `?layers=` spelling either. Answers BARE, so the named schema is the whole body' }, { route: 'GET /api/v1/meta/:type/:name', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getItem', responseSchema: 'GetMetaItemResponseSchema', - note: '[#5950] answers BARE, so the named schema is the whole body. Filled now that meta-item-layered-route.test.ts parses BOTH branches of this mount (cached and uncached) against it — the uncached branch carries the ADR-0010 protection envelope this schema newly declares' }, + note: 'Answers BARE, so the named schema is the whole body. Filled now that meta-item-layered-route.test.ts parses BOTH branches of this mount (cached and uncached) against it — the uncached branch carries the ADR-0010 protection envelope this schema now declares, every key optional because the cached branch never publishes it' }, { route: 'PUT /api/v1/meta/:type/:name', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.saveItem', - note: '[#6603] gated on `manage_metadata` (ADR-0066 D1), same mechanism as POST /meta/_migrate-stored — a session alone is no longer enough. The write-side answer to ADR-0106 D1: a masked read PUT back verbatim used to delete the fields the caller could not see. [#12702] the gate is the shared `metaWriteCapabilityVerdict`: `manage_org_presentation` is also admitted, ONLY for an `allowOrgOverride: true` type written org-scoped to the caller\'s own active organization' }, + note: 'Gated on `manage_metadata` (ADR-0066 D1), same mechanism as POST /meta/_migrate-stored — a session alone is no longer enough. The write-side answer to ADR-0106 D1: a masked read PUT back verbatim used to delete the fields the caller could not see. The gate is the shared `metaWriteCapabilityVerdict`: `manage_org_presentation` is also admitted, ONLY for an `allowOrgOverride: true` type written org-scoped to the caller\'s own active organization, so a tenant org admin authors their own org\'s overlays without platform-wide `manage_metadata`' }, { route: 'DELETE /api/v1/meta/:type/:name', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.deleteItem', - note: 'REST-only: the dispatcher /meta branch has no DELETE handling — it falls into the read path. [#7019] gated on `manage_metadata` (ADR-0066 D1), same mechanism as the PUT twins — but NOT for the ADR-0106 reason: nothing is masked or round-tripped here, this discards a customization overlay outright, and `?dropStorage=true` takes the object table with it. [#12702] same shared verdict as the PUT door: an admitted `manage_org_presentation` reset threads the caller\'s own organization, so the only row it can discard is their own org\'s overlay' }, + note: 'REST-only: the dispatcher /meta branch has no DELETE handling — it falls into the read path. Gated on `manage_metadata` (ADR-0066 D1), same mechanism as the PUT twins — but NOT for the ADR-0106 reason: nothing is masked or round-tripped here, this discards a customization overlay outright, and `?dropStorage=true` takes the object table with it. Same shared verdict as the PUT door: an admitted `manage_org_presentation` reset threads the caller\'s own organization, so the only row it can discard is their own org\'s overlay' }, // The response schema POSTDATES this row: the row was written when the door // had no declaration, and `HistoryMetaItemResponseSchema` was authored later // by the card that declared the history protocol member. That is why this was @@ -264,15 +264,15 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ note: 'REST-only: the dispatcher /meta branch swallows /history as a compound name and 404s. Payload answered BARE, so the named schema is the whole body — a describe-only transcription of `historyMetaItem`\'s declared return. Conformance: the history capture suite in spec `api/protocol.test.ts`, which parses a real two-event body (an update carrying every optional member, and the delete tombstone with `hash: null` and a `null` system actor) and pins the closed `op` vocabulary against the deliberately open `ref.type`' }, { route: 'GET /api/v1/meta/:type/:name/audit', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getAudit', responseSchema: 'AuditMetaItemResponseSchema', - note: '[#12038] REST-only route; payload answered BARE, so the named schema is the whole body. The schema predates this row (#11678, exact field-for-field match of `auditMetaItem`\'s declared return); conformance: the #11678 capture suite in spec `api/protocol.test.ts`' }, + note: 'REST-only route; payload answered BARE, so the named schema is the whole body. The schema predates this row: it joined the spec when `MetadataProtocol` gained its optional `auditMetaItem` member, an exact field-for-field match of that member\'s declared return; conformance: the audit-door capture suite in spec `api/protocol.test.ts`' }, { route: 'POST /api/v1/meta/:type/:name/publish', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.publishItem', - note: 'per-item ADR-0033 publish; packages.publishDrafts remains the package-scoped flow. [#12702] gated by the shared `metaWriteCapabilityVerdict`: `manage_org_presentation` is also admitted for an org-scoped tier-A promotion — the second half of the save→publish loop, promoting only the caller\'s own org partition' }, + note: 'per-item ADR-0033 publish; packages.publishDrafts remains the package-scoped flow. Gated by the shared `metaWriteCapabilityVerdict`: `manage_org_presentation` is also admitted for an org-scoped tier-A promotion — the second half of the save→publish loop, promoting only the caller\'s own org partition' }, { route: 'POST /api/v1/meta/:type/:name/rollback', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.rollbackItem', responseSchema: 'RollbackMetaItemResponseSchema', - note: '[#12702] gated by the shared `metaWriteCapabilityVerdict`: `manage_org_presentation` is also admitted for an org-scoped tier-A rollback, restoring only a version of the caller\'s own org overlay. [#12038] REST-only route; payload answered BARE, so the named schema is the whole body — describe-only transcription of `rollbackMetaItem`\'s declared return; conformance: spec `api/protocol.test.ts`' }, + note: 'Gated by the shared `metaWriteCapabilityVerdict`: `manage_org_presentation` is also admitted for an org-scoped tier-A rollback, restoring only a version of the caller\'s own org overlay. REST-only route; payload answered BARE, so the named schema is the whole body — describe-only transcription of `rollbackMetaItem`\'s declared return; conformance: spec `api/protocol.test.ts`' }, { route: 'GET /api/v1/meta/:type/:name/diff', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.diffItem', responseSchema: 'DiffMetaItemResponseSchema', - note: '[#12038] REST-only route; payload answered BARE, so the named schema is the whole body. Describe-only transcription of `diffMetaItem`\'s declared return; conformance: spec `api/protocol.test.ts`' }, + note: 'REST-only route; payload answered BARE, so the named schema is the whole body. Describe-only transcription of `diffMetaItem`\'s declared return; conformance: spec `api/protocol.test.ts`' }, // [#7526] The two routes that were ledgered in `runtime/src/route-ledger.ts` // and implemented in the dispatcher, but which no registrar ever mounted — // so the SDK guard (#3642) certified them off a DECLARATION while they died @@ -287,10 +287,10 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ // the surviving constraint — a literal-prefixed route above the // `:type`-parameterised route it shares a segment count with. { route: 'GET /api/v1/meta/object/:name/state/:field', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getLegalNextStates', - note: 'ADR-0020 D3.3 legal-next-state introspection. `next: null` = no state_machine governs the field, `next: []` = a declared dead end. #9180 step 2 retired the plural `/api/v1/meta/objects/:name/state/:field` twin that used to carry this `sdk` disposition, and the SDK now spells the segment `object` — the `/meta` type segment is singular, always. The retired twin was a DECLARED registration, not a `META_URL_TO_SINGULAR` fold tolerance (this route matches a literal segment and never consulted the fold), so the boundary accept set is unchanged. ⚠ What the retirement did NOT make universal, because an author reading only this row would assume it did: the legacy dispatcher `/meta` if-chain in `packages/runtime/src/domains/meta.ts` still matches BOTH literals, so the plural is refused HERE and still answered wherever `dispatch()` fronts the request instead of this server. That is deliberate, by the maintainer re-weigh of 2026-08-17 (item 3: no new refusals beyond step 1; the external break deferred with no scheduled window), and it is recorded with its provenance on the dispatcher ledger row plus `runtime/src/domains/meta-state-plural-tolerance.test.ts` (#10179)' }, + note: 'ADR-0020 D3.3 legal-next-state introspection. `next: null` = no state_machine governs the field, `next: []` = a declared dead end. Step 2 of the singular-segment ruling retired the plural `/api/v1/meta/objects/:name/state/:field` twin that used to carry this `sdk` disposition, and the SDK now spells the segment `object` — the `/meta` type segment is singular, always. The retired twin was a DECLARED registration, not a `META_URL_TO_SINGULAR` fold tolerance (this route matches a literal segment and never consulted the fold), so the boundary accept set is unchanged. ⚠ What the retirement did NOT make universal, because an author reading only this row would assume it did: the legacy dispatcher `/meta` if-chain in `packages/runtime/src/domains/meta.ts` still matches BOTH literals, so the plural is refused HERE and still answered wherever `dispatch()` fronts the request instead of this server. That is deliberate, by the maintainer re-weigh of 2026-08-17 (item 3: no new refusals beyond step 1; the external break deferred with no scheduled window), and it is recorded with its provenance on the dispatcher ledger row plus `runtime/src/domains/meta-state-plural-tolerance.test.ts`, which pins both halves as behaviour so this note cannot quietly stop being true' }, { route: 'GET /api/v1/meta/:type/:name/published', family: 'metadata', source: 'route-manager', disposition: 'sdk', client: 'meta.getPublished', responseSchema: 'GetPublishedMetaItemResponseSchema', - note: 'ADR-0033 published snapshot; 404s for a name that does not exist, which the pre-#7526 fall-through into the compound-name route structurally could not do (it answered a protection-envelope stub identical before publish and for a bogus name). [#12038 ruling 1C] the named schema is DELIBERATELY OPAQUE (`z.unknown()`): the route answers an arbitrary metadata item body, BARE on this surface (enveloped on the dispatcher twin) — never a union frozen against the type registry' }, + note: 'ADR-0033 published snapshot; 404s for a name that does not exist, which the fall-through into the compound-name route, before this path had a registration of its own, structurally could not do (it answered a protection-envelope stub identical before publish and for a bogus name). The named schema is DELIBERATELY OPAQUE (`z.unknown()`): the route answers an arbitrary metadata item body, BARE on this surface (enveloped on the dispatcher twin) — never a union frozen against the type registry' }, // [commit 7986d973f] THREE ROWS RETIRED HERE — `GET /api/v1/meta/:type/:section/:name`, // `PUT` on the same path, and `GET …/:section/:name/published`. They were the @@ -310,7 +310,7 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ // ── ui ──────────────────────────────────────────────────────────────────── { route: 'GET /api/v1/ui/view/:object/:type', family: 'ui', source: 'route-manager', disposition: 'sdk', client: 'meta.getView', - note: 'was mismatch — meta.getView spoke the ?type= query dialect this pattern cannot match; since #3611 the client sends the path form both surfaces accept' }, + note: 'was mismatch — meta.getView spoke the ?type= query dialect this pattern cannot match; the client was moved to the path form both surfaces accept, rather than this server registering the query dialect as a second spelling' }, // ── data CRUD ───────────────────────────────────────────────────────────── { route: 'GET /api/v1/data/:object', family: 'crud', source: 'route-manager', disposition: 'sdk', client: 'data.find' }, @@ -359,7 +359,7 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ // (`access-security.capability-declaration-lifecycle`). { route: 'POST /api/v1/data/:object/:id/clone', family: 'data-actions', source: 'route-manager', disposition: 'sdk', client: 'data.clone', responseSchema: 'CloneDataResponseSchema', - note: '[#11924] answers BARE with 201 (res.status(201).json(result), no envelope), so the named schema is the whole body. Filled with its conformance coverage: search-clone-route-schema-conformance.test.ts drives this mount, and metadata-protocol\'s search-clone-schema-conformance.test.ts parses the real cloneData producer' }, + note: 'Answers BARE with 201 (res.status(201).json(result), no envelope), so the named schema is the whole body. Filled with its conformance coverage: search-clone-route-schema-conformance.test.ts drives this mount, and metadata-protocol\'s search-clone-schema-conformance.test.ts parses the real cloneData producer' }, { route: 'POST /api/v1/data/:object/import', family: 'data-actions', source: 'route-manager', disposition: 'sdk', client: 'data.import' }, { route: 'POST /api/v1/data/:object/import/jobs', family: 'data-actions', source: 'route-manager', disposition: 'sdk', client: 'data.createImportJob' }, { route: 'POST /api/v1/data/import/jobs/:jobId/cancel', family: 'data-actions', source: 'route-manager', disposition: 'sdk', client: 'data.cancelImportJob' }, @@ -376,7 +376,7 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ // (doc-authoring rule: tracker ids stay out of runtime string prose). { route: 'GET /api/v1/search', family: 'search', source: 'route-manager', disposition: 'sdk', client: 'search', responseSchema: 'SearchAllResponseSchema', - note: '[#11924] answers BARE (res.json(result), no envelope), so the named schema is the whole body. ⚠️ NOT `SearchResult` — that exported contract types the per-object ISearchService.search (hits of score/document), the #8140 near-miss trap. Filled with its conformance coverage: search-clone-route-schema-conformance.test.ts drives this mount, and metadata-protocol\'s search-clone-schema-conformance.test.ts parses the real searchAll producer. The body additionally carries `pages` — published-page hits swept by the same producer through the caller\'s own meta read verb; still this one named schema, no handler change (bare relay)' }, + note: 'Answers BARE (res.json(result), no envelope), so the named schema is the whole body. ⚠️ NOT `SearchResult` — that exported contract types the per-object ISearchService.search (hits of score/document), a near miss that would compile here and be false. Filled with its conformance coverage: search-clone-route-schema-conformance.test.ts drives this mount, and metadata-protocol\'s search-clone-schema-conformance.test.ts parses the real searchAll producer. The body additionally carries `pages` — published-page hits swept by the same producer through the caller\'s own meta read verb; still this one named schema, no handler change (bare relay)' }, // ── email ───────────────────────────────────────────────────────────────── { route: 'POST /api/v1/email/send', family: 'email', source: 'route-manager', disposition: 'sdk', client: 'email.send' }, @@ -459,7 +459,7 @@ export const REST_ROUTE_LEDGER: readonly RestRouteLedgerEntry[] = [ // `GET /packages/:id` and `DELETE /packages/:id` — their rows live in // `packages/runtime/src/route-ledger.ts`. { route: 'POST /api/v1/packages/publish', family: 'packages', source: 'direct-mount', disposition: 'server-only', - note: 'marketplace registry publish ({manifest, metadata}) — publisher tooling, not app-SDK surface. Moved off the bare POST /packages in #3610: that verb+path is the dispatcher install route, and REST registering it first swallowed every packages.install call with a 400. Mounted UNCONDITIONALLY since #7563 — it has no dispatcher twin, so while it was service-gated the path was absorbed by /packages/:id and answered 405 with THAT route\'s Allow set; it now resolves the `package` service per request and answers an honest 404 on a deployment that composes none.' }, + note: 'marketplace registry publish ({manifest, metadata}) — publisher tooling, not app-SDK surface. Moved off the bare POST /packages to this path: that verb+path is the dispatcher install route, and REST registering it first swallowed every packages.install call with a 400. Mounted UNCONDITIONALLY — it has no dispatcher twin, so while it was service-gated the path was absorbed by /packages/:id and answered 405 with THAT route\'s Allow set; it now resolves the `package` service per request and answers an honest 404 on a deployment that composes none.' }, // ── external datasource federation (ADR-0015 §6.2, direct-mount) ────────── { route: 'GET /api/v1/datasources/:name/external/tables', family: 'external-datasource', source: 'direct-mount', disposition: 'sdk', client: 'datasources.external.listTables' }, diff --git a/packages/rest/src/rest-server.ts b/packages/rest/src/rest-server.ts index c673773e469..4b9a203476c 100644 --- a/packages/rest/src/rest-server.ts +++ b/packages/rest/src/rest-server.ts @@ -4827,7 +4827,7 @@ export class RestServer { description: 'Atomic cross-object batch endpoint (POST {basePath}/batch): all-or-nothing ' + 'create/update/delete across objects in one transaction, with intra-batch ' - + '{ $ref: } parent references (#1604 / ADR-0034).', + + '{ $ref: } parent references (ADR-0034).', }; // [#7541] Global search — the same two-layer AND, for the diff --git a/scripts/doc-authoring-prose-id.baseline.json b/scripts/doc-authoring-prose-id.baseline.json index 79de32a075a..e6f625b16c3 100644 --- a/scripts/doc-authoring-prose-id.baseline.json +++ b/scripts/doc-authoring-prose-id.baseline.json @@ -437,36 +437,6 @@ "packages/qa/downstream-contract/src/stack.ts": { "#2035": 1 }, - "packages/rest/src/import-mapping.ts": { - "#2611": 1 - }, - "packages/rest/src/openapi-builtin-paths.ts": { - "#5588": 2 - }, - "packages/rest/src/rest-api-plugin.ts": { - "#3963": 1 - }, - "packages/rest/src/rest-route-ledger.ts": { - "#10179": 1, - "#11678": 2, - "#11924": 2, - "#12038": 9, - "#12702": 4, - "#3610": 1, - "#3611": 1, - "#4327": 1, - "#5682": 1, - "#5950": 1, - "#6603": 1, - "#7019": 1, - "#7526": 1, - "#7563": 1, - "#8140": 1, - "#9180": 1 - }, - "packages/rest/src/rest-server.ts": { - "#1604": 1 - }, "packages/runtime/src/action-execution.ts": { "#11519": 1, "#5933": 1 From ee1a101133d439ea8345bbe50c3b468055a1b6e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 14:30:25 +0000 Subject: [PATCH 2/2] chore(changeset): @objectstack/rest patch for the stage-2 string rewrites The rewritten refusal, warning and OpenAPI description strings ship in the package's dist; the route-ledger notes do not (package-internal). Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude --- .../20752-rest-strings-state-the-decision.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .changeset/20752-rest-strings-state-the-decision.md diff --git a/.changeset/20752-rest-strings-state-the-decision.md b/.changeset/20752-rest-strings-state-the-decision.md new file mode 100644 index 00000000000..56ccced32f6 --- /dev/null +++ b/.changeset/20752-rest-strings-state-the-decision.md @@ -0,0 +1,16 @@ +--- +'@objectstack/rest': patch +--- + +REST refusals, warnings and the served OpenAPI text no longer cite tracker numbers; each one states the decision behind it in words + +Clause-②: no + +A few strings `@objectstack/rest` sends to callers and operators pointed at an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does. + +- `POST /data/:object/import` with a named mapping that declares a `javascript` transform: the `UNSUPPORTED_TRANSFORM` message now says the import path does not execute it (there is no server-side sandbox), so the import is refused rather than run with that transform skipped. +- `GET /openapi.json`: the built-in section's response description says the section is built from the routes this server actually mounts and that payload schemas are deliberately not invented; the request-body description says the document leaves the route-specific shape undescribed rather than invent one. +- The boot warning for a config that still sets the retired `api.requireAuth` drops its citation; it already says the key was removed and anonymous access to object data is always denied. +- `/discovery`'s `capabilities.transactionalBatch.description` cites ADR-0034 alone. + +Text only: no status, error code, field, route or control flow moves. A client that matches the old message text (for example the tracker-number suffix the `UNSUPPORTED_TRANSFORM` message used to end with) needs the new spelling.