diff --git a/.changeset/spec-error-callback-refusal-ids.md b/.changeset/spec-error-callback-refusal-ids.md new file mode 100644 index 0000000000..a23d2fbc8e --- /dev/null +++ b/.changeset/spec-error-callback-refusal-ids.md @@ -0,0 +1,38 @@ +--- +"@objectstack/spec": patch +--- + +Strip internal issue-tracker ids from refusal prose built inside functions + +The fourth customer-facing refusal population in `packages/spec`: prose a +FUNCTION returns rather than prose written at a recognised position — a zod +`error: (issue) => …` map, a hoisted `const X = (key) => '…'` message builder +referenced from `message:` or `retiredKey(X(…))`, a `$ZodErrorMap` const the +`error` callback dispatches to, a `(v): StrictObjectOptions => ({ history })` +options factory. It reaches exactly the same reader at exactly the same moment +as the three populations already stripped: the author whose metadata was just +rejected. That reader has no tracker, so `#NNNN` was a citation-shaped token +resolving to nothing in the one sentence that most needs to be actionable. + +47 literals carrying 55 tracker ids across 13 sources. + +| how the prose reaches the author | literals | ids | files | +|---|---:|---:|---:| +| built INSIDE a function in a recognised position | 28 | 32 | 8 | +| hoisted into a const an `error:` callback dispatches to | 19 | 23 | 8 | + +**Kept, deliberately:** ADR ids, protocol and package versions, error codes +(`400 INVALID_FIELD` traces the runtime twin far better than the id beside it) +and the `os migrate meta --from ` commands — the anchors a customer can +actually resolve. Where an id was the whole parenthetical, the parenthetical +went with it; where it was load-bearing for an internal reader, it moved to an +adjacent `//` comment. + +`check-doc-authoring` Rule 3 could not see this population at all: its climb +returned `undefined` at `ArrowFunction` / `ReturnStatement`, so the gate printed +`0 violations` over four populated buckets while a fifth sat outside every one +of them. The rule now crosses a function boundary — but only when the FUNCTION +ITSELF sits in a recognised customer-facing position, never unconditionally +through arbitrary function bodies, which would report values as prose. The new +population is its own `functionBuilt` bucket so it carries its own blindness +floor, since an unrecognised spelling produces no flag silently. diff --git a/content/docs/references/data/driver-nosql.mdx b/content/docs/references/data/driver-nosql.mdx index 56179e1bd9..8f7ba998db 100644 --- a/content/docs/references/data/driver-nosql.mdx +++ b/content/docs/references/data/driver-nosql.mdx @@ -165,37 +165,37 @@ const result = AggregationPipelineSchema.parse(data); | **queryDateGranularity** | `Record` | optional | Per-granularity native date bucketing (day/week/month/quarter/year). Missing keys fall back to in-memory bucketing. | | **autonumber** | `boolean` | optional | Driver natively generates persistent autonumber/sequence values | | **batchSchemaSync** | `boolean` | optional | Supports batched schema sync to reduce schema DDL round-trips (absence = false) | -| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | -| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | -| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | -| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | -| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | -| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | -| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | -| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | -| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | -| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | -| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | -| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | -| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | +| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | +| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | +| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | +| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | +| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | +| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | +| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | +| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | +| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | +| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | +| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | +| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | +| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | | **streaming** | `never` | optional | [REMOVED] `DriverCapabilities.streaming` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, and `findStream`, the only read this bit could describe, was itself removed in 17.0.0: nothing ever called it, and two of its three implementations materialised the entire result set before yielding. The bit carried the same defect one level up (`SqlDriver` implemented `findStream` yet declared `streaming: false`; `InMemoryDriver` declared `true` over a full-table read) — which is what zero readers makes inevitable. Page large reads through `find()` with `limit`/`offset`. Delete the key. | -| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | -| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | -| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | -| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | -| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | -| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | -| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | -| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | -| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | +| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | +| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | +| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | +| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | +| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | +| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | +| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | +| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | +| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | ### Nested Shape: `NoSQLDriverConfig.poolConfig` diff --git a/content/docs/references/data/driver-sql.mdx b/content/docs/references/data/driver-sql.mdx index 0b750d12f0..95a938b05c 100644 --- a/content/docs/references/data/driver-sql.mdx +++ b/content/docs/references/data/driver-sql.mdx @@ -79,37 +79,37 @@ const result = DataTypeMappingSchema.parse(data); | **queryDateGranularity** | `Record` | optional | Per-granularity native date bucketing (day/week/month/quarter/year). Missing keys fall back to in-memory bucketing. | | **autonumber** | `boolean` | optional | Driver natively generates persistent autonumber/sequence values | | **batchSchemaSync** | `boolean` | optional | Supports batched schema sync to reduce schema DDL round-trips (absence = false) | -| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | -| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | -| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | -| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | -| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | -| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | -| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | -| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | -| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | -| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | -| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | -| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | -| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | +| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | +| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | +| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | +| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | +| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | +| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | +| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | +| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | +| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | +| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | +| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | +| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | +| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | | **streaming** | `never` | optional | [REMOVED] `DriverCapabilities.streaming` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, and `findStream`, the only read this bit could describe, was itself removed in 17.0.0: nothing ever called it, and two of its three implementations materialised the entire result set before yielding. The bit carried the same defect one level up (`SqlDriver` implemented `findStream` yet declared `streaming: false`; `InMemoryDriver` declared `true` over a full-table read) — which is what zero readers makes inevitable. Page large reads through `find()` with `limit`/`offset`. Delete the key. | -| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | -| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | -| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | -| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | -| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | -| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | -| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | -| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | -| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | +| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | +| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | +| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | +| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | +| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | +| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | +| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | +| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | +| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | ### Nested Shape: `SQLDriverConfig.poolConfig` diff --git a/content/docs/references/data/driver.mdx b/content/docs/references/data/driver.mdx index 1c2db2862d..bef63bfd7c 100644 --- a/content/docs/references/data/driver.mdx +++ b/content/docs/references/data/driver.mdx @@ -33,37 +33,37 @@ const result = DriverCapabilitiesSchema.parse(data); | **queryDateGranularity** | `Record` | optional | Per-granularity native date bucketing (day/week/month/quarter/year). Missing keys fall back to in-memory bucketing. | | **autonumber** | `boolean` | optional | Driver natively generates persistent autonumber/sequence values | | **batchSchemaSync** | `boolean` | optional | Supports batched schema sync to reduce schema DDL round-trips (absence = false) | -| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | -| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | -| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | -| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | -| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | -| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | -| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | -| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | -| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | -| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | -| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | -| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | -| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | +| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | +| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | +| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | +| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | +| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | +| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | +| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | +| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | +| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | +| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | +| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | +| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | +| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | | **streaming** | `never` | optional | [REMOVED] `DriverCapabilities.streaming` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, and `findStream`, the only read this bit could describe, was itself removed in 17.0.0: nothing ever called it, and two of its three implementations materialised the entire result set before yielding. The bit carried the same defect one level up (`SqlDriver` implemented `findStream` yet declared `streaming: false`; `InMemoryDriver` declared `true` over a full-table read) — which is what zero readers makes inevitable. Page large reads through `find()` with `limit`/`offset`. Delete the key. | -| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | -| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | -| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | -| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | -| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | -| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | -| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | -| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | -| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | +| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | +| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | +| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | +| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | +| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | +| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | +| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | +| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | +| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | --- @@ -87,37 +87,37 @@ const result = DriverCapabilitiesSchema.parse(data); | **queryDateGranularity** | `Record` | optional | Per-granularity native date bucketing (day/week/month/quarter/year). Missing keys fall back to in-memory bucketing. | | **autonumber** | `boolean` | optional | Driver natively generates persistent autonumber/sequence values | | **batchSchemaSync** | `boolean` | optional | Supports batched schema sync to reduce schema DDL round-trips (absence = false) | -| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | -| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | -| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | -| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | -| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | -| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | -| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | -| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | -| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | -| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | -| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | -| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | -| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | -| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | -| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | -| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | +| **create** | `never` | optional | [REMOVED] `DriverCapabilities.create` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `create`/`find`/`findOne`/`update`/`delete` are REQUIRED `IDataDriver` methods and the engine calls them unconditionally. Delete the key. | +| **read** | `never` | optional | [REMOVED] `DriverCapabilities.read` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: reads go through the REQUIRED `find`/`findOne`/`count` methods, called unconditionally. Delete the key. | +| **update** | `never` | optional | [REMOVED] `DriverCapabilities.update` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `update`/`upsert` are REQUIRED `IDataDriver` methods, called unconditionally. Delete the key. | +| **delete** | `never` | optional | [REMOVED] `DriverCapabilities.delete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. CRUD is not optional for a driver: `delete` is a REQUIRED `IDataDriver` method, called unconditionally. Delete the key. | +| **bulkCreate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkCreate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods (`bulkCreate`/`bulkUpdate`/`bulkDelete`) are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkUpdate** | `never` | optional | [REMOVED] `DriverCapabilities.bulkUpdate` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **bulkDelete** | `never` | optional | [REMOVED] `DriverCapabilities.bulkDelete` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. The bulk methods are REQUIRED `IDataDriver` methods and the engine calls them directly; wire-level batch capability is advertised by REST discovery from the live composition (#3298), never from this record. Delete the key. | +| **transactions** | `never` | optional | [REMOVED] `DriverCapabilities.transactions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Transaction use is gated on METHOD PRESENCE — `driver.beginTransaction` (`engine.transaction()`, ADR-0034 ambient transactions): a driver without the method gets the non-transactional fallback, whatever this bit claimed. Discovery's `transactionalBatch` capability is likewise derived from `engine.transaction` plus the mounted batch route, never from this bit. Delete the key. | +| **savepoints** | `never` | optional | [REMOVED] `DriverCapabilities.savepoints` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No savepoint code path exists in the engine — a capability bit for a feature the platform does not call is a false affordance, not documentation. Delete the key. | +| **isolationLevels** | `never` | optional | [REMOVED] `DriverCapabilities.isolationLevels` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Isolation is requested per transaction via `beginTransaction({ isolationLevel })`; no planner ever consulted this list to decide anything. Delete the key. | +| **queryFilters** | `never` | optional | [REMOVED] `DriverCapabilities.queryFilters` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST (`where`/`orderBy`/`limit`/`offset`) and MUST execute all of it — the "ObjectQL will filter in memory" fallback this bit's description promised was never built. Delete the key. | +| **querySorting** | `never` | optional | [REMOVED] `DriverCapabilities.querySorting` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will sort in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryPagination** | `never` | optional | [REMOVED] `DriverCapabilities.queryPagination` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `find()` receives the full QueryAST and MUST execute all of it — the "ObjectQL will paginate in memory" fallback this bit's description promised was never built. Delete the key. | +| **queryAggregations** | `never` | optional | [REMOVED] `DriverCapabilities.queryAggregations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Aggregate pushdown is decided by `typeof driver.aggregate === 'function'` plus `queryDateGranularity` (engine aggregate dispatch) — never by this bit. Delete the key. | +| **queryWindowFunctions** | `never` | optional | [REMOVED] `DriverCapabilities.queryWindowFunctions` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans window functions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **querySubqueries** | `never` | optional | [REMOVED] `DriverCapabilities.querySubqueries` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans subqueries through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **queryCTE** | `never` | optional | [REMOVED] `DriverCapabilities.queryCTE` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. ObjectQL never plans Common Table Expressions through a driver, so there was nothing for the bit to switch on. Delete the key. | +| **joins** | `never` | optional | [REMOVED] `DriverCapabilities.joins` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Related data is resolved by the engine (lookup expansion over `find()`), not by driver-side JOIN planning — no code consulted the bit. Delete the key. | +| **fullTextSearch** | `never` | optional | [REMOVED] `DriverCapabilities.fullTextSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. `$search` is compiled by the engine into an `$or` of `$contains` predicates over the searchable fields (ADR-0061) and removed from the AST before the driver sees it — no driver-side full-text path exists. Delete the key. | +| **jsonQuery** | `never` | optional | [REMOVED] `DriverCapabilities.jsonQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No engine path ever branched on driver-side JSON querying. Delete the key. | +| **geospatialQuery** | `never` | optional | [REMOVED] `DriverCapabilities.geospatialQuery` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No geospatial query path exists in the platform — declaring the bit advertised a capability nothing delivers. Delete the key. | | **streaming** | `never` | optional | [REMOVED] `DriverCapabilities.streaming` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, and `findStream`, the only read this bit could describe, was itself removed in 17.0.0: nothing ever called it, and two of its three implementations materialised the entire result set before yielding. The bit carried the same defect one level up (`SqlDriver` implemented `findStream` yet declared `streaming: false`; `InMemoryDriver` declared `true` over a full-table read) — which is what zero readers makes inevitable. Page large reads through `find()` with `limit`/`offset`. Delete the key. | -| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | -| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | -| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | -| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | -| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | -| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | -| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | -| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | -| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | +| **jsonFields** | `never` | optional | [REMOVED] `DriverCapabilities.jsonFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself (e.g. `SqlDriver`'s per-object JSON/date column tracking); no engine path consulted the bit. Delete the key. | +| **arrayFields** | `never` | optional | [REMOVED] `DriverCapabilities.arrayFields` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Field-type handling is negotiated per object at `syncSchema` time by the driver itself; no engine path consulted the bit. Delete the key. | +| **vectorSearch** | `never` | optional | [REMOVED] `DriverCapabilities.vectorSearch` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No vector read path routes through `IDataDriver`. When one exists it should arrive WITH its caller and its capability bit together (the honest order under enforce-or-remove), not as a dangling boolean. Delete the key. | +| **schemaSync** | `never` | optional | [REMOVED] `DriverCapabilities.schemaSync` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Schema sync is gated on METHOD PRESENCE — `typeof driver.syncSchema === 'function'` (engine and ObjectQL plugin init). Delete the key. | +| **migrations** | `never` | optional | [REMOVED] `DriverCapabilities.migrations` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No migration engine ever consulted it. Delete the key. | +| **indexes** | `never` | optional | [REMOVED] `DriverCapabilities.indexes` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Declared indexes are materialised by the driver itself during schema sync (`SqlDriver.syncDeclaredIndexes`); no engine path consulted the bit. Delete the key. | +| **connectionPooling** | `never` | optional | [REMOVED] `DriverCapabilities.connectionPooling` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Pooling is configured via `poolConfig` and owned by the driver; `getPoolStats` is duck-typed where monitoring wants it. Nothing consulted the bit. Delete the key. | +| **preparedStatements** | `never` | optional | [REMOVED] `DriverCapabilities.preparedStatements` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. Parameterised execution is an implementation detail of the driver (`execute(command, parameters)`); nothing consulted the bit. Delete the key. | +| **queryCache** | `never` | optional | [REMOVED] `DriverCapabilities.queryCache` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — no code in any repository ever read it, so its value never changed which code path ran. No query-cache layer keyed off it exists; `DriverOptions.skipCache` is a per-call hint to the driver, not a switch on this bit. Delete the key. | ### Nested Shape: `DriverConfig.poolConfig` diff --git a/content/docs/references/ui/component.mdx b/content/docs/references/ui/component.mdx index 1832baa7e3..9149aa32b7 100644 --- a/content/docs/references/ui/component.mdx +++ b/content/docs/references/ui/component.mdx @@ -95,12 +95,12 @@ const result = AIChatWindowProps.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **object** | `never` | optional | [REMOVED] `element:filter` property `object` was removed in @objectstack/spec 17 (#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **fields** | `never` | optional | [REMOVED] `element:filter` property `fields` was removed in @objectstack/spec 17 (#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **targetVariable** | `never` | optional | [REMOVED] `element:filter` property `targetVariable` was removed in @objectstack/spec 17 (#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **layout** | `never` | optional | [REMOVED] `element:filter` property `layout` was removed in @objectstack/spec 17 (#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **showSearch** | `never` | optional | [REMOVED] `element:filter` property `showSearch` was removed in @objectstack/spec 17 (#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **aria** | `never` | optional | [REMOVED] `element:filter` property `aria` was removed in @objectstack/spec 17 (#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **object** | `never` | optional | [REMOVED] `element:filter` property `object` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **fields** | `never` | optional | [REMOVED] `element:filter` property `fields` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **targetVariable** | `never` | optional | [REMOVED] `element:filter` property `targetVariable` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **layout** | `never` | optional | [REMOVED] `element:filter` property `layout` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **showSearch** | `never` | optional | [REMOVED] `element:filter` property `showSearch` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **aria** | `never` | optional | [REMOVED] `element:filter` property `aria` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:filter` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion), so every key on this element was a capability claim nothing kept. Delete the `element:filter` component; list surfaces own their filtering — use a view's `userFilters` quick-filter bar or the list toolbar's filter builder. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | --- @@ -111,12 +111,12 @@ const result = AIChatWindowProps.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **object** | `never` | optional | [REMOVED] `element:form` property `object` was removed in @objectstack/spec 17 (#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead (#7751) — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **fields** | `never` | optional | [REMOVED] `element:form` property `fields` was removed in @objectstack/spec 17 (#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead (#7751) — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **mode** | `never` | optional | [REMOVED] `element:form` property `mode` was removed in @objectstack/spec 17 (#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead (#7751) — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **submitLabel** | `never` | optional | [REMOVED] `element:form` property `submitLabel` was removed in @objectstack/spec 17 (#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead (#7751) — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **onSubmit** | `never` | optional | [REMOVED] `element:form` property `onSubmit` was removed in @objectstack/spec 17 (#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead (#7751) — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | -| **aria** | `never` | optional | [REMOVED] `element:form` property `aria` was removed in @objectstack/spec 17 (#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead (#7751) — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **object** | `never` | optional | [REMOVED] `element:form` property `object` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **fields** | `never` | optional | [REMOVED] `element:form` property `fields` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **mode** | `never` | optional | [REMOVED] `element:form` property `mode` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **submitLabel** | `never` | optional | [REMOVED] `element:form` property `submitLabel` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **onSubmit** | `never` | optional | [REMOVED] `element:form` property `onSubmit` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | +| **aria** | `never` | optional | [REMOVED] `element:form` property `aria` was removed in @objectstack/spec 17 (ADR-0049) — the whole `element:form` element is retired: no renderer for it ever shipped in objectui, framework or cloud (Studio's designer palette lists it as a no-renderer exclusion — "use the object-bound `object-form` block"), so every key on this element was a capability claim nothing kept. Delete the `element:form` component and use the object-bound `object-form` block instead — it is rendered, designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, `submitText`). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. | --- diff --git a/content/docs/references/ui/dashboard.mdx b/content/docs/references/ui/dashboard.mdx index 127a18b064..551b18b503 100644 --- a/content/docs/references/ui/dashboard.mdx +++ b/content/docs/references/ui/dashboard.mdx @@ -70,9 +70,9 @@ const result = DashboardSchema.parse(data); | **colorVariant** | `Enum<'default' \| 'blue' \| 'teal' \| 'orange' \| 'purple' \| 'success' \| 'warning' \| 'danger'>` | optional | Widget color variant for theming | | **requiresObject** | `string` | optional | Hide the widget unless the named object is registered | | **requiresService** | `string` | optional | Hide the widget unless the named kernel service is registered | -| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | -| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | -| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | +| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | +| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | +| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | | **filter** | `any` | optional | Presentation-scope filter (runtimeFilter) | | **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'>; dimension?: string }` | optional | Period-over-period comparison window (`{ kind, dimension? }`) | | **dataset** | `string` | ✅ | Dataset name to bind (ADR-0021) | @@ -175,9 +175,9 @@ Dashboard header action | **colorVariant** | `Enum<'default' \| 'blue' \| 'teal' \| 'orange' \| 'purple' \| 'success' \| 'warning' \| 'danger'>` | optional | Widget color variant for theming | | **requiresObject** | `string` | optional | Hide the widget unless the named object is registered | | **requiresService** | `string` | optional | Hide the widget unless the named kernel service is registered | -| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | -| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | -| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | +| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | +| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | +| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. | | **filter** | `any` | optional | Presentation-scope filter (runtimeFilter) | | **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'>; dimension?: string }` | optional | Period-over-period comparison window (`{ kind, dimension? }`) | | **dataset** | `string` | ✅ | Dataset name to bind (ADR-0021) | diff --git a/packages/services/service-analytics/src/__tests__/dataset-compiler.test.ts b/packages/services/service-analytics/src/__tests__/dataset-compiler.test.ts index c57b0f35f4..a33556a3b4 100644 --- a/packages/services/service-analytics/src/__tests__/dataset-compiler.test.ts +++ b/packages/services/service-analytics/src/__tests__/dataset-compiler.test.ts @@ -105,14 +105,27 @@ describe('compileDataset', () => { // carries a prescription instead of naming the supported list. Re-pointed // rather than deleted: the dataset measure is one of the retirement's two // authoring surfaces, and this is where that surface is exercised. + // + // ⚠️ Pinned on the CUSTOMER-RESOLVABLE anchors the prescription carries — + // the package version and the ADR — never on a tracker id. The id used to be + // in this message and is not any more: `check:doc-authoring` Rule 3 bans an + // internal `#NNNN` from prose printed at a refused author, and a consumer-side + // regex spelling one is a pin on the thing that must not be there. it('rejects a retired aggregate at the schema, with the retirement prescription', () => { - expect(() => DatasetSchema.parse({ + const parse = () => DatasetSchema.parse({ name: 'agg', label: 'Agg', object: 'opportunity', dimensions: [], measures: [{ name: 'tags', aggregate: 'array_agg', field: 'tag' }], - })).toThrowError(/`array_agg`.*was removed.*#6188/s); + }); + expect(parse).toThrowError(/`array_agg`.*was removed.*ADR-0049 enforce-or-remove/s); + // It is a PRESCRIPTION, not a bare refusal: it must still say what to do. + expect(parse).toThrowError(/Delete the aggregation/s); + // ...and it must carry no tracker id, in either the bare or repo-qualified + // spelling. Negative pin, so a re-introduced id reds HERE too and not only + // at the gate. + expect(parse).not.toThrowError(/#\d{3,5}(?![0-9A-Za-z])/s); }); it('still compiles the aggregate the ruling kept', () => { diff --git a/packages/spec/src/data/driver.zod.ts b/packages/spec/src/data/driver.zod.ts index 1fa8913aee..51b6eee585 100644 --- a/packages/spec/src/data/driver.zod.ts +++ b/packages/spec/src/data/driver.zod.ts @@ -111,7 +111,7 @@ export const DriverOptionsSchema = lazySchema(() => z.object({ * record is the semantic migration `driver-capabilities-inert-bits-removed`. */ const capRemoved = (key: string, mechanism: string) => - `\`DriverCapabilities.${key}\` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 ` + + `\`DriverCapabilities.${key}\` was removed in @objectstack/spec 17.0.0 (ADR-0049 ` + `enforce-or-remove) — no code in any repository ever read it, so its value never changed ` + `which code path ran. ${mechanism} Delete the key.`; diff --git a/packages/spec/src/data/driver/common.zod.ts b/packages/spec/src/data/driver/common.zod.ts index 2256776311..b1618f1339 100644 --- a/packages/spec/src/data/driver/common.zod.ts +++ b/packages/spec/src/data/driver/common.zod.ts @@ -102,7 +102,7 @@ export const SSL_DETAIL_BELONGS_ON_DATASOURCE = * rejection `shared/strict-object.ts` documents. */ export const INLINE_CREDENTIAL_REFUSED = (key: string): string => - `\`${key}\` is a credential and is not accepted inline in driver config (#7990): the ` + `\`${key}\` is a credential and is not accepted inline in driver config: the ` + 'datasource is persisted whole into `sys_metadata`, which is served back by the ordinary ' + 'data API, so an inline credential lands in cleartext at rest. Bind the secret instead: ' + "the Setup → Datasources connection form's secret field hands it to the datasource secret " @@ -145,9 +145,9 @@ export const INLINE_CREDENTIAL_REFUSED = (key: string): string => */ export const URL_EMBEDDED_CREDENTIAL_REFUSED = (key: string): string => `this \`${key}\` embeds a password in its userinfo (\`user:password@host\`) and is not ` - + 'accepted at publish (#8082): the datasource is persisted whole into `sys_metadata`, which ' + + 'accepted at publish: the datasource is persisted whole into `sys_metadata`, which ' + 'is served back by the ordinary data API, so a URL-embedded credential lands in cleartext ' - + 'at rest exactly like an inline `password` (#7990). Keep the authored URL credential-free ' + + 'at rest exactly like an inline `password`. Keep the authored URL credential-free ' + '(a bare username, `user@host`, is fine) and bind the secret instead: the Setup → ' + "Datasources connection form's secret field hands it to the datasource secret binder, " + 'which encrypts it into `sys_secret` and stores only an opaque handle at ' @@ -155,7 +155,7 @@ export const URL_EMBEDDED_CREDENTIAL_REFUSED = (key: string): string => + '`external.credentialsRef`. The resolved secret is injected at connect time and wins over ' + 'anything embedded in the URL. Do NOT substitute a `${…}` placeholder into the URL: ' + 'placeholders in authored metadata are resolved by nothing and reach the database client ' - + 'verbatim (#8078, measured), and are themselves refused at publish (#8336). ' + + 'verbatim (measured), and are themselves refused at publish. ' + 'Runtime-environment DSNs (`OS_DATABASE_URL` and friends) do ' + 'not pass through this publish door and are unaffected.'; @@ -325,17 +325,17 @@ export const CREDENTIAL_URL_QUERY_PARAM_NAMES: readonly string[] = */ export const URL_CREDENTIAL_QUERY_PARAM_REFUSED = (key: string, param: string): string => `this \`${key}\` carries \`?${param}=\` in its query string — credential material that is not ` - + 'accepted at publish (#8337): the datasource is persisted whole into `sys_metadata`, which ' + + 'accepted at publish: the datasource is persisted whole into `sys_metadata`, which ' + 'is served back by the ordinary data API, so a query-embedded credential lands in cleartext ' - + 'at rest exactly like a userinfo password (#8082) or an inline key (#7990) — and at connect ' + + 'at rest exactly like a userinfo password or an inline key — and at connect ' + 'it can silently override the secret the binder injects. Remove the parameter and bind the ' + "secret instead: the Setup → Datasources connection form's secret field hands it to the " + 'datasource secret binder, which encrypts it into `sys_secret` and stores only an opaque ' + 'handle at `external.credentialsRef` — or reference the secrets store directly with ' + '`external.credentialsRef`. The resolved secret is injected at connect time. Do NOT ' + 'substitute a `${…}` placeholder into the URL: placeholders in authored metadata are ' - + 'resolved by nothing and reach the database client verbatim (#8078, measured), and are ' - + 'themselves refused at publish (#8336). Runtime-environment DSNs (`OS_DATABASE_URL` and ' + + 'resolved by nothing and reach the database client verbatim (measured), and are ' + + 'themselves refused at publish. Runtime-environment DSNs (`OS_DATABASE_URL` and ' + 'friends) do not pass through this publish door and are unaffected.'; /** @@ -355,17 +355,17 @@ export const URL_CREDENTIAL_QUERY_PARAM_REFUSED = (key: string, param: string): * defeats the binder. */ export const PASSTHROUGH_INLINE_CREDENTIAL_REFUSED = (path: string): string => - `\`${path}\` is a credential and is not accepted in the driver-options passthrough (#9040): ` + `\`${path}\` is a credential and is not accepted in the driver-options passthrough: ` + 'the datasource is persisted whole into `sys_metadata`, which is served back by the ' + 'ordinary data API, so a passthrough credential lands in cleartext at rest exactly like an ' - + 'inline `password` (#7990), a URL userinfo password (#8082) or a credential query ' - + 'parameter (#8337). Remove the `auth` block\'s password and bind the secret instead: the ' + + 'inline `password`, a URL userinfo password or a credential query ' + + 'parameter. Remove the `auth` block\'s password and bind the secret instead: the ' + "Setup → Datasources connection form's secret field hands it to the datasource secret " + 'binder, which encrypts it into `sys_secret` and stores only an opaque handle at ' + '`external.credentialsRef` — or reference the secrets store directly with ' + '`external.credentialsRef`. The resolved secret is injected at connect time and wins over ' - + 'an `auth` block embedded in the passthrough (#8696, measured). Placeholders are no ' - + 'escape either: a `${…}` span anywhere in `options` is itself refused at publish (#8336).'; + + 'an `auth` block embedded in the passthrough (measured). Placeholders are no ' + + 'escape either: a `${…}` span anywhere in `options` is itself refused at publish.'; /** * The paths inside mongo's `options` passthrough that resolve into a login @@ -579,9 +579,9 @@ export function credentialFreeUrl( */ export const UNRESOLVED_PLACEHOLDER_REFUSED = (key: string): string => `this \`${key}\` contains a \`\${…}\` placeholder, and placeholders are not resolved here ` - + '(#8336): nothing in the platform substitutes `${…}` in authored datasource config — ' + + 'at all: nothing in the platform substitutes `${…}` in authored datasource config — ' + 'placeholders in authored metadata are resolved by nothing, are stored verbatim in ' - + '`sys_metadata`, and reach the database client verbatim (#8078, measured), so the ' + + '`sys_metadata`, and reach the database client verbatim (measured), so the ' + 'connection fails (or connects somewhere unintended) with no error naming the unresolved ' + 'placeholder. Write the literal value instead. For secret material, bind it: the Setup → ' + "Datasources connection form's secret field hands it to the datasource secret binder, " diff --git a/packages/spec/src/data/driver/driver-credential-refusal.test.ts b/packages/spec/src/data/driver/driver-credential-refusal.test.ts index 2d1389b673..6490173650 100644 --- a/packages/spec/src/data/driver/driver-credential-refusal.test.ts +++ b/packages/spec/src/data/driver/driver-credential-refusal.test.ts @@ -644,8 +644,12 @@ describe('mongo options passthrough — credential refusal (#9040)', () => { const at = result.error!.issues.filter((i) => i.path.join('.') === 'options.auth.password'); expect(at.length).toBe(2); const texts = at.map((i) => i.message).join('\n'); - expect(texts).toContain('#9040'); - expect(texts).toContain('#8336'); + // Pinned on what each refusal SAYS, not on a tracker id: the ids were + // stripped from customer-facing refusal prose, so a text pin on them would + // pin the thing that must not be there. + expect(texts).toContain('is a credential and is not accepted in the driver-options passthrough'); + expect(texts).toContain('placeholder'); + expect(texts).not.toMatch(/#\d{3,5}\b/); }); it('re-paths the refusal under `config.options.auth.password` on the authored artefact', () => { @@ -889,7 +893,7 @@ describe('datasource — bound credentialsRef + user-less mongo url refused (#90 expect(paths).toContain('config.url'); expect(paths).toContain('config.options.auth.password'); expect(result.error!.issues.some((i) => i.message.includes("the URL's own userinfo"))).toBe(true); - expect(result.error!.issues.some((i) => i.message.includes('#9040'))).toBe(true); + expect(result.error!.issues.some((i) => i.message.includes('not accepted in the driver-options passthrough'))).toBe(true); }); it('an empty `config.url` is the COMPOSED branch, not this one — a live discrete username is not refused', () => { @@ -919,7 +923,7 @@ describe('datasource — bound credentialsRef + user-less mongo url refused (#90 external: { ...BOUND }, }); expect(result.success).toBe(false); - expect(result.error!.issues.some((i) => i.message.includes('#8082'))).toBe(true); + expect(result.error!.issues.some((i) => i.message.includes('embeds a password in its userinfo'))).toBe(true); expect(result.error!.issues.some((i) => i.message.includes("the URL's own userinfo"))).toBe(false); }); }); @@ -1120,6 +1124,6 @@ describe('datasource — bound credentialsRef + composed mongo config naming no expect(paths).toContain('config.username'); expect(paths).toContain('config.options.auth.password'); expect(result.error!.issues.some((i) => i.message.includes("add `username` to `config`"))).toBe(true); - expect(result.error!.issues.some((i) => i.message.includes('#9040'))).toBe(true); + expect(result.error!.issues.some((i) => i.message.includes('not accepted in the driver-options passthrough'))).toBe(true); }); }); diff --git a/packages/spec/src/data/driver/postgres.zod.ts b/packages/spec/src/data/driver/postgres.zod.ts index 6308eaca77..cf8920b04e 100644 --- a/packages/spec/src/data/driver/postgres.zod.ts +++ b/packages/spec/src/data/driver/postgres.zod.ts @@ -122,11 +122,18 @@ function pgParseableUrl(schema: S, key: string) { }); } -/** Prescription for a pool knob written inside `config` instead of `pool`. */ +/** + * Prescription for a pool knob written inside `config` instead of `pool`. + * + * The knobs were declared on this config surface and read by nothing until the + * #4410 liveness audit moved them onto the datasource's own `pool` block; the + * id is kept HERE, in a comment, and out of the prescription — a customer being + * refused has no tracker to resolve it against. + */ const poolBelongsOnDatasource = (key: string, canonical: string) => `\`${key}\` is not driver config — connection pooling is configured once for every driver in ` + `the datasource's own \`pool\` block. Move it to \`pool: { ${canonical}: … }\`. ` - + `(It was declared here and read by nothing until #4410.)`; + + `(It was declared here and read by nothing.)`; export const PostgresConfigSchema = lazySchema(() => strictObject( { diff --git a/packages/spec/src/data/hook-body.test.ts b/packages/spec/src/data/hook-body.test.ts index 134dbace74..83c117a2e1 100644 --- a/packages/spec/src/data/hook-body.test.ts +++ b/packages/spec/src/data/hook-body.test.ts @@ -83,7 +83,9 @@ describe('HookBody', () => { const message = r.success ? '' : JSON.stringify(r.error.issues); // The prescription itself, not a bare "invalid enum value": it must name // the token, say it was removed, and tell the author what to do instead. - expect(message).toMatch(/crypto\.hash.*was removed.*17.*#4391/s); + expect(message).toMatch(/crypto\.hash.*was removed.*17.*ADR-0049/s); + // Negative pin: the tracker id resolves to nothing for this audience. + expect(message).not.toMatch(/#\d{3,5}\b/); expect(message).toMatch(/sandbox never implemented it/s); expect(message).toMatch(/Delete the capability/s); }); diff --git a/packages/spec/src/data/hook-body.zod.ts b/packages/spec/src/data/hook-body.zod.ts index cda842a02d..81c47266d1 100644 --- a/packages/spec/src/data/hook-body.zod.ts +++ b/packages/spec/src/data/hook-body.zod.ts @@ -8,8 +8,8 @@ import { strictObject } from '../shared/strict-object'; // takes the file's FIRST JSDoc as the reference page's module blurb, so a doc // comment here would replace the whole capability-token table. const CRYPTO_HASH_RETIRED = - "`crypto.hash` was removed from `HookBodyCapability` in @objectstack/spec 17 (#4391, " - + 'ADR-0049 enforce-or-remove) — the sandbox never implemented it. `installCtx` wired only ' + "`crypto.hash` was removed from `HookBodyCapability` in @objectstack/spec 17 " + + '(ADR-0049 enforce-or-remove) — the sandbox never implemented it. `installCtx` wired only ' + '`ctx.crypto.randomUUID`, so `ctx.crypto.hash(...)` threw inside the VM on every call the ' + 'token ever "granted", while the build-time extractor inferred the token from that very ' + 'call and let `os build` pass. Delete the capability from `capabilities` AND delete the ' diff --git a/packages/spec/src/data/object.zod.ts b/packages/spec/src/data/object.zod.ts index 513ff65dce..45768cc319 100644 --- a/packages/spec/src/data/object.zod.ts +++ b/packages/spec/src/data/object.zod.ts @@ -417,7 +417,7 @@ const declaredIndexUniqueScopeError: z.core.$ZodErrorMap = (issue) => { `(one holder across the whole installation — materialized over exactly ` + `\`fields\`, and the positional meaning of bare true on a declared index: ` + `bare true is warned by lint unique/unscoped-declared-index in 17.x and ` + - `rejected at protocol 18, #5082).${nearMiss}` + `rejected at protocol 18).${nearMiss}` ); }; @@ -1413,7 +1413,7 @@ export type RowCrudActionOverrideParsed = z.infer { // asserted `.toThrow()` would stay green if the error map were deleted. it('prescribes the retirement for `array_agg`', () => { expect(() => AggregationFunction.parse('array_agg')) - .toThrow(/`array_agg`.*was removed.*#6188.*Delete the aggregation/s); + .toThrow(/`array_agg`.*was removed.*ADR-0049.*Delete the aggregation/s); + expect(() => AggregationFunction.parse('array_agg')).not.toThrow(/#\d{3,5}\b/); }); it('prescribes the retirement for `string_agg`', () => { expect(() => AggregationFunction.parse('string_agg')) - .toThrow(/`string_agg`.*was removed.*#6188.*Delete the aggregation/s); + .toThrow(/`string_agg`.*was removed.*ADR-0049.*Delete the aggregation/s); + expect(() => AggregationFunction.parse('string_agg')).not.toThrow(/#\d{3,5}\b/); }); it('does NOT tell a mis-spelling that it "was removed"', () => { diff --git a/packages/spec/src/data/query.zod.ts b/packages/spec/src/data/query.zod.ts index 429d501a7d..7e40486284 100644 --- a/packages/spec/src/data/query.zod.ts +++ b/packages/spec/src/data/query.zod.ts @@ -71,7 +71,7 @@ export const SortNodeSchema = lazySchema(() => strictObject( // table below. (The `crypto.hash` / `HookBodyCapability` precedent, // `data/hook-body.zod.ts`, which is the other enum-value retirement in tree.) const AGG_RETIRED_MIDDLE = - ' was removed from `AggregationFunction` in @objectstack/spec 17 (#6188, ADR-0049 ' + ' was removed from `AggregationFunction` in @objectstack/spec 17 (ADR-0049 ' + 'enforce-or-remove) — no SQL backend ever compiled it. `SqlDriver.mapAggregateFunc` and ' + '`RemoteTransport.aggregate` each lower the same set of functions and refuse the rest, and ' + "the v1 dataset runtime had to subtract this one by name to stop it reaching a `COUNT(*)` " @@ -332,14 +332,14 @@ export type FieldNode = string; */ const FIELD_NODE_OBJECT_FORM_REMOVED = 'A `fields[]` entry is a field name (string). The nested-select object form ' - + '`{ field, fields, alias }` was removed in @objectstack/spec 17 (#4196, ADR-0049) — nothing ' + + '`{ field, fields, alias }` was removed in @objectstack/spec 17 (ADR-0049) — nothing ' + 'ever produced it and nothing ever read `.fields`/`.alias`: every consumer on this path ' + 'treats the list as `string[]`, so the object form was dropped by the SQL and memory drivers, ' + 'projected as a column literally named "[object Object]" by MongoDB, and refused as an unknown ' + 'field by the REST ingress. Select related data with `expand` — ' + "`expand: { owner_id: { object: 'user', fields: ['name'] } }` — whose nested query names the " + 'related columns you want. A dotted `fields` path is NOT the replacement: no driver ever ' - + 'resolved one and the ingress refuses it (`400 INVALID_FIELD`, #7532). Keep the foreign-key ' + + 'resolved one and the ingress refuses it (`400 INVALID_FIELD`). Keep the foreign-key ' + "column in your own projection (`fields: ['title', 'owner_id']`) — the relation is carried by " + 'that key, so projecting it away leaves expansion nothing to resolve. `alias` has no ' + 'replacement here; an aliased projection is an `aggregations` or `windowFunctions` entry, ' diff --git a/packages/spec/src/data/unique-scope-message.test.ts b/packages/spec/src/data/unique-scope-message.test.ts index 45f3608a7a..c10d804085 100644 --- a/packages/spec/src/data/unique-scope-message.test.ts +++ b/packages/spec/src/data/unique-scope-message.test.ts @@ -99,11 +99,13 @@ describe('unique scope rejection message — the two surfaces disagree about bar expect(message).toContain("'global'"); expect(message).toContain('the positional meaning of bare true on a declared index'); - // The migration the author is standing in front of (#5082): the 17.x - // warning channel and the protocol-18 rejection, named where they are read. + // The migration the author is standing in front of: the 17.x warning + // channel and the protocol-18 rejection, named where they are read. The + // tracker id that used to sit beside them is gone — it resolved to nothing + // for the author reading this refusal. expect(message).toContain('unique/unscoped-declared-index'); expect(message).toContain('protocol 18'); - expect(message).toContain('#5082'); + expect(message).not.toMatch(/#\d{3,5}\b/); // And the organization scope described by what it DOES here, not by an // equivalence to `true` that does not hold on this surface. diff --git a/packages/spec/src/kernel/context.zod.ts b/packages/spec/src/kernel/context.zod.ts index 0b58d48da5..a3b0731f18 100644 --- a/packages/spec/src/kernel/context.zod.ts +++ b/packages/spec/src/kernel/context.zod.ts @@ -19,14 +19,14 @@ import { retiredKey } from '../shared/retired-key'; // `kernel-context-preview-mode-retired`. const RUNTIME_MODE_PREVIEW_RETIRED = "`context.mode: 'preview'` was removed from `RuntimeMode` in @objectstack/spec 17 " - + '(#11846, ADR-0049 enforce-or-remove) — no layer of the platform ever branched on it: ' + + '(ADR-0049 enforce-or-remove) — no layer of the platform ever branched on it: ' + 'the value promised "bypass auth, simulate admin identity" and no code path implemented ' + 'either half, so a deployment declaring it ran with ordinary production behaviour under ' + 'a misleading label. Delete the value — `mode` defaults to `production`; use ' + '`development` for local demo work. Preview DEPLOYMENTS are the deployment layer\'s ' + 'job (`OS_PREVIEW_MODE` is routing-only and never touched identity). If a preview ' + 'experience becomes a product capability it re-declares fresh, with the ' - + 'production-posture hard-refusal as the first-landed half (#11846 ruling record).'; + + 'production-posture hard-refusal as the first-landed half.'; const PREVIEW_MODE_RETIRED = '`context.previewMode` was removed in @objectstack/spec 17 (ADR-0049 ' + 'enforce-or-remove) — nothing ever read the block: none of its six keys (`autoLogin`, ' diff --git a/packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts b/packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts index 4cc6503e07..84aa2e5657 100644 --- a/packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts +++ b/packages/spec/src/kernel/plugin-lifecycle-advanced.test.ts @@ -211,8 +211,11 @@ describe('Plugin Lifecycle Advanced Schemas', () => { // the byte string. const message = result.success ? '' : result.error.issues[0]?.message ?? ''; expect(message).toContain('were removed'); - expect(message).toContain('#12340'); expect(message).toContain('ADR-0049'); + // The customer-resolvable anchors stay; the tracker id does not reach + // this audience at all. Negative pin, so a re-introduced id reds here + // rather than only at `check:doc-authoring`. + expect(message).not.toMatch(/#\d{3,5}\b/); expect(message).toMatch(/memory fallback|in-memory Map/); expect(message).toContain("Use 'memory'"); }); @@ -226,7 +229,6 @@ describe('Plugin Lifecycle Advanced Schemas', () => { expect(result.success).toBe(false); const message = result.success ? '' : result.error.issues[0]?.message ?? ''; expect(message).not.toContain('were removed'); - expect(message).not.toContain('#12340'); }); it('still accepts the two strategies the runtime implements', () => { diff --git a/packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts b/packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts index 73278a66ff..6d9a4c0a7e 100644 --- a/packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts +++ b/packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts @@ -222,7 +222,7 @@ export const PluginHealthReportSchema = lazySchema(() => z.object({ */ const HOT_RELOAD_STATE_STRATEGY_RETIRED = "`HotReloadConfig.stateStrategy: 'disk'` and `HotReloadConfig.stateStrategy: " - + "'distributed'` were removed in @objectstack/spec 18 (#12340, ADR-0049 " + + "'distributed'` were removed in @objectstack/spec 18 (ADR-0049 " + 'enforce-or-remove) — neither was ever implemented. Both switch arms in ' + "`PluginStateManager.saveState` wrote to the SAME in-memory Map as 'memory' " + "(the in-source comments said 'memory fallback'), and the only trace was a " diff --git a/packages/spec/src/ui/action.test.ts b/packages/spec/src/ui/action.test.ts index c98ca5b86b..47400b5f4f 100644 --- a/packages/spec/src/ui/action.test.ts +++ b/packages/spec/src/ui/action.test.ts @@ -1472,7 +1472,8 @@ describe('ACTION_LOCATIONS — canonical source of truth', () => { // and closes with the house `os migrate meta` sentence (#6856 route D). const prescribes = (fn: () => unknown) => { expect(fn).toThrow(/`global_nav` was removed from `ACTION_LOCATIONS`/s); - expect(fn).toThrow(/#6888/s); + expect(fn).toThrow(/ADR-0049 enforce-or-remove/s); + expect(fn).not.toThrow(/#\d{3,5}\b/); expect(fn).toThrow(/locations: \[\]/s); expect(fn).toThrow(/os migrate meta --from 16/s); }; diff --git a/packages/spec/src/ui/action.zod.ts b/packages/spec/src/ui/action.zod.ts index bce52429ea..2fc47a80f3 100644 --- a/packages/spec/src/ui/action.zod.ts +++ b/packages/spec/src/ui/action.zod.ts @@ -582,7 +582,7 @@ const TARGET_REQUIRED_TYPES: ReadonlySet = new Set( // `HookBodyCapability` and `array_agg` / `AggregationFunction` enum-value // retirements — `data/hook-body.zod.ts`, `data/query.zod.ts`.) const GLOBAL_NAV_RETIRED = - '`global_nav` was removed from `ACTION_LOCATIONS` in @objectstack/spec 17 (#6888, ADR-0049 ' + '`global_nav` was removed from `ACTION_LOCATIONS` in @objectstack/spec 17 (ADR-0049 ' + 'enforce-or-remove) — no running-app surface ever rendered it. The console command palette ' + '(`⌘K`) builds its groups from nav items, objects, dashboards, pages, reports, recent items ' + 'and record search; it reads no action metadata at all, so an action declaring this location ' @@ -1096,10 +1096,10 @@ const actionObject = () => strictObject({ && typeof iss.input === 'object' && !Array.isArray(iss.input) ? "`params` is the parameter DEFINITION array (fields collected from the user before the action runs), not a values map. " - + "For a `type:'api'` action's static request body — including `{{page.}}` tokens — use `bodyExtra: { … }` instead (#5777). " + + "For a `type:'api'` action's static request body — including `{{page.}}` tokens — use `bodyExtra: { … }` instead. " + "For a `type:'url'` action there is nowhere to move it to, by decision: put static values straight into the `target` string " + "(`${param.X}` interpolates a value collected by the params dialog, `${ctx.X}` one from the action context), and open a new tab with " - + "`openIn: 'new-tab'`. The url-side readings of an object `params` — a static `${param.X}` scope, and `params.newTab` — are RETIRED, not renamed (#6828). " + + "`openIn: 'new-tab'`. The url-side readings of an object `params` — a static `${param.X}` scope, and `params.newTab` — are RETIRED, not renamed. " + 'Expected an array of ActionParam, received an object.' : undefined ), diff --git a/packages/spec/src/ui/app.zod.ts b/packages/spec/src/ui/app.zod.ts index 368fa6a285..fe8562b7da 100644 --- a/packages/spec/src/ui/app.zod.ts +++ b/packages/spec/src/ui/app.zod.ts @@ -251,7 +251,7 @@ const navItemSurface = (variant: NavItemVariant): StrictObjectOptions => ({ ...(variant === 'separator' ? SEPARATOR_NAV_ITEM_GUIDANCE : {}), }, history: - 'Until #4001 these were dropped silently — the entry still parsed, so a mis-spelled ' + + 'Until this shape was closed these were dropped silently — the entry still parsed, so a mis-spelled ' + 'config shipped as a nav item that quietly ignored it (a stripped `visible` renders ' + 'an entry that should have been gated).', }); diff --git a/packages/spec/src/ui/component.zod.ts b/packages/spec/src/ui/component.zod.ts index 595c3734c9..97678b04ea 100644 --- a/packages/spec/src/ui/component.zod.ts +++ b/packages/spec/src/ui/component.zod.ts @@ -1212,18 +1212,18 @@ export const RecordActivityProps = strictObject({ // `issue.input` so only a value which used to be legal gets the "was removed" // message (the `view.exportOptions` `'pdf'` precedent, #8010). const CHATTER_POSITION_RETIRED: ReadonlyMap = new Map([ - ['sidebar', "'sidebar' was removed from `record:chatter` / `record:discussion` `position` (#8762) — " + ['sidebar', "'sidebar' was removed from `record:chatter` / `record:discussion` `position` — " + 'no renderer branch ever compared the old vocabulary: `RecordChatterPanel` docks on ' + "'right'/'left' and renders in flow on 'bottom', so a spec-valid 'sidebar' silently fell " + "through to the in-flow render. Write 'right' — the docked side panel 'sidebar' meant. " + 'Run `os migrate meta` to list the mechanical edits for existing sources ' + '(registered under protocol major 18); apply them by hand.'], - ['inline', "'inline' was removed from `record:chatter` / `record:discussion` `position` (#8762) — " + ['inline', "'inline' was removed from `record:chatter` / `record:discussion` `position` — " + 'no renderer branch ever compared the old vocabulary. Write \'bottom\' — the renderer\'s ' + "in-flow branch, which is where 'inline' already rendered. Run `os migrate meta` to " + 'list the mechanical edits for existing sources (registered under protocol major 18); ' + 'apply them by hand.'], - ['drawer', "'drawer' was removed from `record:chatter` / `record:discussion` `position` (#8762) " + ['drawer', "'drawer' was removed from `record:chatter` / `record:discussion` `position` " + 'with no successor: no renderer branch ever implemented an overlay drawer — the value fell ' + "through to the in-flow render. Write 'right' — the docked side panel is the nearest " + 'surviving shape of a side drawer. Run `os migrate meta` to list the mechanical edits ' @@ -1892,7 +1892,7 @@ export const ElementButtonPropsSchema = lazySchema(() => strictObject({ */ const elementFilterRetired = (key: string): string => '`element:filter` property `' + key + '` was removed in @objectstack/spec 17 ' - + '(#9220, ADR-0049) — the whole `element:filter` element is retired: no renderer for it ' + + '(ADR-0049) — the whole `element:filter` element is retired: no renderer for it ' + 'ever shipped in objectui, framework or cloud (Studio\'s designer palette lists it as a ' + 'no-renderer exclusion), so every key on this element was a capability claim nothing ' + 'kept. Delete the `element:filter` component; list surfaces own their filtering — use a ' @@ -1938,11 +1938,11 @@ export const ElementFilterPropsSchema = lazySchema(() => strictObject({ */ const elementFormRetired = (key: string): string => '`element:form` property `' + key + '` was removed in @objectstack/spec 17 ' - + '(#9249, ADR-0049) — the whole `element:form` element is retired: no renderer for it ' + + '(ADR-0049) — the whole `element:form` element is retired: no renderer for it ' + 'ever shipped in objectui, framework or cloud (Studio\'s designer palette lists it as a ' + 'no-renderer exclusion — "use the object-bound `object-form` block"), so every key on ' + 'this element was a capability claim nothing kept. Delete the `element:form` component ' - + 'and use the object-bound `object-form` block instead (#7751) — it is rendered, ' + + 'and use the object-bound `object-form` block instead — it is rendered, ' + 'designer-publishable, and carries the same intent (`objectName`, `fields`, `mode`, ' + '`submitText`). ' + 'Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand.'; @@ -2237,9 +2237,9 @@ export const ElementTextInputPropsSchema = lazySchema(() => strictObject({ * absent from the map entirely, so even the #5068 gate said nothing. */ const objectBlockHistory = (type: string) => - `Until #7751 \`${type}\` had no entry in ComponentPropsMap at all, so the #5068 authoring gate ` - + 'skipped the type: a misspelled key inside `properties` parsed clean, was stored, reached ' - + "objectui's renderer and was ignored there (the #7750 shape — `filters` for `filter` silently " + `Until this type was added to ComponentPropsMap, \`${type}\` had no entry there at all, so ` + + 'the authoring gate skipped it: a misspelled key inside `properties` parsed clean, was ' + + "stored, reached objectui's renderer and was ignored there (`filters` for `filter` silently " + 'unfiltered a personal work queue, with a success receipt).'; /** @@ -2629,22 +2629,22 @@ export type ObjectFormProps = z.input; // a bare `z.string()` — and the authored-value census on both repos (this // repo + objectui#5939's) found zero occurrences to rewrite. const MASTER_DETAIL_FORM_TYPE_RETIRED: ReadonlyMap = new Map([ - ['wizard', "'wizard' is not part of `object-master-detail-form` `formType` (#11873 — objectui#5939 " - + 'measured the renderer): only the current wizard step\'s fields mount and the block\'s single ' + ['wizard', "'wizard' is not part of `object-master-detail-form` `formType` — the renderer " + + 'was measured on both repos: only the current wizard step\'s fields mount and the block\'s single ' + "Save bar acts as the wizard's Next, so parent + details never save through the atomic batch " + "(ADR-0001, the block's whole contract). Write 'simple' (sections render stacked) or 'tabbed'; " + "for a wizard without inline details author an `object-form`, where 'wizard' is honoured."], - ['split', "'split' is not part of `object-master-detail-form` `formType` (#11873 — objectui#5939 " - + 'measured the renderer): the parent half renders inline but persists via `dataSource.create`, ' + ['split', "'split' is not part of `object-master-detail-form` `formType` — the renderer " + + 'was measured on both repos: the parent half renders inline but persists via `dataSource.create`, ' + "bypassing the atomic parent+details batch (ADR-0001, the block's whole contract). Write " + "'simple' or 'tabbed'; for a split presentation without inline details author an " + "`object-form`, where 'split' is honoured."], - ['drawer', "'drawer' is not part of `object-master-detail-form` `formType` (#11873 — objectui#5939 " - + 'measured the renderer): the parent half renders in a portal dialog outside the master-detail ' + ['drawer', "'drawer' is not part of `object-master-detail-form` `formType` — the renderer " + + 'was measured on both repos: the parent half renders in a portal dialog outside the master-detail ' + "container, so the block's Save bar has no form to submit. Write 'simple' or 'tabbed'; for a " + "drawer overlay without inline details author an `object-form`, where 'drawer' is honoured."], - ['modal', "'modal' is not part of `object-master-detail-form` `formType` (#11873 — objectui#5939 " - + 'measured the renderer): the parent half renders in a portal dialog outside the master-detail ' + ['modal', "'modal' is not part of `object-master-detail-form` `formType` — the renderer " + + 'was measured on both repos: the parent half renders in a portal dialog outside the master-detail ' + "container (the same portal shape as 'drawer'), so the block's Save bar has no form to " + "submit. Write 'simple' or 'tabbed'; for a modal overlay without inline details author an " + "`object-form`, where 'modal' is honoured."], diff --git a/packages/spec/src/ui/dashboard-compareto.test.ts b/packages/spec/src/ui/dashboard-compareto.test.ts index 7db4b1b21c..66cc418655 100644 --- a/packages/spec/src/ui/dashboard-compareto.test.ts +++ b/packages/spec/src/ui/dashboard-compareto.test.ts @@ -118,8 +118,8 @@ describe('#5011 — every retired spelling is rejected WITH its upgrade', () => it('the bare string arms carry a prescription naming the exact replacement', () => { const period = reject('previousPeriod'); expect(period).toContain('was removed in'); - expect(period).toContain('#5011'); expect(period).toContain('DROPPED'); + expect(period).not.toMatch(/#\d{3,5}\b/); expect(period).toContain("kind: "); expect(period).toContain('previousPeriod'); expect(period).toContain('os migrate meta --from 16'); @@ -228,7 +228,7 @@ describe('#5011 — the converged slot is union-free, so its prescriptions reach const top = issuesFor('previousPeriod').map((i) => i.message).join('\n'); expect(top).not.toBe('Invalid input'); expect(top).toContain('was removed in'); - expect(top).toContain('#5011'); + expect(top).not.toMatch(/#\d{3,5}\b/); const offsetTop = issuesFor({ offset: '7d' }).map((i) => i.message).join('\n'); expect(offsetTop).toContain('was removed in'); diff --git a/packages/spec/src/ui/dashboard.test.ts b/packages/spec/src/ui/dashboard.test.ts index f856b4f95c..bea614f18f 100644 --- a/packages/spec/src/ui/dashboard.test.ts +++ b/packages/spec/src/ui/dashboard.test.ts @@ -617,10 +617,12 @@ describe('[#5010] DashboardWidgetSchema — retired action trio + `aria`', () => const message = parseWith({ [key]: value }); // The prescription, in the parts an upgrading author needs: the - // fully-qualified key, the version, the issue, and the fix. + // fully-qualified key, the version, the ADR, and the fix. NOT the tracker + // id — it resolves to nothing for the author reading this refusal. expect(message).toMatch(new RegExp(`dashboard\\.widgets\\[\\]\\.${key}`)); expect(message).toMatch(/removed in @objectstack\/spec 17\.0\.0/); - expect(message).toMatch(/#5010/); + expect(message).toMatch(/ADR-0049 enforce-or-remove/); + expect(message).not.toMatch(/#\d{3,5}\b/); // The three went together — an author who deletes only the one key they // were told about would hit this same error twice more. expect(message).toMatch(/delete all three/i); diff --git a/packages/spec/src/ui/dashboard.zod.ts b/packages/spec/src/ui/dashboard.zod.ts index 681b857cdc..4d7efce178 100644 --- a/packages/spec/src/ui/dashboard.zod.ts +++ b/packages/spec/src/ui/dashboard.zod.ts @@ -288,7 +288,7 @@ const COMPARE_TO_OFFSET_RETIRED = // container changes, which is what makes this a mechanical conversion. const COMPARE_TO_STRING_RETIRED = (kind: 'previousPeriod' | 'previousYear') => `\`dashboard.widgets[].compareTo: '${kind}'\` (the bare string form) was removed in ` - + '@objectstack/spec 17.0.0 (#5011) — the ADR-0021 dataset renderer silently DROPPED it, so the ' + + '@objectstack/spec 17.0.0 — the ADR-0021 dataset renderer silently DROPPED it, so the ' + 'widget rendered its base numbers with the comparison the author asked for quietly absent. ' + `Write \`compareTo: { kind: '${kind}' }\` instead — same comparison, spelled the way the ` + 'analytics executor actually reads it (`DatasetSelection.compareTo`). Add `dimension` only ' @@ -307,8 +307,8 @@ const COMPARE_TO_STRING_RETIRED = (kind: 'previousPeriod' | 'previousYear') => // `actionUrl` should learn in the same breath that its two companions are gone // too, rather than hitting three parse errors in three edit rounds. const WIDGET_ACTION_RETIRED = (key: 'actionUrl' | 'actionType' | 'actionIcon') => - `\`dashboard.widgets[].${key}\` was removed in @objectstack/spec 17.0.0 (#5010, ` - + 'ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. ' + `\`dashboard.widgets[].${key}\` was removed in @objectstack/spec 17.0.0 ` + + '(ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. ' + 'No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from ' + '`header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; ' + 'delete all three. ' diff --git a/packages/spec/src/ui/inline-action.test.ts b/packages/spec/src/ui/inline-action.test.ts index 7c7ff0c25b..9f81e20bad 100644 --- a/packages/spec/src/ui/inline-action.test.ts +++ b/packages/spec/src/ui/inline-action.test.ts @@ -164,7 +164,7 @@ describe('InlineActionSchema — `bodyExtra` is the payload key, `params` is not expect(paramsIssue!.code).toBe('invalid_type'); expect((paramsIssue as unknown as { expected: string }).expected).toBe('array'); expect(paramsIssue!.message).toContain('bodyExtra'); - expect(paramsIssue!.message).toContain('#5777'); + expect(paramsIssue!.message).not.toMatch(/#\d{3,5}\b/); // And it says what `params` IS, not only what it is not. expect(paramsIssue!.message).toContain('DEFINITION array'); }); @@ -246,7 +246,8 @@ describe('object-form `params` prescribes per action type (#6828)', () => { expect((issue as unknown as { expected: string }).expected).toBe('array'); expect(issue.message).toContain('`target` string'); expect(issue.message).toContain("openIn: 'new-tab'"); - expect(issue.message).toContain('RETIRED, not renamed (#6828)'); + expect(issue.message).toContain('RETIRED, not renamed'); + expect(issue.message).not.toMatch(/#\d{3,5}\b/); }); it('names the retired `params.newTab` escape hatch, so the flag half has an answer too', () => { @@ -275,7 +276,7 @@ describe('object-form `params` prescribes per action type (#6828)', () => { const issue = paramsIssue(r); expect(issue.code).toBe('invalid_type'); expect(issue.message).toContain('bodyExtra: { … }'); - expect(issue.message).toContain('(#5777)'); + expect(issue.message).not.toMatch(/#\d{3,5}\b/); }); it('guides the REGISTERED action surface identically — the field factory is shared', () => { @@ -292,8 +293,9 @@ describe('object-form `params` prescribes per action type (#6828)', () => { }) as { success: boolean; error?: z.ZodError }; const issue = paramsIssue(r); expect(issue.code).toBe('invalid_type'); - expect(issue.message).toContain('RETIRED, not renamed (#6828)'); + expect(issue.message).toContain('RETIRED, not renamed'); expect(issue.message).toContain('bodyExtra: { … }'); + expect(issue.message).not.toMatch(/#\d{3,5}\b/); }); it('leaves the definition-array meaning of `params` accepted on a url action', () => { diff --git a/packages/spec/src/ui/view.test.ts b/packages/spec/src/ui/view.test.ts index d9c1ed9f8c..f6bcdb6605 100644 --- a/packages/spec/src/ui/view.test.ts +++ b/packages/spec/src/ui/view.test.ts @@ -3150,13 +3150,14 @@ describe('ListViewSchema.exportOptions — object form + array lift + pdf retire expect(parsed.exportOptions).toStrictEqual({ formats: ['csv', 'xlsx'] }); }); - it("REJECTS 'pdf' in the legacy array form with the prescription naming #1301 and the survivors", () => { + it("REJECTS 'pdf' in the legacy array form with the prescription naming the survivors", () => { let message = ''; try { ListViewSchema.parse({ type: 'grid', columns: ['name'], exportOptions: ['xlsx', 'pdf'] }); } catch (e) { message = String((e as Error).message); } expect(message).toMatch(/'pdf' was removed from `view\.exportOptions` formats/); - expect(message).toMatch(/#1301/); + expect(message).toMatch(/PDF export itself was declined as NOT PLANNED/); + expect(message).not.toMatch(/#\d{3,5}\b/); expect(message).toMatch(/'csv', 'xlsx' and 'json'/); expect(message).toMatch(/Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand\./); }); @@ -3164,7 +3165,7 @@ describe('ListViewSchema.exportOptions — object form + array lift + pdf retire it("REJECTS 'pdf' in the object form's `formats` with the same prescription", () => { expect(() => ListViewSchema.parse({ type: 'grid', columns: ['name'], exportOptions: { formats: ['csv', 'pdf'] }, - })).toThrow(/'pdf' was removed from `view\.exportOptions` formats.*#1301/s); + })).toThrow(/'pdf' was removed from `view\.exportOptions` formats.*NOT PLANNED/s); }); it('a wrong format that was NEVER legal keeps the plain enum message, not the retirement text', () => { diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 747f1132f9..1a8d75ea1f 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -1323,8 +1323,8 @@ export const NavigationConfigSchema = lazySchema(() => strictObject({ // hook-body precedent's placement note applies here too: build-docs takes a // file's first JSDoc per exported symbol, and this constant needs no doc page. const LIST_VIEW_EXPORT_PDF_RETIRED = - "'pdf' was removed from `view.exportOptions` formats in @objectstack/spec 17.0.0 (#8010; " - + 'PDF export itself was declined as #1301 NOT_PLANNED) — no renderer has ever produced a PDF ' + "'pdf' was removed from `view.exportOptions` formats in @objectstack/spec 17.0.0 " + + '(PDF export itself was declined as NOT PLANNED) — no renderer has ever produced a PDF ' + 'export: ObjectGrid dropped the declared format from the export menu with only a runtime ' + "console.warn, so authoring it was a parse-clean no-op. Delete the value; the surviving " + "formats are 'csv', 'xlsx' and 'json'. " diff --git a/scripts/check-doc-authoring.mjs b/scripts/check-doc-authoring.mjs index 164020b6f1..0efd4a7a2f 100644 --- a/scripts/check-doc-authoring.mjs +++ b/scripts/check-doc-authoring.mjs @@ -405,6 +405,56 @@ const INTERNAL_ID = new RegExp(INTERNAL_ID_SOURCE, 'g'); // rule meant widening the RECOGNISED POSITIONS below, exactly as this comment // used to prescribe. // +// ## The FOURTH population: text BUILT INSIDE A FUNCTION +// +// The three buckets above are positions a literal is WRITTEN at. A share of +// this tree's refusal prose is instead RETURNED BY A FUNCTION that occupies one +// of those positions — `error: (iss) => '…'` on a zod schema's options, a +// hoisted `const X = (key: string): string => '…'` referenced from `message:` +// or `retiredKey(X(…))`, a `$ZodErrorMap` const referenced from `error:`, a +// `(v): StrictObjectOptions => ({ history: '…' })` options factory. The climb +// terminated at `ArrowFunction` / `ReturnStatement` and returned `undefined`, +// so 28 literals carrying 32 tracker ids across 8 files sat outside every +// bucket while this rule reported `0 violations` over four populated ones. The +// gate was not wrong, it was SCOPED — and the scope boundary was invisible from +// its output. +// +// Maintainer ruling 2026-08-29, verbatim: 「同意」 — the inheritance REACHES +// refusal prose built inside `error: () =>` callbacks: same audience, same +// moment; the ban follows the audience, not the spelling. +// +// ⛔ The climb is NOT unconditional through function bodies. That version +// sweeps every string a helper happens to build, VALUES included, and a rule +// that reports values as prose is one authors get disabled. The rule here is +// exactly one sentence wide: +// +// **A function is transparent to the climb only when the FUNCTION ITSELF +// sits in a recognised customer-facing position.** +// +// which is decided by asking {@link customerTextPosition} the same question +// about the function node that it was asked about the literal. So: +// +// - `error: (iss) => '…'` — the function IS the `error` option. +// - `const X = (k) => '…'` where `X` — the function IS a text-sink const, +// is a {@link collectTextSinkConsts} the same fixed point the hoisted +// sink VALUE spelling already rides on. +// - `(v): StrictObjectOptions => …` — the function IS declared, by its own +// with a STRICT_OPTION_KEYS key return-type annotation, to build a +// latched on the way up strictObject options record. +// - `const helper = () => 'x'` that — NOT transparent. Its body stays +// no recognised position consumes unreachable, which is the whole +// point of the three clauses above. +// +// Their literals are bucketed `functionBuilt` rather than folded into the +// bucket of the position the function occupies, and that is deliberate: the +// blindness floor is PER BUCKET, so folding them into `message` / `tombstone` +// would let this clause rot back to `undefined` while those buckets' DIRECT +// members held the floor up — the exact silence this population was found by. +// +// `error:` is also recognised as a position in its own right (zod 4 renamed the +// error-map option away from `message`); a plain string there is a `message`, +// since nothing was built in a function. +// // ## Why the positions alone are not enough: the hoisted-const spelling // // A position-only matcher reads `guidance: { where: '…' }` and stops at the @@ -666,6 +716,43 @@ function calleeName(call, ts) { return ts.isPropertyAccessExpression(callee) ? callee.name.getText() : callee.getText(); } +/** Is this node a function whose body could hold customer-facing prose? */ +function isFunctionLike(n, ts) { + return ts.isArrowFunction(n) || ts.isFunctionExpression(n) + || ts.isFunctionDeclaration(n) || ts.isMethodDeclaration(n); +} + +/** + * The function a `return` belongs to, or `undefined` at module scope. + * + * Stops at a class or the source file rather than walking forever: a `return` + * with no enclosing function is not a shape this tree has, and treating one as + * transparent would be a climb with no boundary at all. + */ +function enclosingFunction(node, ts) { + for (let cur = node.parent; cur; cur = cur.parent) { + if (isFunctionLike(cur, ts)) return cur; + if (ts.isSourceFile(cur) || ts.isClassDeclaration(cur)) return undefined; + } + return undefined; +} + +/** + * Does this function DECLARE, by its own return-type annotation, that it builds + * a `StrictObjectOptions` record? + * + * The same anchor {@link inStrictOptions} already trusts on a const + * (`const X: StrictObjectOptions = …`), one indirection over: the shared + * per-variant option factories in `ui/app.zod.ts` are written + * `(variant): StrictObjectOptions => ({ surface, aliases, history })`, and + * without this their `history` prose is written at a recognised KEY inside a + * function nothing else identifies. Annotation-driven, never name-driven: a + * factory that does not say what it returns is not taken at its word. + */ +function buildsStrictObjectOptions(fn, ts) { + return !!fn && /\bStrictObjectOptions\b/.test(fn.type ? fn.type.getText() : ''); +} + /** * Is this property assignment a `StrictObjectOptions` key, in a position where * the value is really printed at the author? @@ -707,7 +794,11 @@ function inStrictOptions(prop, ts) { const nm = p.name.getText(); return /_STRICT_OPTIONS$/.test(nm) || /\bStrictObjectOptions\b/.test(declaredTypeText(p, ts)); } - if (ts.isReturnStatement(p) || ts.isArrowFunction(p) || ts.isFunctionDeclaration(p)) return false; + // A function boundary. Transparent ONLY when the function declares itself + // an options factory by its return-type annotation — the fourth-population + // clause, kept as narrow here as it is in `customerTextPosition`. + if (isFunctionLike(p, ts)) return buildsStrictObjectOptions(p, ts); + if (ts.isReturnStatement(p)) return buildsStrictObjectOptions(enclosingFunction(p, ts), ts); cur = p; } return false; @@ -822,7 +913,12 @@ function collectTextSinkConsts(sf, ts) { } if (ts.isPropertyAssignment(n)) { const name = n.name.getText(); - if (name === 'message') seed(n.initializer, 'message'); + // `error:` is zod 4's spelling of the error-map option and seeds exactly + // like `message:`. It is load-bearing rather than tidy: the credential + // refusals in `data/driver/common.zod.ts` reach their sink ONLY through + // `z.never({ error: () => INLINE_CREDENTIAL_REFUSED(key) })`, so without + // this line that const is not a sink and its whole body stays invisible. + if (name === 'message' || name === 'error') seed(n.initializer, 'message'); else if (STRICT_OPTION_KEYS.has(name) && inStrictOptions(n, ts)) seed(n.initializer, 'strictObject'); } if (ts.isCallExpression(n)) { @@ -866,11 +962,37 @@ function collectTextSinkConsts(sf, ts) { * written in, so a nested prescription is reached rather than abandoned at its * own key. * + * A function boundary is crossed only when the FUNCTION ITSELF sits in a + * recognised position — decided by asking this same question about the function + * node (see the "fourth population" section of the Rule 3 header). ⛔ Never an + * unconditional climb through function bodies: that reports values as prose. + * + * @param {number} [fnDepth] how many function boundaries have been crossed + * already. A bound, not a belief: the deepest real chain in this tree is one + * (a literal in a const arrow referenced from a `message:`), and an + * unbounded recursion over a self-referential const would not terminate. * @returns {{where: string, bucket: string}|undefined} */ -function customerTextPosition(node, ts, sinkConsts = new Map()) { +function customerTextPosition(node, ts, sinkConsts = new Map(), fnDepth = 0) { let cur = node; let strictKey; + /** + * The fourth population's clause: `fn` encloses the literal and the climb + * wants to leave through it. Transparent only if `fn` is itself somewhere + * customer-facing. + */ + const throughFunction = (fn) => { + if (!fn || fnDepth >= 4) return undefined; + // An options FACTORY, declared as such by its own return type, once a + // STRICT_OPTION_KEYS key has been latched on the way up. + if (strictKey && buildsStrictObjectOptions(fn, ts)) { + return { where: `strictObject ${strictKey} (built in a function)`, bucket: 'functionBuilt' }; + } + const outer = customerTextPosition(fn, ts, sinkConsts, fnDepth + 1); + return outer + ? { where: `${outer.where} (built in a function)`, bucket: 'functionBuilt' } + : undefined; + }; // A bound, not a belief: refusal prose in this tree reaches ~14 concatenated // operands, and an unbounded climb would walk to the SourceFile and start // reporting whole modules as messages. Raised from 60 with the structural @@ -890,6 +1012,10 @@ function customerTextPosition(node, ts, sinkConsts = new Map()) { if (ts.isPropertyAssignment(p) && p.initializer === cur) { const name = p.name.getText(); if (name === 'message') return { where: 'message:', bucket: 'message' }; + // zod 4's spelling of the same option, printed at the same author in the + // same breath. A plain string here IS a message; only text a FUNCTION + // built gets the fourth population's own bucket, above. + if (name === 'error') return { where: 'error:', bucket: 'message' }; if (!strictKey && STRICT_OPTION_KEYS.has(name) && inStrictOptions(p, ts)) strictKey = name; cur = p; continue; } @@ -924,7 +1050,8 @@ function customerTextPosition(node, ts, sinkConsts = new Map()) { bucket: strictKey ? 'strictObject' : sinkConsts.get(nm), }; } - if (ts.isReturnStatement(p) || ts.isArrowFunction(p)) return undefined; + if (ts.isReturnStatement(p)) return throughFunction(enclosingFunction(p, ts)); + if (isFunctionLike(p, ts)) return throughFunction(p); cur = p; } return undefined; @@ -948,7 +1075,7 @@ function customerTextPosition(node, ts, sinkConsts = new Map()) { */ function findCustomerTextIdViolations(source, file, ts) { const out = []; - const seen = { message: 0, strictObject: 0, tombstone: 0, describe: 0 }; + const seen = { message: 0, strictObject: 0, tombstone: 0, describe: 0, functionBuilt: 0 }; const sf = parseSourceFile(file, source); const sinkConsts = collectTextSinkConsts(sf, ts); const visit = (node) => { @@ -1345,7 +1472,7 @@ function selfTest() { console.error(`\n✗ check-doc-authoring self-test failed:\n${failures.join('\n')}\n`); process.exit(1); } - console.log('✓ check-doc-authoring self-test: scope wiring (.claude and the live docs/ corpus in, .claude/worktrees and docs/{audits,handoff,plans} out), detection, the dead-root hard error (red when a ROOT is renamed, green when restored), the empty-scan hard error (red when a root yields nothing and when the whole scan does, green when restored), the published-catalog internal-id rule (red on a planted id in prose, in a fenced comment and in the repo#NNNN spelling, green when removed; hex colours, version numbers, HTTP codes, array indices and the "#1" ordinal all pass; references/ reached, generated artifacts and the internal roots out; the `#` placeholder passes while the concrete ids it replaced stay red, with no exemption to reach for), the spec customer-facing-text internal-id rule (red on an id planted on a LATER line of a concatenated message — the shape a line-oriented census cannot see, proven here — and in a template chain, a positional validator message, the repo#NNNN spelling, a nested strictObject `guidance` prescription, a HOISTED guidance const, a `KeySetGuidance` const consumed only CROSS-MODULE in both the annotated and the `as const satisfies` spelling, a HOISTED refusal message, a `retiredKey()` tombstone, `new Map` and `Object.freeze` guidance tables, `.describe()` prose, and the nested `guidance` of a whole options table written `satisfies StrictObjectOptions`; green when removed; an ADR id on a tombstone, a `.default()` VALUE, `history`/`guidance` outside a strictObject options position, `extraKeys` key names and an inferred local that merely MENTIONS `KeySetGuidance` all pass; test bodies out; the seen floor is PER BUCKET so one matcher rotting while the others carry the total still reds; and the two TYPE ANCHORS are pinned on the predicate itself — the annotation, `satisfies` and `as const satisfies` spellings all read as a strictObject options position while some other satisfied type does not, and the `*_STRICT_OPTIONS` NAME branch still fires where no type is written at all — which is the only place they can be told apart, since end to end they are redundant) and the dispatch-gates declaration (every separator-less ROOT declared as a subtree, nothing declared this gate does not walk, the over-claim bounded to SKIP_PATHS) all hold.'); + console.log('✓ check-doc-authoring self-test: scope wiring (.claude and the live docs/ corpus in, .claude/worktrees and docs/{audits,handoff,plans} out), detection, the dead-root hard error (red when a ROOT is renamed, green when restored), the empty-scan hard error (red when a root yields nothing and when the whole scan does, green when restored), the published-catalog internal-id rule (red on a planted id in prose, in a fenced comment and in the repo#NNNN spelling, green when removed; hex colours, version numbers, HTTP codes, array indices and the "#1" ordinal all pass; references/ reached, generated artifacts and the internal roots out; the `#` placeholder passes while the concrete ids it replaced stay red, with no exemption to reach for), the spec customer-facing-text internal-id rule (red on an id planted on a LATER line of a concatenated message — the shape a line-oriented census cannot see, proven here — and in a template chain, a positional validator message, the repo#NNNN spelling, a nested strictObject `guidance` prescription, a HOISTED guidance const, a `KeySetGuidance` const consumed only CROSS-MODULE in both the annotated and the `as const satisfies` spelling, a HOISTED refusal message, a `retiredKey()` tombstone, `new Map` and `Object.freeze` guidance tables, `.describe()` prose, and the nested `guidance` of a whole options table written `satisfies StrictObjectOptions`; green when removed; an ADR id on a tombstone, a `.default()` VALUE, `history`/`guidance` outside a strictObject options position, `extraKeys` key names and an inferred local that merely MENTIONS `KeySetGuidance` all pass; test bodies out; the seen floor is PER BUCKET so one matcher rotting while the others carry the total still reds; and the two TYPE ANCHORS are pinned on the predicate itself — the annotation, `satisfies` and `as const satisfies` spellings all read as a strictObject options position while some other satisfied type does not, and the `*_STRICT_OPTIONS` NAME branch still fires where no type is written at all — which is the only place they can be told apart, since end to end they are redundant), the fourth population — customer-facing text BUILT INSIDE A FUNCTION (red on an id in an inline `error: () =>` callback, in a const the callback only dispatches to, inside a `message:` builder function, RETURNED from a tombstone-prescription builder, in a `: StrictObjectOptions` options factory, and in a plain `error:` string; ⛔ the body of an ordinary helper and a local inside a recognised factory stay unswept, because the climb crosses a function only when the FUNCTION sits in a recognised position; and `functionBuilt` carries its own blindness floor, since an unrecognised spelling produces no flag SILENTLY) and the dispatch-gates declaration (every separator-less ROOT declared as a subtree, nothing declared this gate does not walk, the over-claim bounded to SKIP_PATHS) all hold.'); } /** @@ -1377,10 +1504,13 @@ function selfTestRule3(expect) { // A test body carrying an id: in the tree, out of the population. write('packages/spec/src/ui/pin.test.ts', "expect(issue.message).toContain('400 INVALID_FILTER, #5869');"); - // A clean member of each of the three folded-in buckets, so every bucket's + // A clean member of each of the FOUR folded-in buckets, so every bucket's // `seen` floor is satisfied on the green tree and the per-bucket blindness - // assertion below has something to go blind ABOUT. - write('packages/spec/src/data/doc.ts', [ + // assertion below has something to go blind ABOUT. Held as ONE const with + // the fourth-population member last, so the two blindness cases below can + // each drop exactly one bucket from the same baseline rather than each + // carrying a hand-copied variant that drifts. + const DOC_CLEAN_BASE = [ "import { z } from 'zod';", "import { strictObject } from '../shared/strict-object';", "import { retiredKey } from '../shared/retired-key';", @@ -1393,12 +1523,21 @@ function selfTestRule3(expect) { '}, {', " cursor: retiredKey('`cursor` was removed in protocol 17 (ADR-0049). Use `after`.'),", '});', - ].join('\n')); + ]; + // The fourth population, clean: prose BUILT INSIDE a function that itself + // sits in a recognised position. + const DOC_FUNCTION_BUILT = [ + 'export const E = z.string({', + " error: () => 'a machine name is a string — quote it.',", + '});', + ]; + const DOC_CLEAN = [...DOC_CLEAN_BASE, ...DOC_FUNCTION_BUILT].join('\n'); + write('packages/spec/src/data/doc.ts', DOC_CLEAN); const target = write('packages/spec/src/ui/action.zod.ts', CLEAN); const scan = () => { let out = []; - const seen = { message: 0, strictObject: 0, tombstone: 0, describe: 0 }; + const seen = { message: 0, strictObject: 0, tombstone: 0, describe: 0, functionBuilt: 0 }; for (const f of collectSpecSourceFiles()) { const r = findCustomerTextIdViolations(readFileSync(f, 'utf8'), f, ts); out = out.concat(r.violations); @@ -1418,6 +1557,7 @@ function selfTestRule3(expect) { expect('the detector recognised a message string', r.seen.message >= 1, true); expect('the detector recognised a strictObject option string', r.seen.strictObject >= 1, true); expect('the detector recognised a tombstone prescription', r.seen.tombstone >= 1, true); + expect('the detector recognised text built inside a function', r.seen.functionBuilt >= 1, true); expect('the detector recognised a `.describe()` string', r.seen.describe >= 1, true); // Scope: a test body is out, an ordinary source is in. Asserted as a pair @@ -1742,7 +1882,183 @@ function selfTestRule3(expect) { declaredTypeText(decl, ts), ''); } + // ── The FOURTH population: text BUILT INSIDE A FUNCTION ───────────────── + // + // Ruled 2026-08-29, verbatim 「同意」 — the inheritance reaches refusal prose + // built inside `error: () =>` callbacks: same audience, same moment. Each + // case below is the pair, and the negative cases at the end are the reason + // the clause is written as "the FUNCTION must sit in a recognised position" + // rather than "climb through function bodies". + + // RED #14 — the founding shape of this population: an `error:` error map + // written inline as an arrow function, its prose in a `+` chain. + writeFileSync(target, [ + "import { z } from 'zod';", + 'export const S = z.array(ParamSchema, {', + ' error: (iss) => (', + ' isObjectInput(iss)', + " ? '`params` is the parameter DEFINITION array, not a values map. '", + " + 'Use `bodyExtra: { … }` for a static request body (#5777). '", + " + 'Expected an array of ActionParam, received an object.'", + ' : undefined', + ' ),', + '});', + ].join('\n')); + r = scan(); + expect('an id in an `error: () =>` callback is RED', r.violations.length, 1); + expect('the error-callback red names the position', r.violations[0]?.where, 'error: (built in a function)'); + expect('the error-callback red gets its OWN bucket, not `message`', + r.violations[0]?.bucket, 'functionBuilt'); + + // RED #15 — the HOISTED spelling of the same thing, which is how most of + // this tree writes it: the prose lives in a module const and the `error` + // callback only DISPATCHES to it, so the literal is not lexically inside + // any function at all. Without `error:` seeding the sink pass, this const + // is not a sink and its whole body is silently clean. + writeFileSync(target, [ + "import { z } from 'zod';", + 'const PREVIEW_RETIRED =', + " '`preview` was removed in @objectstack/spec 17.0.0 (#11846, ADR-0049) — '", + " + 'use `dev` for the same behaviour.';", + "export const S = z.enum(['dev', 'prod'], {", + " error: (issue) => (issue.input === 'preview' ? PREVIEW_RETIRED : undefined),", + '});', + ].join('\n')); + r = scan(); + expect('an id in a const DISPATCHED from an `error:` callback is RED', r.violations.length, 1); + expect('the hoisted error-map red names its const', r.violations[0]?.where, 'via PREVIEW_RETIRED'); + + // RED #16 — a message BUILDER function referenced from `message:`. The + // literal sits in a function; the function is a text-sink const. + writeFileSync(target, [ + "import { z } from 'zod';", + 'const INLINE_CREDENTIAL_REFUSED = (key: string): string =>', + " `\\`${key}\\` is a credential and is not accepted inline (#7990): bind a secret `", + " + 'with `credentialsRef` instead.';", + 'export const S = z.object({ a: z.string() }).refine((v) => !!v.a, {', + " message: INLINE_CREDENTIAL_REFUSED('password'),", + '});', + ].join('\n')); + r = scan(); + expect('an id inside a message-BUILDER function is RED', r.violations.length, 1); + expect('the builder red names the const it travelled through', + r.violations[0]?.where, 'via INLINE_CREDENTIAL_REFUSED (built in a function)'); + + // RED #17 — a builder whose result is a `retiredKey()` argument, and one + // whose body ends in a `return` rather than a concise arrow body. The + // `return` leg is a separate clause in the climb and was measured live. + writeFileSync(target, [ + "import { z } from 'zod';", + "import { retiredKey } from '../shared/retired-key';", + 'const capRemoved = (key: string) => {', + " return `\\`DriverCapabilities.${key}\\` was removed in 17.0.0 (#4634, ADR-0049).`;", + '};', + 'export const S = z.object({', + " joins: retiredKey(capRemoved('joins')),", + '});', + ].join('\n')); + r = scan(); + expect('an id RETURNED from a tombstone-prescription builder is RED', r.violations.length, 1); + expect('the return-leg red names the const it travelled through', + r.violations[0]?.where, 'via capRemoved (built in a function)'); + + // RED #18 — an options FACTORY declared by its return-type annotation. The + // literal sits at a recognised strictObject key inside a function nothing + // else identifies, so only the annotation makes it reachable. + writeFileSync(target, [ + "import { z } from 'zod';", + "import { strictObject } from '../shared/strict-object';", + "import type { StrictObjectOptions } from '../shared/strict-object';", + 'const navItemSurface = (variant: string): StrictObjectOptions => ({', + ' surface: `this \\`${variant}\\` navigation item`,', + " history: 'Until #4001 these were dropped silently — the entry still parsed.',", + '});', + "export const S = strictObject(navItemSurface('object'), { name: z.string() });", + ].join('\n')); + r = scan(); + expect('an id in a `: StrictObjectOptions` options FACTORY is RED', r.violations.length, 1); + expect('the options-factory red names the position', + r.violations[0]?.where, 'strictObject history (built in a function)'); + + // RED #19 — a plain STRING at `error:`, zod 4's spelling of `message:`. + // Nothing was built in a function, so it is bucketed `message`. + writeFileSync(target, [ + "import { z } from 'zod';", + "export const S = z.string({ error: 'a machine name is a string — quote it (#4286).' });", + ].join('\n')); + r = scan(); + expect('an id in a plain `error:` string is RED', r.violations.length, 1); + expect('the plain `error:` red names the position', r.violations[0]?.where, 'error:'); + expect('a plain `error:` string is a MESSAGE, not function-built', + r.violations[0]?.bucket, 'message'); + // ── Precision: what must NEVER fire ───────────────────────────────────── + // + // ⛔ The clause is NOT "climb through function bodies". These three are the + // difference, and each is the shape an unconditional climb would sweep: + // a helper building a VALUE, a plain local, and a comparison operand inside + // a function that IS recognised. A rule that reports values as prose is one + // authors get disabled (the false-positive lesson this file's Rule 3 header + // carries), so these are pinned as hard as the reds above. + + writeFileSync(target, [ + "import { z } from 'zod';", + '// An ordinary helper. Nothing customer-facing consumes it, so its body', + '// must stay unreachable — this is the whole boundary.', + "const slugFor = (kind: string) => `${kind}-#4286`;", + "const legacyToken = () => 'tag#4286';", + 'export const S = z.object({ a: z.string().default(slugFor("x")) });', + 'export const T = legacyToken;', + ].join('\n')); + expect('precision — an ordinary helper\'s body is NOT swept', scan().violations.length, 0); + + // A function that IS recognised still only yields its recognised POSITIONS. + // A local inside the factory, and a key that is not a STRICT_OPTION_KEY, + // stay unreachable — crossing the function boundary does not turn the body + // into one big text position. + // + // ⚠️ Deliberately NOT asserted here: a conditional or comparison operand + // sitting UNDER a recognised key. The climb has passed through + // `ConditionalExpression` since the rule was written, and this file errs + // toward INCLUSION at a recognised position on purpose — the failure mode + // it guards is silence. That is the pre-existing rule, unchanged by the + // function clause, and pinning the opposite here would be pinning a claim + // the rule does not make. + writeFileSync(target, [ + "import { z } from 'zod';", + "import { strictObject } from '../shared/strict-object';", + "import type { StrictObjectOptions } from '../shared/strict-object';", + 'const navItemSurface = (variant: string): StrictObjectOptions => {', + " const telemetryTag = `nav-${variant}-#4286`;", + ' void telemetryTag;', + ' return {', + ' surface: `this \\`${variant}\\` navigation item`,', + " history: 'an unknown key here was dropped silently.',", + " extraKeys: ['legacyTag4286'],", + ' };', + '};', + "export const S = strictObject(navItemSurface('object'), { name: z.string() });", + ].join('\n')); + expect('precision — a LOCAL inside a recognised options factory is not prose', + scan().violations.length, 0); + + // ⭐ The self-test the ruling required IN THE SAME EDIT: an UNRECOGNISED + // spelling produces NO FLAG, SILENTLY — so the only thing that can speak + // for this population is its own `seen` floor. Prove that floor can fire + // while every other bucket stays populated. Without this, a future edit + // that returns `undefined` at the function boundary again reads as a clean + // tree, which is exactly how these 28 literals went unseen. + writeFileSync(target, CLEAN); + // The same baseline MINUS its one function-built member — the tree an + // unrecognised spelling leaves behind. + write('packages/spec/src/data/doc.ts', DOC_CLEAN_BASE.join('\n')); + r = scan(); + expect('the function-built bucket can go blind on its own (main reds on it)', + r.seen.functionBuilt, 0); + expect('...while the other four stay populated, so the zero above is about THAT clause', + r.seen.message >= 1 && r.seen.strictObject >= 1 + && r.seen.tombstone >= 1 && r.seen.describe >= 1, true); + write('packages/spec/src/data/doc.ts', DOC_CLEAN); // restore the baseline // A validator's VALUE argument is not prose. `.min(3, …)` takes a message; // `.default('#4286')` does not, and an open "any string after position 0" @@ -1818,23 +2134,14 @@ function selfTestRule3(expect) { // the case a total floor cannot see: three buckets still populated, one // gone silent. Emptying only the `.describe()` bucket must still register // as blindness in that bucket while the others stay positive. - write('packages/spec/src/data/doc.ts', [ - "import { z } from 'zod';", - "import { strictObject } from '../shared/strict-object';", - "import { retiredKey } from '../shared/retired-key';", - 'export const T = strictObject({', - " surface: 'this doc',", - " history: 'an unknown key here was dropped silently.',", - " guidance: { where: 'not a doc key — delete it.' },", - '}, {', - " cursor: retiredKey('`cursor` was removed in protocol 17 (ADR-0049). Use `after`.'),", - '});', - ].join('\n')); + write('packages/spec/src/data/doc.ts', + [...DOC_CLEAN_BASE.filter((l) => !l.includes('.describe(')), ...DOC_FUNCTION_BUILT].join('\n')); r = scan(); expect('one bucket can go blind while the others stay populated — describe', r.seen.describe, 0); expect('...and the surviving buckets really did stay positive (so the zero above is about ' + 'that bucket, not an emptied tree)', - r.seen.message >= 1 && r.seen.strictObject >= 1 && r.seen.tombstone >= 1, true); + r.seen.message >= 1 && r.seen.strictObject >= 1 && r.seen.tombstone >= 1 + && r.seen.functionBuilt >= 1, true); // ...and the whole-population version: no recognised string of any kind. writeFileSync(target, "export const S = 1;\n"); @@ -1927,7 +2234,7 @@ function main() { return; } const messageIdViolations = []; - const seenByBucket = { message: 0, strictObject: 0, tombstone: 0, describe: 0 }; + const seenByBucket = { message: 0, strictObject: 0, tombstone: 0, describe: 0, functionBuilt: 0 }; for (const file of specSources) { const r = findCustomerTextIdViolations(readFileSync(file, 'utf8'), file, ts); messageIdViolations.push(...r.violations); @@ -1983,11 +2290,12 @@ function main() { + `\n\n ${blindBuckets.join(', ')}` + `\n\nso "no violations" below would be a verdict on a population this run never located.` + `\n\nThat is the dormant-gate shape, not a clean tree: the spec really does declare refusal` - + `\nprose, unknown-key guidance, tombstone prescriptions and \`.describe()\` docs, so a zero` - + `\nhere means the DETECTOR stopped matching how one of them is spelled — an options-object` - + `\nkey renamed away from \`message\`, a new validator helper, a \`strictObject\` wrapper under` - + `\na new name, a guidance table moved behind a helper \`customerTextPosition()\` does not` - + `\nclimb through.` + + `\nprose, unknown-key guidance, tombstone prescriptions, \`.describe()\` docs AND prose built` + + `\ninside \`error: () =>\` callbacks and message-builder functions, so a zero here means the` + + `\nDETECTOR stopped matching how one of them is spelled — an options-object key renamed away` + + `\nfrom \`message\`, a new validator helper, a \`strictObject\` wrapper under a new name, a` + + `\nguidance table moved behind a helper \`customerTextPosition()\` does not climb through, or` + + `\n— for \`functionBuilt\` — the function-boundary clause silently back to \`undefined\`.` + `\n\nThe floor is PER BUCKET and not on the total, deliberately: \`.describe()\` alone would` + `\nhold a total positive forever while the \`guidance\` matcher rotted unseen.` + `\n\nFix \`customerTextPosition()\` / \`collectTextSinkConsts()\` / STRICT_OPTION_KEYS /` @@ -2041,7 +2349,8 @@ function main() { `✓ doc authoring guard: ${totalTextSeen} customer-facing string(s) across ` + `${specSources.length} spec sources clean — no internal issue-id references ` + `(message ${seenByBucket.message} · strictObject ${seenByBucket.strictObject} · ` - + `tombstone ${seenByBucket.tombstone} · describe ${seenByBucket.describe}).`, + + `tombstone ${seenByBucket.tombstone} · describe ${seenByBucket.describe} · ` + + `functionBuilt ${seenByBucket.functionBuilt}).`, ); }