|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +"@objectstack/rest": minor |
| 4 | +--- |
| 5 | + |
| 6 | +feat(spec): retire the ten inert `RestServerConfig` keys the liveness ledger recorded as `dead` (#14691, ADR-0049 enforce-or-remove) |
| 7 | + |
| 8 | +<!-- adr-0087: registered rest-server-config-dead-keys-retired --> |
| 9 | + |
| 10 | +**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep |
| 11 | +launch-window convention ships it as `minor`; the prescription is registered |
| 12 | +under protocol major 18, where `os migrate meta` users will look). |
| 13 | + |
| 14 | +#14369 enrolled the four `RestServerConfig` sub-objects (`crud`, `metadata`, |
| 15 | +`batch`, `routes`) in the spec liveness ledger and found 15 of their 32 rows |
| 16 | +`dead`: parsed, defaulted and normalized into the REST server's config by |
| 17 | +`normalizeConfig` (#11984) and never read back. This change is the |
| 18 | +enforce-or-remove call on every one of them, taken per family, and every |
| 19 | +family resolved to REMOVE — each promised capability either already exists at |
| 20 | +its proper seat or would contradict a fixed contract. The closed-set cloud |
| 21 | +sweep (#14796, `objectstack-ai/cloud` @ `9b6abe0f2fd5`) returned zero hits, |
| 22 | +structurally: cloud never authors a `RestServerConfig`. |
| 23 | + |
| 24 | +**What is refused:** authoring any of the ten keys below. Each is a |
| 25 | +`retiredKey()` tombstone (all four sub-schemas are non-strict `z.object()`s, |
| 26 | +so a plain deletion would have silently stripped the key), so authoring it is |
| 27 | +a `tsc` error and a parse error carrying the prescription — and, because |
| 28 | +`RestServer` parses these sub-objects at construction (#11984), |
| 29 | +`new RestServer(...)` / `createRestApiPlugin().start()` now refuse a config |
| 30 | +that carries one, naming the sub-object, the key and the declaring schema. |
| 31 | + |
| 32 | +**FROM → TO** (delete the key in every case; none ever had an effect to preserve): |
| 33 | + |
| 34 | +- `crud.patterns` → the mounted CRUD paths are the contract the client SDK, the |
| 35 | + discovery document and the served `/openapi.json` all describe; `crud.dataPrefix` |
| 36 | + moves them deployment-wide. An endpoint on a custom path or method is a |
| 37 | + declarative `api` endpoint (`type: 'object_operation'`). Its value schema |
| 38 | + `CrudEndpointPatternSchema` / type `CrudEndpointPattern` are removed with it |
| 39 | + (no other consumer; `CrudOperation` stays — `GeneratedEndpoint.operation` reads it). |
| 40 | +- `crud.objectParamStyle` → the object name is always a path segment. |
| 41 | +- `metadata.cacheTtl` → `metadata.enableCache` is the live switch (it selects the |
| 42 | + protocol's `getMetaItemCached` path, which takes no TTL); a declarative `api` |
| 43 | + endpoint's `cacheTtl` is the key that reaches the wire. The unbounded negative |
| 44 | + TTL this key accepted goes with it. |
| 45 | +- `metadata.endpoints.schema` → gated `GET /meta/:type/:name/schema`, which does |
| 46 | + not exist; `endpoints.types` / `items` / `item` gate real mounts and stay. |
| 47 | +- `batch.operations.upsertMany` → gated `POST /data/:object/upsertMany`, which |
| 48 | + was never built; upsert is an operation type of the generic |
| 49 | + `POST /data/:object/batch` endpoint (`BatchOperationType` `'upsert'`), gated |
| 50 | + by `batch.enableBatchEndpoint`. |
| 51 | +- `batch.defaultAtomic` → atomicity is the per-request `options.atomic` |
| 52 | + (ADR-0119 D4, opt-in); a server-side default that flipped it silently would |
| 53 | + change the failure semantics of callers who send nothing, which that ADR |
| 54 | + refused. Callers that need all-or-nothing send `options: { atomic: true }`. |
| 55 | +- `routes.includeObjects` / `routes.excludeObjects` / `routes.overrides` |
| 56 | + (`enabled` / `basePath` / `operations`) → per-object API exposure is declared |
| 57 | + ON the object and enforced by the REST data surface: `enable.apiEnabled: false` |
| 58 | + hides it (404), `enable.apiMethods` whitelists its operations (405). The data |
| 59 | + base path is deployment-wide (`crud.dataPrefix`). |
| 60 | +- `routes.nameTransform` → the object `name` is the canonical id on every |
| 61 | + surface, the REST path segment included; there is no URL transform to configure. |
| 62 | + |
| 63 | +**What stays, byte-identical:** every live key of the four sub-objects — |
| 64 | +`crud.operations.*`, `crud.dataPrefix`, `metadata.prefix` / `enableCache` / |
| 65 | +`maskObjectFields` / `endpoints.types|items|item`, `batch.maxBatchSize` / |
| 66 | +`enableBatchEndpoint` / `operations.createMany|updateMany|deleteMany` — with |
| 67 | +its default and its mount. The mounted REST surface does not change: none of |
| 68 | +the ten keys ever reached it. `@objectstack/rest`'s normalized config no longer |
| 69 | +carries the retired keys (they were written and never read), and the #11984 |
| 70 | +pins of their accept/reject behaviour are reversed to refusal pins, by design. |
| 71 | + |
| 72 | +The retirement kit: `retiredKey()` tombstones on the four sub-schemas; |
| 73 | +`RestServerConfigSchema`'s `@example` no longer advertises |
| 74 | +`routes: { excludeObjects: [...] }`; ledger rows kept `dead` with a REMOVED |
| 75 | +note and `evidenceScope: cross-repo` (the two container rows collapse into one |
| 76 | +each, since their child keys left the walked shape); `RETIRED_KEYS_BY_MAJOR[18]` |
| 77 | +× 10 and `RETIRED_DEFS_BY_MAJOR[18]` `api/CrudEndpointPattern`; D3 semantic |
| 78 | +entry `rest-server-config-dead-keys-retired`. No D2 conversion: a |
| 79 | +`RestServerConfig` is plugin TS configuration (REST plugin constructor / |
| 80 | +`plugin-hono-server` `restConfig`), never a stack collection member or a |
| 81 | +`sys_metadata` row (the `openApi31` precedent, #4579), so there is no source |
| 82 | +for the chain to rewrite and the prescription carries no `os migrate meta` |
| 83 | +sentence. |
0 commit comments