From c2a9936356d92bc455a03d6b6e355aefabf4583f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 05:33:42 +0000 Subject: [PATCH] docs(spec): re-anchor the dead tracker citations in seven more liveness ledgers to the commits that decided them 123 note sites across the datasource, api, query, object, email_template, mapping and webhook ledgers cited GitHub issues that answer 404. Each now names the commit that decided it, or says the decision in words where the number alone carried it. The manifest permissions note gains the commit that recorded its structured arm's zero. Note strings only: every row's status, evidence, proof and verifiedAt is unchanged. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- ...34-liveness-ledger-provenance-anchors-2.md | 16 +++++++ packages/spec/liveness/api.json | 44 ++++++++--------- packages/spec/liveness/datasource.json | 48 +++++++++---------- packages/spec/liveness/email_template.json | 28 +++++------ packages/spec/liveness/manifest.json | 2 +- packages/spec/liveness/mapping.json | 14 +++--- packages/spec/liveness/object.json | 30 ++++++------ packages/spec/liveness/query.json | 34 ++++++------- packages/spec/liveness/webhook.json | 24 +++++----- 9 files changed, 128 insertions(+), 112 deletions(-) create mode 100644 .changeset/20234-liveness-ledger-provenance-anchors-2.md diff --git a/.changeset/20234-liveness-ledger-provenance-anchors-2.md b/.changeset/20234-liveness-ledger-provenance-anchors-2.md new file mode 100644 index 00000000000..581104bb5a7 --- /dev/null +++ b/.changeset/20234-liveness-ledger-provenance-anchors-2.md @@ -0,0 +1,16 @@ +--- +'@objectstack/spec': patch +--- + +Notes in seven more liveness ledgers cite the commit that decided them, or say the decision in words, instead of a tracker number that no longer resolves + +Clause-②: no + +Notes in the `datasource`, `api`, `query`, `object`, `email_template`, `mapping` and +`webhook` ledgers named GitHub issues that no longer exist, so a reader could not tell why +a row carries its verdict. Each such note now either names the commit that made the +decision or, where the number alone carried the meaning, says what was decided. The +`manifest` ledger's `permissions` note also names the commit that recorded its structured +arm's zero apart. The `liveness/` ledgers ship in this package's tarball, which is why +this is a release note at all. Note text only: no row's status, evidence, proof or date +changes, and no schema, export or runtime behaviour changes. diff --git a/packages/spec/liveness/api.json b/packages/spec/liveness/api.json index cf7fd5129d5..547d1238c1c 100644 --- a/packages/spec/liveness/api.json +++ b/packages/spec/liveness/api.json @@ -1,48 +1,48 @@ { "type": "api", - "_note": "ApiEndpointSchema (packages/spec/src/api/endpoint.zod.ts). Seeded 2026-08-04 (#5271, part of #5206) in the same change that made `api` a REGISTERED metadata type — governance and registration land together, which is the whole lesson of `datasource` (#4487): it went ungoverned long enough to accumulate six inert keys that had to be found by hand. Consumers, by layer: the MATCHER (`packages/metadata/src/endpoint-matcher.ts`) indexes `name`/`path`/`method`; the EXECUTOR (`packages/runtime/src/endpoint-executor.ts`) dispatches on `type` and reads `target` / `objectParams`; the POLICY chain (`packages/runtime/src/endpoint-policy.ts` + `security/inbound-rate-limit.ts`) enforces `authRequired` / `rateLimit` / `cacheTtl`; the MAPPING layer (`packages/runtime/src/api-mapping.ts`) applies `inputMapping` / `outputMapping`; and the OpenAPI enrichment (`packages/rest/src/openapi-endpoints.ts`) emits `summary` / `description`. This is the cheapest possible moment to govern the type: #5040's E-series built every one of those consumers and all of it is on main, so each key has a real evidence path today rather than a promise. Nothing here is `dead`. The two `transform` keys are `planned`, not `dead`, and the distinction is load-bearing — see their notes. No `api` property is a bound HIGH_RISK class in proof-registry.mts, so no entry carries a `proof`; none is invented to look thorough. The protection-envelope keys (`_lock*`, `_provenance`, `_packageId/Version`) are auto-classified live by the gate (ADR-0010) and are deliberately absent from this file. 2026-08-28 (#13003): every citation in this file was re-anchored to its consuming symbol. Worth recording about THIS file specifically — the layer map above stayed true for 24 days while nearly every line number under it went stale: the files did not move, the functions did not move, the LINES did, and the ledger cited lines.", + "_note": "ApiEndpointSchema (packages/spec/src/api/endpoint.zod.ts). Seeded 2026-08-04 (#5271, part of #5206) in the same change that made `api` a REGISTERED metadata type — governance and registration land together, which is the whole lesson of `datasource` (#4487): it went ungoverned long enough to accumulate six inert keys that had to be found by hand. Consumers, by layer: the MATCHER (`packages/metadata/src/endpoint-matcher.ts`) indexes `name`/`path`/`method`; the EXECUTOR (`packages/runtime/src/endpoint-executor.ts`) dispatches on `type` and reads `target` / `objectParams`; the POLICY chain (`packages/runtime/src/endpoint-policy.ts` + `security/inbound-rate-limit.ts`) enforces `authRequired` / `rateLimit` / `cacheTtl`; the MAPPING layer (`packages/runtime/src/api-mapping.ts`) applies `inputMapping` / `outputMapping`; and the OpenAPI enrichment (`packages/rest/src/openapi-endpoints.ts`) emits `summary` / `description`. This is the cheapest possible moment to govern the type: #5040's E-series built every one of those consumers and all of it is on main, so each key has a real evidence path today rather than a promise. Nothing here is `dead`. The two `transform` keys are `planned`, not `dead`, and the distinction is load-bearing — see their notes. No `api` property is a bound HIGH_RISK class in proof-registry.mts, so no entry carries a `proof`; none is invented to look thorough. The protection-envelope keys (`_lock*`, `_provenance`, `_packageId/Version`) are auto-classified live by the gate (ADR-0010) and are deliberately absent from this file. 2026-08-28 (commit 8cb96ec41): every citation in this file was re-anchored to its consuming symbol. Worth recording about THIS file specifically — the layer map above stayed true for 24 days while nearly every line number under it went stale: the files did not move, the functions did not move, the LINES did, and the ledger cited lines.", "props": { "name": { "status": "live", "evidence": "packages/metadata/src/endpoint-matcher.ts#buildEndpointIndex (`endpoint.name < incumbent.name` — the duplicate-claim tiebreaker, plus the two exclusion messages that name the offending item)", "verifiedAt": "2026-08-28", - "note": "The metadata item key, and the tiebreaker with real consequences: when two stored declarations claim the same METHOD + normalized path, the lexicographically-first `name` keeps the route and the loser is named at `error` level. Also the subject of every publish-gate and matcher message, so a rename changes what an author is told. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `endpoint-matcher.ts:210` is a real read of `endpoint.name`, but of the WRONG one: it is the publish-gate exclusion message, ~22 lines above the tiebreaker this note is about. A citation that lands on a different read of the same key in the same file is the least visible rot there is — it survives the existence check, the line bound and the key-mention check by construction, and it reads correct to a human skimming for the key. Anchored to the function that holds both reads. Re-closed by hand against 93ea19bca." + "note": "The metadata item key, and the tiebreaker with real consequences: when two stored declarations claim the same METHOD + normalized path, the lexicographically-first `name` keeps the route and the loser is named at `error` level. Also the subject of every publish-gate and matcher message, so a rename changes what an author is told. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `endpoint-matcher.ts:210` is a real read of `endpoint.name`, but of the WRONG one: it is the publish-gate exclusion message, ~22 lines above the tiebreaker this note is about. A citation that lands on a different read of the same key in the same file is the least visible rot there is — it survives the existence check, the line bound and the key-mention check by construction, and it reads correct to a human skimming for the key. Anchored to the function that holds both reads. Re-closed by hand against 93ea19bca." }, "path": { "status": "live", "evidence": "packages/metadata/src/endpoint-matcher.ts#endpointIndexKey (`normalizeEndpointPath(path)` — half the index key); packages/metadata/src/endpoint-matcher.ts#buildEndpointIndex (`endpointIndexKey(endpoint.method, endpoint.path)` — the call site that puts a declaration on a route)", "verifiedAt": "2026-08-28", - "note": "Half the index key. Compared as a WHOLE STRING with exactly one trailing slash trimmed — no percent-decoding, no Unicode normalization, no case folding in 17.x. ADR-0121 D1 additionally confines it to the stack's `apps//` carve-out at publish; that is a gate, not a second consumer. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `endpoint-matcher.ts:199` had rotted onto the bare `continue;` that ends the schema-parse-failure branch. The evidence's own parenthetical named `endpointIndexKey`, which sits at ~:151 and never moved; only the number was wrong, and nothing in the ledger compares a citation's line against the symbol its prose names. Re-closed by hand against 93ea19bca." + "note": "Half the index key. Compared as a WHOLE STRING with exactly one trailing slash trimmed — no percent-decoding, no Unicode normalization, no case folding in 17.x. ADR-0121 D1 additionally confines it to the stack's `apps//` carve-out at publish; that is a gate, not a second consumer. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `endpoint-matcher.ts:199` had rotted onto the bare `continue;` that ends the schema-parse-failure branch. The evidence's own parenthetical named `endpointIndexKey`, which sits at ~:151 and never moved; only the number was wrong, and nothing in the ledger compares a citation's line against the symbol its prose names. Re-closed by hand against 93ea19bca." }, "method": { "status": "live", "evidence": "packages/metadata/src/endpoint-matcher.ts#normalizeEndpointMethod (upper-cased before comparison); packages/metadata/src/endpoint-matcher.ts#endpointIndexKey (the other half of the index key)", "verifiedAt": "2026-08-28", - "note": "The other half of the index key, upper-cased before comparison. Also read by the policy layer: `cacheTtl` is GET-only, so `method` decides whether a Cache-Control header can be emitted at all (`endpoint-policy.ts#computeCacheControl`, cited under `cacheTtl`). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `endpoint-matcher.ts:115` had rotted onto a module docblock line about the store-read contract, ~19 lines above `normalizeEndpointMethod`; the `:199` leg rotted with `path`'s onto the same `continue;`. Re-closed by hand against 93ea19bca." + "note": "The other half of the index key, upper-cased before comparison. Also read by the policy layer: `cacheTtl` is GET-only, so `method` decides whether a Cache-Control header can be emitted at all (`endpoint-policy.ts#computeCacheControl`, cited under `cacheTtl`). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `endpoint-matcher.ts:115` had rotted onto a module docblock line about the store-read contract, ~19 lines above `normalizeEndpointMethod`; the `:199` leg rotted with `path`'s onto the same `continue;`. Re-closed by hand against 93ea19bca." }, "summary": { "status": "live", "evidence": "packages/rest/src/openapi-endpoints.ts#buildEndpointOperation (`if (endpoint.summary) operation.summary = endpoint.summary`)", "verifiedAt": "2026-08-28", - "note": "Emitted as the OpenAPI operation's `summary` (#5040 E6). Documentation-shaped rather than behavioural, but it genuinely reaches a served artifact — /openapi.json — so it is live, not decorative. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `openapi-endpoints.ts:214` had rotted onto `successStatus: 501` inside `httpFactsFor`, the unsupported-type branch: a line about what the runtime REFUSES to execute, cited as evidence that a documentation key is emitted. ~26 lines from the read. Re-closed by hand against 93ea19bca." + "note": "Emitted as the OpenAPI operation's `summary` (#5040 E6). Documentation-shaped rather than behavioural, but it genuinely reaches a served artifact — /openapi.json — so it is live, not decorative. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `openapi-endpoints.ts:214` had rotted onto `successStatus: 501` inside `httpFactsFor`, the unsupported-type branch: a line about what the runtime REFUSES to execute, cited as evidence that a documentation key is emitted. ~26 lines from the read. Re-closed by hand against 93ea19bca." }, "description": { "status": "live", "evidence": "packages/rest/src/openapi-endpoints.ts#buildEndpointOperation (`if (endpoint.description) operation.description = endpoint.description`)", "verifiedAt": "2026-08-28", - "note": "Emitted as the OpenAPI operation's `description` (#5040 E6). Same reasoning as `summary`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `openapi-endpoints.ts:215` had rotted onto `requiresRecordId: false` in the same unsupported-type branch as `summary`'s, one line below it. The pair rotted together and by the same distance, which is what a citation pair written on one day against one function does when the function moves. Re-closed by hand against 93ea19bca." + "note": "Emitted as the OpenAPI operation's `description` (#5040 E6). Same reasoning as `summary`. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `openapi-endpoints.ts:215` had rotted onto `requiresRecordId: false` in the same unsupported-type branch as `summary`'s, one line below it. The pair rotted together and by the same distance, which is what a citation pair written on one day against one function does when the function moves. Re-closed by hand against 93ea19bca." }, "type": { "status": "live", "evidence": "packages/runtime/src/endpoint-executor.ts#planEndpointTarget (`endpoint.type === 'object_operation'` / `=== 'flow'`, and the trailing `unsupported` plan that answers 501 for everything else)", "verifiedAt": "2026-08-28", - "note": "The executor's dispatch key. NOTE the enum is wider than the runtime: only 'object_operation' and 'flow' execute; 'script' and 'proxy' are REFUSED at publish (endpoint-publish-gate.ts targetGate) and again by `planEndpointTarget`. The KEY is live — it is the two rejected VALUES that are not, which is why this is a `live` row and not a qualified one. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:216` had rotted onto the docblock ABOVE `planEndpointTarget` (a sentence about SSRF/egress policy), and `:232` onto a hint string inside it. Both were inside the right neighbourhood and neither named a read — the docblock case is the one worth naming: a citation onto the prose that EXPLAINS a function is indistinguishable, to every check the ledger has, from a citation onto the function. Re-closed by hand against 93ea19bca." + "note": "The executor's dispatch key. NOTE the enum is wider than the runtime: only 'object_operation' and 'flow' execute; 'script' and 'proxy' are REFUSED at publish (endpoint-publish-gate.ts targetGate) and again by `planEndpointTarget`. The KEY is live — it is the two rejected VALUES that are not, which is why this is a `live` row and not a qualified one. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:216` had rotted onto the docblock ABOVE `planEndpointTarget` (a sentence about SSRF/egress policy), and `:232` onto a hint string inside it. Both were inside the right neighbourhood and neither named a read — the docblock case is the one worth naming: a citation onto the prose that EXPLAINS a function is indistinguishable, to every check the ledger has, from a citation onto the function. Re-closed by hand against 93ea19bca." }, "target": { "status": "live", "evidence": "packages/runtime/src/endpoint-executor.ts#planEndpointTarget (`const flow = endpoint.target` on the flow branch; an empty one returns an `unsupported` plan naming the endpoint)", "verifiedAt": "2026-08-28", - "note": "The flow name a `type: 'flow'` endpoint triggers, delegated through the same automation pipeline as POST /automation//trigger (ADR-0121 D5 — no second execution dialect). For `object_operation` the routing comes from `objectParams` instead, so an empty `target` is only refused on the flow branch. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `endpoint-executor.ts:233` had rotted onto a bare `};` closing the object_operation refusal literal, i.e. onto punctuation inside the sibling branch. Re-closed by hand against 93ea19bca." + "note": "The flow name a `type: 'flow'` endpoint triggers, delegated through the same automation pipeline as POST /automation//trigger (ADR-0121 D5 — no second execution dialect). For `object_operation` the routing comes from `objectParams` instead, so an empty `target` is only refused on the flow branch. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `endpoint-executor.ts:233` had rotted onto a bare `};` closing the object_operation refusal literal, i.e. onto punctuation inside the sibling branch. Re-closed by hand against 93ea19bca." }, "objectParams": { "children": { @@ -50,13 +50,13 @@ "status": "live", "evidence": "packages/runtime/src/endpoint-executor.ts#planEndpointTarget (`const object = endpoint.objectParams?.object` — read from the DECLARATION and carried on the plan); packages/runtime/src/endpoint-executor.ts#executeObjectOperation (`const { object, operation } = plan` — every delegated callData argument takes it from there, never from the request)", "verifiedAt": "2026-08-28", - "note": "The object an `object_operation` endpoint reads or writes. What matters for security: `object` comes from the DECLARATION, never from the request, so a caller cannot redirect a declared endpoint at another object (#3946 was the mirror-image bug on /data). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — BOTH legs were docblock lines: `:217` in `planEndpointTarget`'s header, `:394` in `executeObjectOperation`'s. The note's own claim (\"Line 394 is the one that matters for security\") was true of the PROSE at :394 and false of any read — the security property is real and is enforced ~15 lines lower, in the destructure. An entry whose evidence cites the documentation of its own claim is the purest form of the unfalsifiable pointer this migration exists to remove. Re-closed by hand against 93ea19bca." + "note": "The object an `object_operation` endpoint reads or writes. What matters for security: `object` comes from the DECLARATION, never from the request, so a caller cannot redirect a declared endpoint at another object (#3946 was the mirror-image bug on /data). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — BOTH legs were docblock lines: `:217` in `planEndpointTarget`'s header, `:394` in `executeObjectOperation`'s. The note's own claim (\"Line 394 is the one that matters for security\") was true of the PROSE at :394 and false of any read — the security property is real and is enforced ~15 lines lower, in the destructure. An entry whose evidence cites the documentation of its own claim is the purest form of the unfalsifiable pointer this migration exists to remove. Re-closed by hand against 93ea19bca." }, "operation": { "status": "live", "evidence": "packages/runtime/src/endpoint-executor.ts#planEndpointTarget (`const operation = endpoint.objectParams?.operation`; an incomplete pair is refused before anything runs); packages/runtime/src/endpoint-executor.ts#executeObjectOperation (the find/get/create/update/delete branch, each delegating to the callData shape /data uses); packages/runtime/src/endpoint-executor.ts#requireRecordId (names the operation in the 400 when `?id=` is missing on get/update/delete)", "verifiedAt": "2026-08-28", - "note": "find / get / create / update / delete, delegated to the same callData pipeline the built-in /data route uses. Also decides whether a request body is read at all, which is what makes `inputMapping` inert on the bodyless operations (publish refuses that combination — #5111). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:218` was a docblock line, `:368` a comment inside `requireRecordId` about the error constructor rather than the read two lines below it. Re-closed by hand against 93ea19bca." + "note": "find / get / create / update / delete, delegated to the same callData pipeline the built-in /data route uses. Also decides whether a request body is read at all, which is what makes `inputMapping` inert on the bodyless operations (publish refuses that combination — #5111). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:218` was a docblock line, `:368` a comment inside `requireRecordId` about the error constructor rather than the read two lines below it. Re-closed by hand against 93ea19bca." } } }, @@ -66,19 +66,19 @@ "status": "live", "evidence": "packages/runtime/src/api-mapping.ts#project (`readPath(source, splitPath(entry.source)!)` — the dot-path read); packages/runtime/src/api-mapping.ts#applyInputMapping (the caller: no entries ⇒ the caller's own body rides through by reference)", "verifiedAt": "2026-08-28", - "note": "Dot path read out of the request body, before delegation — so a mapping can never buy a caller past `authRequired` or the rate limiter. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `api-mapping.ts:318` landed on `const projected: … = {}`, the accumulator two lines above the read, and the evidence's parenthetical named `readPath`, which is a DIFFERENT function ~138 lines up. `project` is the symbol that reads `entry.source`; `readPath` only walks segments it is handed. Anchoring makes the distinction the pointer already claimed. Re-closed by hand against 93ea19bca." + "note": "Dot path read out of the request body, before delegation — so a mapping can never buy a caller past `authRequired` or the rate limiter. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `api-mapping.ts:318` landed on `const projected: … = {}`, the accumulator two lines above the read, and the evidence's parenthetical named `readPath`, which is a DIFFERENT function ~138 lines up. `project` is the symbol that reads `entry.source`; `readPath` only walks segments it is handed. Anchoring makes the distinction the pointer already claimed. Re-closed by hand against 93ea19bca." }, "target": { "status": "live", "evidence": "packages/runtime/src/api-mapping.ts#project (`writePath(projected, splitPath(entry.target)!, value)`); packages/runtime/src/api-mapping.ts#mappingDeclarationRejection (prototype keys and colliding/prefixing targets are REFUSED at declaration rather than silently discarded)", "verifiedAt": "2026-08-28", - "note": "Dot path written into the params the executor sees. Prototype keys and colliding targets are refused rather than silently discarded. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `api-mapping.ts:322` landed on the comment \"a mapping projects, it does not assert that a field was supplied\", two lines above the write. The note's `:291` collision pointer was never in `evidence`, so nothing bounded it; it is now an anchor on the function that performs the refusal. Re-closed by hand against 93ea19bca." + "note": "Dot path written into the params the executor sees. Prototype keys and colliding targets are refused rather than silently discarded. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `api-mapping.ts:322` landed on the comment \"a mapping projects, it does not assert that a field was supplied\", two lines above the write. The note's `:291` collision pointer was never in `evidence`, so nothing bounded it; it is now an anchor on the function that performs the refusal. Re-closed by hand against 93ea19bca." }, "transform": { "status": "planned", "evidence": "packages/runtime/src/api-mapping.ts#mappingDeclarationRejection (`if (entry.transform !== undefined) return reject(…)` — parsed, then LOUDLY refused, naming the entry and what to do instead)", "verifiedAt": "2026-08-28", - "note": "PLANNED, deliberately not `dead`, and the difference is the point. `dead` in this ledger means 'parsed, no consumer' — a silent no-op. `transform` is the opposite: it is parsed and then LOUDLY REFUSED, at publish (endpoint-publish-gate.ts mappingGate) and again at runtime, because there is no transformation-function registry anywhere in the platform. An author who writes it is told so and told what to do instead. It stays in the vocabulary rather than being removed because admitting it needs a function registry AND a sandbox ruling of its own (#5040 §3.4) — that is a design decision to make, not a key to quietly delete. Nothing enforce-or-remove has to chase: it is already refused. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `api-mapping.ts:259` landed on `const at = …`, the diagnostic-label line two above the refusal. For a `planned` entry the anchor earns more than for a `live` one: the whole verdict is \"this key is refused, not ignored\", and the refusal disappearing is exactly the change that must go red. Re-closed by hand against 93ea19bca." + "note": "PLANNED, deliberately not `dead`, and the difference is the point. `dead` in this ledger means 'parsed, no consumer' — a silent no-op. `transform` is the opposite: it is parsed and then LOUDLY REFUSED, at publish (endpoint-publish-gate.ts mappingGate) and again at runtime, because there is no transformation-function registry anywhere in the platform. An author who writes it is told so and told what to do instead. It stays in the vocabulary rather than being removed because admitting it needs a function registry AND a sandbox ruling of its own (#5040 §3.4) — that is a design decision to make, not a key to quietly delete. Nothing enforce-or-remove has to chase: it is already refused. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `api-mapping.ts:259` landed on `const at = …`, the diagnostic-label line two above the refusal. For a `planned` entry the anchor earns more than for a `live` one: the whole verdict is \"this key is refused, not ignored\", and the refusal disappearing is exactly the change that must go red. Re-closed by hand against 93ea19bca." } } }, @@ -88,19 +88,19 @@ "status": "live", "evidence": "packages/runtime/src/api-mapping.ts#project (the same dot-path read — one code path serves both mapping keys); packages/runtime/src/api-mapping.ts#applyOutputMapping (projects `data` out of the `{ success, data, meta }` envelope, or the body itself when it is not one)", "verifiedAt": "2026-08-28", - "note": "Dot path read out of the SUCCESS body only. An error answer is never remapped — a projection able to reshape a 401/429/500 into data could disguise a failure as a result (the caller applies it on success only, `api-endpoint-step.ts`). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:318` rotted with its `inputMapping` twin, and the parenthetical's `applyOutputMapping at :358` named a docblock line, not the function (which begins at ~:360). Re-closed by hand against 93ea19bca." + "note": "Dot path read out of the SUCCESS body only. An error answer is never remapped — a projection able to reshape a 401/429/500 into data could disguise a failure as a result (the caller applies it on success only, `api-endpoint-step.ts`). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:318` rotted with its `inputMapping` twin, and the parenthetical's `applyOutputMapping at :358` named a docblock line, not the function (which begins at ~:360). Re-closed by hand against 93ea19bca." }, "target": { "status": "live", "evidence": "packages/runtime/src/api-mapping.ts#project (the same dot-path write); packages/runtime/src/api-mapping.ts#applyOutputMapping (validated BEFORE the envelope is rebuilt, and every non-`data` member rides through untouched)", "verifiedAt": "2026-08-28", - "note": "Dot path written into the response body. Validated BEFORE delegation, so a broken projection cannot let a `create` insert a record and then fail to answer. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:322` rotted with its `inputMapping` twin onto a comment, and `applyOutputMapping at :358` named the docblock rather than the function. Re-closed by hand against 93ea19bca." + "note": "Dot path written into the response body. Validated BEFORE delegation, so a broken projection cannot let a `create` insert a record and then fail to answer. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:322` rotted with its `inputMapping` twin onto a comment, and `applyOutputMapping at :358` named the docblock rather than the function. Re-closed by hand against 93ea19bca." }, "transform": { "status": "planned", "evidence": "packages/runtime/src/api-mapping.ts#mappingDeclarationRejection (the same walk, called once per mapping key — `outputMapping[i].transform` is refused by the identical branch)", "verifiedAt": "2026-08-28", - "note": "Same key, same gate, same reasoning as `inputMapping.transform` — the mapping validator walks both arrays through one code path. See that entry. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — same `:259` rot as its twin. Anchoring both to the one function is now literally true of the code rather than an assertion in prose. Re-closed by hand against 93ea19bca." + "note": "Same key, same gate, same reasoning as `inputMapping.transform` — the mapping validator walks both arrays through one code path. See that entry. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — same `:259` rot as its twin. Anchoring both to the one function is now literally true of the code rather than an assertion in prose. Re-closed by hand against 93ea19bca." } } }, @@ -108,7 +108,7 @@ "status": "live", "evidence": "packages/runtime/src/endpoint-policy.ts#applyEndpointPolicies (`if (endpoint.authRequired !== false) { if (shouldDenyAnonymous(…)) return anonymousDenial(); }` — step ② of the chain); packages/runtime/src/endpoint-policy.ts#anonymousDenial (the 401 envelope it returns)", "verifiedAt": "2026-08-28", - "note": "The default-deny gate, and the key whose history is the reason this whole surface was refused for a release: #4936 measured it parsing green and gating NOTHING (no route was mounted, no matcher existed) — declared security that enforced nothing, i.e. false compliance. It is enforced now. It DEFAULTS to true, so `false` is the only way to open an anonymous entry point, and ADR-0121 D6 pairs that with a mandatory armed `rateLimit`. Note the deliberate omission at the call site: no path argument is passed to `shouldDenyAnonymous`, so a declared `/…/auth/…` path can never exempt itself from its own default deny. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `endpoint-policy.ts:354` had rotted onto `message: 'Rate limit exceeded…'`, i.e. onto step ①'s 429 body: a SECURITY key whose only pointer named a DIFFERENT key's enforcement, ~21 lines above its own. This is the shape batch 2 recorded on `permission.writeScope` and it is worth naming twice — inside one policy chain every branch reads as policy code, so nothing about the wrong line looks wrong. Re-closed by hand against 93ea19bca." + "note": "The default-deny gate, and the key whose history is the reason this whole surface was refused for a release: #4936 measured it parsing green and gating NOTHING (no route was mounted, no matcher existed) — declared security that enforced nothing, i.e. false compliance. It is enforced now. It DEFAULTS to true, so `false` is the only way to open an anonymous entry point, and ADR-0121 D6 pairs that with a mandatory armed `rateLimit`. Note the deliberate omission at the call site: no path argument is passed to `shouldDenyAnonymous`, so a declared `/…/auth/…` path can never exempt itself from its own default deny. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `endpoint-policy.ts:354` had rotted onto `message: 'Rate limit exceeded…'`, i.e. onto step ①'s 429 body: a SECURITY key whose only pointer named a DIFFERENT key's enforcement, ~21 lines above its own. This is the shape batch 2 recorded on `permission.writeScope` and it is worth naming twice — inside one policy chain every branch reads as policy code, so nothing about the wrong line looks wrong. Re-closed by hand against 93ea19bca." }, "rateLimit": { "children": { @@ -116,19 +116,19 @@ "status": "live", "evidence": "packages/runtime/src/security/inbound-rate-limit.ts#deriveBucketConfig (`if (!budget?.enabled) return null` — an unarmed budget yields NO limiter at all); consumed via packages/runtime/src/endpoint-policy.ts#createEndpointRateLimiterRegistry (`deriveBucketConfig(endpoint.rateLimit)` in `limiterFor`, rethrowing with the authoring noun the reader edits)", "verifiedAt": "2026-08-28", - "note": "ARMS the budget, and it defaults to FALSE — so `rateLimit: { windowMs, maxRequests }` written without it parses into a budget that meters nothing. That is exactly why ADR-0121 D6's gate tests `enabled === true` rather than the presence of `rateLimit`: a presence check would be vacuous. 2026-08-28: RE-ANCHORED (#13003), one leg REPOINTED — the `inbound-rate-limit.ts:88` leg was ACCURATE and is a grammar migration; the `endpoint-policy.ts:148` leg had rotted onto a BLANK LINE. A blank line is the one rot a reader can catch instantly and no check can: it exists, it is in range, and the file names the key elsewhere. Re-closed by hand against 93ea19bca." + "note": "ARMS the budget, and it defaults to FALSE — so `rateLimit: { windowMs, maxRequests }` written without it parses into a budget that meters nothing. That is exactly why ADR-0121 D6's gate tests `enabled === true` rather than the presence of `rateLimit`: a presence check would be vacuous. 2026-08-28: RE-ANCHORED (commit 8cb96ec41), one leg REPOINTED — the `inbound-rate-limit.ts:88` leg was ACCURATE and is a grammar migration; the `endpoint-policy.ts:148` leg had rotted onto a BLANK LINE. A blank line is the one rot a reader can catch instantly and no check can: it exists, it is in range, and the file names the key elsewhere. Re-closed by hand against 93ea19bca." }, "windowMs": { "status": "live", "evidence": "packages/runtime/src/security/inbound-rate-limit.ts#deriveBucketConfig (`const windowMs = budget.windowMs ?? 60_000`, then `refillPerSec = maxRequests / (windowMs / 1000)`; a zero-or-negative window THROWS rather than silently disabling metering)", "verifiedAt": "2026-08-28", - "note": "Budget window in milliseconds; with `maxRequests` it sizes the token bucket. An armed-but-unusable budget fails CLOSED rather than silently disabling metering. 2026-08-28: RE-ANCHORED (#13003) — `:91` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." + "note": "Budget window in milliseconds; with `maxRequests` it sizes the token bucket. An armed-but-unusable budget fails CLOSED rather than silently disabling metering. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) — `:91` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." }, "maxRequests": { "status": "live", "evidence": "packages/runtime/src/security/inbound-rate-limit.ts#deriveBucketConfig (`const maxRequests = budget.maxRequests ?? 100` → the bucket's `capacity`; zero or negative THROWS, naming `enabled: false` as the way to turn metering off)", "verifiedAt": "2026-08-28", - "note": "Bucket capacity. Same fail-closed rule as `windowMs`: 'the author asked for metering and got none' is the worse outcome, so an impossible budget errors rather than passing traffic. 2026-08-28: RE-ANCHORED (#13003) — `:90` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." + "note": "Bucket capacity. Same fail-closed rule as `windowMs`: 'the author asked for metering and got none' is the worse outcome, so an impossible budget errors rather than passing traffic. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) — `:90` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." } } }, @@ -136,12 +136,12 @@ "status": "live", "evidence": "packages/runtime/src/endpoint-policy.ts#computeCacheControl (`const ttl = endpoint.cacheTtlSeconds`; absent ⇒ no header, non-GET ⇒ no header plus a warn naming the endpoint); packages/runtime/src/endpoint-policy.ts#applyEndpointPolicies (step ③ — the returned header is attached to the SUCCESS answer only)", "verifiedAt": "2026-08-28", - "note": "Seconds, emitted as a Cache-Control response header and nothing more (#5091 narrowed the original design to header semantics only — there is no response store). Applied to SUCCESSFUL answers only: telling a client to reuse a 401/429/5xx for half a minute is worse than saying nothing. GET-only — on any other method publish refuses it (#5040 §3.3) and the runtime warns instead of emitting. 2026-08-28: RE-ANCHORED (#13003) and PROSE CORRECTED — the line `:252` was ACCURATE (it is the function's own declaration line), but the parenthetical named `cacheControlHeader`, which is not a symbol anywhere in `packages/**` — the function is `computeCacheControl`, and the only other occurrence of the old spelling is a docblock table in `endpoint-publish-gate.ts` that names it and `endpointRateLimiterRegistry` (also stale — `createEndpointRateLimiterRegistry`) as this file's exports, i.e. the same rename went unpropagated in two places. Line-accurate and name-wrong is the combination nothing in the ledger could flag, because no check has ever compared a citation's prose against its position — and it is the combination an anchor removes by construction, since the name IS the pointer now. Re-closed by hand against 93ea19bca. RENAMED 2026-09-05 (#15677, #14478 ruling B) from `cacheTtl`: the unit lived only in the describe prose. `computeCacheControl` moved from `endpoint.cacheTtl` to `endpoint.cacheTtlSeconds` in the same PR at the same magnitude, and the publish gate's issue path moved with it (`apis.N.cacheTtlSeconds`). Anchors carried over from the `cacheTtl` row (re-anchored #13003)." + "note": "Seconds, emitted as a Cache-Control response header and nothing more (#5091 narrowed the original design to header semantics only — there is no response store). Applied to SUCCESSFUL answers only: telling a client to reuse a 401/429/5xx for half a minute is worse than saying nothing. GET-only — on any other method publish refuses it (#5040 §3.3) and the runtime warns instead of emitting. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and PROSE CORRECTED — the line `:252` was ACCURATE (it is the function's own declaration line), but the parenthetical named `cacheControlHeader`, which is not a symbol anywhere in `packages/**` — the function is `computeCacheControl`, and the only other occurrence of the old spelling is a docblock table in `endpoint-publish-gate.ts` that names it and `endpointRateLimiterRegistry` (also stale — `createEndpointRateLimiterRegistry`) as this file's exports, i.e. the same rename went unpropagated in two places. Line-accurate and name-wrong is the combination nothing in the ledger could flag, because no check has ever compared a citation's prose against its position — and it is the combination an anchor removes by construction, since the name IS the pointer now. Re-closed by hand against 93ea19bca. RENAMED 2026-09-05 (#15677, #14478 ruling B) from `cacheTtl`: the unit lived only in the describe prose. `computeCacheControl` moved from `endpoint.cacheTtl` to `endpoint.cacheTtlSeconds` in the same PR at the same magnitude, and the publish gate's issue path moved with it (`apis.N.cacheTtlSeconds`). Anchors carried over from the `cacheTtl` row (re-anchored in commit 8cb96ec41)." }, "cacheTtl": { "status": "dead", "verifiedAt": "2026-09-05", - "note": "REMOVED 2026-09-05 (#15677, #14478 ruling B) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and renamed out of stored sources by the protocol-18 conversion `api-endpoint-cache-ttl-to-cache-ttl-seconds`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); use `cacheTtlSeconds` — rename the key, the value (seconds) is unchanged, it is still GET-only, and `os migrate meta --from 17` lists the mechanical edits. The tombstone is packages/spec/src/api/endpoint.zod.ts#cacheTtl. Not to be confused with `RestServerConfig.metadata.cacheTtl` (liveness/metadata_endpoints.json), a different key retired for a different reason by #14691." + "note": "REMOVED 2026-09-05 (#15677, #14478 ruling B) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and renamed out of stored sources by the protocol-18 conversion `api-endpoint-cache-ttl-to-cache-ttl-seconds`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); use `cacheTtlSeconds` — rename the key, the value (seconds) is unchanged, it is still GET-only, and `os migrate meta --from 17` lists the mechanical edits. The tombstone is packages/spec/src/api/endpoint.zod.ts#cacheTtl. Not to be confused with `RestServerConfig.metadata.cacheTtl` (liveness/metadata_endpoints.json), a different key retired for a different reason by commit b3a63d32c." } } } diff --git a/packages/spec/liveness/datasource.json b/packages/spec/liveness/datasource.json index 2f489b0b4c9..c566a485377 100644 --- a/packages/spec/liveness/datasource.json +++ b/packages/spec/liveness/datasource.json @@ -6,7 +6,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#toSpec (`name: record.name` — the record→`DatasourceConnectionSpec` projection every driver is built from); packages/services/service-datasource/src/datasource-connection-service.ts#connectDeclared (the same name keys the per-datasource connection state and every result row)", - "note": "registry key + the driver name the engine routes on (`driver.name` must equal it). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and DATED for the first time — `datasource-connection-service.ts:674` had rotted in range onto a docblock about an `onEnable` bridge and the default driver's idempotent replay; the projection is at ~:807. This file's entries carried NO `verifiedAt` at all, which the ACCEPT ruling on #13003 sorts as OLDEST (undated means never clock-checked, strictly worse than any date), and this row is why: an undated citation into an 823-line service had nothing asking whether it still pointed anywhere. Re-closed by hand against c459da6bc." + "note": "registry key + the driver name the engine routes on (`driver.name` must equal it). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED, and DATED for the first time — `datasource-connection-service.ts:674` had rotted in range onto a docblock about an `onEnable` bridge and the default driver's idempotent replay; the projection is at ~:807. This file's entries carried NO `verifiedAt` at all, which the review that accepted the re-anchoring sorts as OLDEST (undated means never clock-checked, strictly worse than any date), and this row is why: an undated citation into an 823-line service had nothing asking whether it still pointed anywhere. Re-closed by hand against c459da6bc." }, "label": { "status": "live", @@ -14,19 +14,19 @@ "evidenceScope": "cross-repo", "evidence": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:96 takes `label` off the datasource draft with the datasource's own `name` as fallback, and packages/app-shell/src/views/metadata-admin/previews/DatasourcePreview.tsx:146 renders it as the preview card's title", "producer": "objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts:44 registers DatasourcePreview against the `datasource` metadata type, and packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx:949 resolves that registration and hands the component the draft being edited — the read point is reachable, not merely present", - "note": "display metadata (Setup → Datasources list). ADR-0033 docs-shaped, deliberately kept, not authorWarn'd. PROSE RECONCILED 2026-08-10 (#7427): the old clause 'No runtime consumer by design' is superseded as a whole-system claim — a designer preview rendering a key to a human IS a runtime consumer (maintainer ruling 2026-08-10, #7131; README, 'Designer previews count as consumers'), and the metadata-admin datasource preview has been showing this value as the card title the whole time. What the old clause got right, and what is preserved: NO CONNECTION-PATH CONSUMER reads it — nothing in `ConnectableDatasource` / `DatasourceConnectionSpec` / the driver factory takes `label`, and `live` here does not mean a driver acquired a use for it. UNCHANGED by the reconciliation: still ADR-0033 docs-shaped, still deliberately KEPT, still not authorWarn'd. Status did not move (this row was already `live`); what it gained is a falsifiable pointer, which is the thing a `live` verdict is supposed to be. Reachability checked rather than assumed: `datasource` IS a registered metadata kind (the `datasource: DatasourceSchema` row of `BUILTIN_METADATA_TYPE_SCHEMAS` in packages/spec/src/kernel/metadata-type-schemas.ts), so the metadata-admin route that resolves this preview is a live authoring path — contrast `validation.label`, whose preview renders the same way into a kind ADR-0088 retired. 2026-08-28 (#13003): that reachability pointer was written `metadata-type-schemas.ts:119` and had drifted onto the `flow: FlowSchema` row of the same map — the right map, the wrong kind, which is the one rot shape a reader is least likely to catch by eye. Restated by symbol. The objectui-realm citations in `evidence` / `producer` are pinned to @e9ab52f9 and are not re-graded here (this batch reads the framework tree only)." + "note": "display metadata (Setup → Datasources list). ADR-0033 docs-shaped, deliberately kept, not authorWarn'd. PROSE RECONCILED 2026-08-10 (#7427): the old clause 'No runtime consumer by design' is superseded as a whole-system claim — a designer preview rendering a key to a human IS a runtime consumer (maintainer ruling 2026-08-10, #7131; README, 'Designer previews count as consumers'), and the metadata-admin datasource preview has been showing this value as the card title the whole time. What the old clause got right, and what is preserved: NO CONNECTION-PATH CONSUMER reads it — nothing in `ConnectableDatasource` / `DatasourceConnectionSpec` / the driver factory takes `label`, and `live` here does not mean a driver acquired a use for it. UNCHANGED by the reconciliation: still ADR-0033 docs-shaped, still deliberately KEPT, still not authorWarn'd. Status did not move (this row was already `live`); what it gained is a falsifiable pointer, which is the thing a `live` verdict is supposed to be. Reachability checked rather than assumed: `datasource` IS a registered metadata kind (the `datasource: DatasourceSchema` row of `BUILTIN_METADATA_TYPE_SCHEMAS` in packages/spec/src/kernel/metadata-type-schemas.ts), so the metadata-admin route that resolves this preview is a live authoring path — contrast `validation.label`, whose preview renders the same way into a kind ADR-0088 retired. 2026-08-28 (commit 9ee2dcfbd): that reachability pointer was written `metadata-type-schemas.ts:119` and had drifted onto the `flow: FlowSchema` row of the same map — the right map, the wrong kind, which is the one rot shape a reader is least likely to catch by eye. Restated by symbol. The objectui-realm citations in `evidence` / `producer` are pinned to @e9ab52f9 and are not re-graded here (this batch reads the framework tree only)." }, "driver": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveKind (`return resolveDriverId(driverId)` — the alias normalization); packages/services/service-datasource/src/default-datasource-driver-factory.ts#createDefaultDatasourceDriverFactory (`const kind = resolveKind(spec.driver)`, then the per-driver construction arms; an id that normalizes to nothing is refused by name)", - "note": "factory dispatch; `resolveDriverId` normalizes aliases before the switch. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:345` had rotted in range onto a docblock line about `datasource.ssl` in this 1314-line file; the dispatch is at ~:1037. Since #6345 that table is also what both boot hosts read for `OS_DATABASE_DRIVER` / `--database-driver`, and the canonical mongo id is `mongodb` (stored `mongo` converges via the ADR-0087 conversion `datasource-driver-mongo-to-mongodb`; the alias stays accepted). Every builtin id now has an explicit construction arm — the trailing `memory` fall-through is gone. Re-closed by hand against c459da6bc; DATED for the first time (this file's rows were all undated, which the #13003 ACCEPT ruling sorts as oldest)." + "note": "factory dispatch; `resolveDriverId` normalizes aliases before the switch. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:345` had rotted in range onto a docblock line about `datasource.ssl` in this 1314-line file; the dispatch is at ~:1037. Since commit e2798fab7 that table is also what both boot hosts read for `OS_DATABASE_DRIVER` / `--database-driver`, and the canonical mongo id is `mongodb` (stored `mongo` converges via the ADR-0087 conversion `datasource-driver-mongo-to-mongodb`; the alias stays accepted). Every builtin id now has an explicit construction arm — the trailing `memory` fall-through is gone. Re-closed by hand against c459da6bc; DATED for the first time (this file's rows were all undated, which the review that accepted the re-anchoring sorts as oldest)." }, "config": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#toSpec (`config: record.config ?? {}` — how the block reaches the factory at all); packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlConnection (the pg / better-sqlite3 arm reads `spec.config` for host, port, filename …); packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildMysqlConnection; packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildMemoryConfig; packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildMongoUrl (with `buildMongoAuth` beside it for the credential half)", - "note": "per-driver connection config. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:129` had rotted in range onto an error-message helper's `const cause = …` line, ~360 lines from the nearest read. The single line was also understating the entry: `config` has no ONE consumer, it has one per driver arm plus the projection that carries it there, and a single rotted pointer hid five real ones. Re-closed by hand against c459da6bc. Validated against the driver contract since #4410 (data/driver/config-registry.zod.ts). NOTE the walk boundary: `config` is a `z.record`, so the gate cannot see inside it — the keys an author actually writes (`host`, `port`, `filename`) are governed by the per-driver zod schemas in data/driver/*.zod.ts, not by this ledger. That is a real gap in coverage, recorded here rather than left implicit." + "note": "per-driver connection config. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:129` had rotted in range onto an error-message helper's `const cause = …` line, ~360 lines from the nearest read. The single line was also understating the entry: `config` has no ONE consumer, it has one per driver arm plus the projection that carries it there, and a single rotted pointer hid five real ones. Re-closed by hand against c459da6bc. Validated against the driver contract since #4410 (data/driver/config-registry.zod.ts). NOTE the walk boundary: `config` is a `z.record`, so the gate cannot see inside it — the keys an author actually writes (`host`, `port`, `filename`) are governed by the per-driver zod schemas in data/driver/*.zod.ts, not by this ledger. That is a real gap in coverage, recorded here rather than left implicit." }, "pool": { "children": { @@ -34,28 +34,28 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (the knex pool floor on `postgres` / `mysql`); packages/services/service-datasource/src/default-datasource-driver-factory.ts#createDefaultDatasourceDriverFactory (the mongodb arm maps it onto the MongoClient's `minPoolSize`, and the same function calls `assertDatasourcePoolSupported` at the last door); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS (the four arms on which declaring it is an authoring ERROR)", - "note": "knex pool floor on `postgres` / `mysql` (`buildSqlPool`, handed to `SqlDriver` in the pg and mysql arms), and the MongoClient's `minPoolSize` on `mongodb`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — all three citations had rotted in range. `:191` and `:482` in the driver factory landed in a docblock about import-error interpolation and on a `throw new Error(unparseablePostgresDsnMessage(...))` respectively; the reads are at ~:623 and ~:1182. `datasource-pool-support.ts:79` landed on a docblock line, ~36 lines above the constant it names. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465 — the factory used to hardcode `{ min: 0, max: 5 }` over the carried value. NOT live on `memory` / `sqlite` / `sqlite-wasm`, and not silently dropped there either: declaring it is an authoring ERROR since #5714 (the SQLite pair) and #5931 (`memory`) — `POOL_UNSUPPORTED_DRIVER_IDS` (datasource-pool-support.ts:79). Also an authoring ERROR on `turso` since #7243 (maintainer ruling 2026-08-11, whole-arm — `TursoDriverConfig` has no `min` / `max` and neither libSQL transport pools). See the block note below." + "note": "knex pool floor on `postgres` / `mysql` (`buildSqlPool`, handed to `SqlDriver` in the pg and mysql arms), and the MongoClient's `minPoolSize` on `mongodb`. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — all three citations had rotted in range. `:191` and `:482` in the driver factory landed in a docblock about import-error interpolation and on a `throw new Error(unparseablePostgresDsnMessage(...))` respectively; the reads are at ~:623 and ~:1182. `datasource-pool-support.ts:79` landed on a docblock line, ~36 lines above the constant it names. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465 — the factory used to hardcode `{ min: 0, max: 5 }` over the carried value. NOT live on `memory` / `sqlite` / `sqlite-wasm`, and not silently dropped there either: declaring it is an authoring ERROR since #5714 (the SQLite pair) and #5931 (`memory`) — `POOL_UNSUPPORTED_DRIVER_IDS` (datasource-pool-support.ts:79). Also an authoring ERROR on `turso` since #7243 (maintainer ruling 2026-08-11, whole-arm — `TursoDriverConfig` has no `min` / `max` and neither libSQL transport pools). See the block note below." }, "max": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (the knex pool ceiling on `postgres` / `mysql`); packages/services/service-datasource/src/default-datasource-driver-factory.ts#createDefaultDatasourceDriverFactory (mapped onto the Mongo client's `maxPoolSize`, #4465); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS", - "note": "knex pool ceiling on `postgres` / `mysql` (`buildSqlPool`); also mapped onto the Mongo client's `maxPoolSize` (#4465). Same driver qualification as `min`: an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243). See the block note below. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — same three rots as `min`, one line over. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "knex pool ceiling on `postgres` / `mysql` (`buildSqlPool`); also mapped onto the Mongo client's `maxPoolSize` (#4465). Same driver qualification as `min`: an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243). See the block note below. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — same three rots as `min`, one line over. Re-closed by hand against c459da6bc; DATED for the first time." }, "idleTimeoutMillis": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (passed through to knex verbatim — the two SQL arms are the only readers); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNREAD_KEYS_BY_DRIVER (the mongodb by-name rejection); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS (the four whole-block rejections)", - "note": "passed through to knex verbatim on `postgres` / `mysql` (`buildSqlPool`) — the two SQL arms are the ONLY ones that read it. `mongodb` takes `min` / `max` out of the block and nothing else, so this key reaches nothing on that arm; the unqualified `live` this row carried before #6214 overstated it. Since #7243 that reach-nothing is REJECTED BY NAME rather than dropped: `POOL_UNREAD_KEYS_BY_DRIVER.mongodb` lists this key, so a mongo datasource declaring it is refused at all three doors (maintainer ruling 2026-08-11 — rejected, not wired onto MongoClient's `maxIdleTimeMS`, for want of a measured consumer). An authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` too (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243; the whole-block rejection text names `min` / `max` and the timeouts together). See the block note below. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `default-datasource-driver-factory.ts:193` had rotted into an import-error docblock and `datasource-pool-support.ts:79` onto a docblock line, ~36 lines above the constant it meant; the entry also gains the `POOL_UNREAD_KEYS_BY_DRIVER` anchor its own note already named in prose but never cited, which is the by-name rejection this row's whole qualification rests on. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "passed through to knex verbatim on `postgres` / `mysql` (`buildSqlPool`) — the two SQL arms are the ONLY ones that read it. `mongodb` takes `min` / `max` out of the block and nothing else, so this key reaches nothing on that arm; the unqualified `live` this row carried before #6214 overstated it. Since #7243 that reach-nothing is REJECTED BY NAME rather than dropped: `POOL_UNREAD_KEYS_BY_DRIVER.mongodb` lists this key, so a mongo datasource declaring it is refused at all three doors (maintainer ruling 2026-08-11 — rejected, not wired onto MongoClient's `maxIdleTimeMS`, for want of a measured consumer). An authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` too (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243; the whole-block rejection text names `min` / `max` and the timeouts together). See the block note below. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `default-datasource-driver-factory.ts:193` had rotted into an import-error docblock and `datasource-pool-support.ts:79` onto a docblock line, ~36 lines above the constant it meant; the entry also gains the `POOL_UNREAD_KEYS_BY_DRIVER` anchor its own note already named in prose but never cited, which is the by-name rejection this row's whole qualification rests on. Re-closed by hand against c459da6bc; DATED for the first time." }, "connectionTimeoutMillis": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#buildSqlPool (`acquireTimeoutMillis: pool.connectionTimeoutMillis` — the rename happens here); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNREAD_KEYS_BY_DRIVER (the mongodb by-name rejection); packages/services/service-datasource/src/datasource-pool-support.ts#POOL_UNSUPPORTED_DRIVER_IDS", - "note": "mapped onto knex's `acquireTimeoutMillis` (different name, same meaning) on `postgres` / `mysql` (`buildSqlPool`). Carries `idleTimeoutMillis`' qualification exactly: `mongodb` reads only `min` / `max` from the block, so this key reaches nothing there and is REJECTED BY NAME since #7243 (`POOL_UNREAD_KEYS_BY_DRIVER.mongodb`), and the whole block is an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243). See the block note below. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — same two rots as its sibling. Note what the anchor fixes that the line could not even in principle: this key's consumer RENAMES it on the way through, so `buildSqlPool` is the only place in the tree where the authored spelling and the knex spelling are both visible, and a citation that drifts off it leaves the rename undocumented. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "mapped onto knex's `acquireTimeoutMillis` (different name, same meaning) on `postgres` / `mysql` (`buildSqlPool`). Carries `idleTimeoutMillis`' qualification exactly: `mongodb` reads only `min` / `max` from the block, so this key reaches nothing there and is REJECTED BY NAME since #7243 (`POOL_UNREAD_KEYS_BY_DRIVER.mongodb`), and the whole block is an authoring ERROR on `memory` / `sqlite` / `sqlite-wasm` / `turso` (`POOL_UNSUPPORTED_DRIVER_IDS` — #5714 / #5931 / #7243). See the block note below. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — same two rots as its sibling. Note what the anchor fixes that the line could not even in principle: this key's consumer RENAMES it on the way through, so `buildSqlPool` is the only place in the tree where the authored spelling and the knex spelling are both visible, and a citation that drifts off it leaves the rename undocumented. Re-closed by hand against c459da6bc; DATED for the first time." } }, - "note": "QUALIFIED BY DRIVER 2026-08-10 (#6214) — the spec half of the #5931 ruling. `live` on the four rows above means \"honoured by the pooled arms\", never \"honoured everywhere\", and the rows said so nowhere until this repair: an author reading them got `pool` presented as an unconditional knob on a block that four built-in drivers now REFUSE outright and a fifth (`mongodb`) honours only half of. HONOURED: `postgres` / `mysql` hand `buildSqlPool(spec)` to `SqlDriver`, and `mongodb` maps `min` / `max` — only those two — onto the MongoClient's `minPoolSize` / `maxPoolSize`, both inside `createDefaultDatasourceDriverFactory`'s per-driver arms. LOUDLY REJECTED on `memory` / `sqlite` / `sqlite-wasm` / `turso` (#7243): `POOL_UNSUPPORTED_DRIVER_IDS` (packages/services/service-datasource/src/datasource-pool-support.ts) with one explanation per arm in `POOL_UNSUPPORTED_REASONS`, thrown by `assertDatasourcePoolSupported` at every door a `pool` block can come in through — the Setup wizard's `createDatasource` / `updateDatasource`, the boot-time pre-pass (`DatasourceConnectionService.assertDeclaredPoolsAreHonoured` via `unsupportedPoolIssue`, and `attemptConnect` before the factory call), and the factory's last door in `createDefaultDatasourceDriverFactory`. 2026-08-28 (#13003): every line reference in this block note was re-read and replaced with the symbol it meant — they had drifted with the same files their sibling `evidence` citations did (`:188-198` landed in an import-error docblock, `:374` inside `resolveSslOption`, `datasource-pool-support.ts:79` on a docblock line), and a line inside a `note` is worse than one in `evidence` because no check reads `note` at all. Two arms, two reasons, one verdict: knex's better-sqlite3 dialect pins `{min:1,max:1}` on purpose because a second connection to `:memory:` opens a SEPARATE, empty database, so sizing the pool would split one datasource's data across several stores (#5714, maintainer ruling 2026-08-06 option B); `memory` opens no connection at all — its store is a plain data structure inside this process, reached by a direct call — so `min` / `max` and the timeouts have nothing to configure (#5931, maintainer ruling 2026-08-07, which also set the default that a silently-dropped key JOINS an existing rejection set rather than queueing for its own ruling, #6140). Measured through the real factory before the rejection existed (#5931): `postgres + pool{min:3,max:9}` → live `{min:3,max:9}`; `sqlite + pool{min:3,max:9}` → live `{min:1,max:1}`; `memory + pool{min:3,max:9}` → driver config `{\"persistence\":false}`, `pool` undefined. `examples/app-crm` was the live specimen — `CrmDatasource` declared `pool: { min: 1, max: 5 }` and ran on `{min:1,max:1}`. NOTHING IS SILENT HERE ANY MORE, as of #7243 (maintainer ruling 2026-08-11, both halves). The two faces this note recorded as still-dropped are now loud. (a) `turso` JOINS the rejected set whole-arm, with no fork by url mode: `TursoDriverConfig` has no `min` / `max`, a `file:` / `:memory:` url runs the very better-sqlite3 engine the set already rejects for, and a `libsql://` url is a remote request transport with no persistent connections, capped by `config.concurrency`. Measured before the change: the turso arm never referenced `spec.pool` at all. (b) `mongodb`'s two timeout keys are REJECTED BY NAME rather than wired — a new per-KEY shape (`POOL_UNREAD_KEYS_BY_DRIVER`, datasource-pool-support.ts), because rejecting the whole block on that arm would throw away the `min` / `max` it does honour. Measured before the change: `mongodb + pool{max:20,idleTimeoutMillis:30000,connectionTimeoutMillis:3000}` built a driver whose config was url + database + maxPoolSize:20 and nothing else — half the block honoured, which reads to the author as all of it. MongoClient does expose `maxIdleTimeMS` / `connectTimeoutMS`, so this one COULD have been implemented; the ruling declined for want of a measured consumer and left the wiring as a one-line pulled change for the day demand appears. A driver id the platform ships no contract for (`com.vendor.snowflake`) is deliberately NOT judged — \"we validate what we can construct\" (the docblock over `POOL_UNSUPPORTED_DRIVER_IDS` in datasource-pool-support.ts)." + "note": "QUALIFIED BY DRIVER 2026-08-10 (#6214) — the spec half of the #5931 ruling. `live` on the four rows above means \"honoured by the pooled arms\", never \"honoured everywhere\", and the rows said so nowhere until this repair: an author reading them got `pool` presented as an unconditional knob on a block that four built-in drivers now REFUSE outright and a fifth (`mongodb`) honours only half of. HONOURED: `postgres` / `mysql` hand `buildSqlPool(spec)` to `SqlDriver`, and `mongodb` maps `min` / `max` — only those two — onto the MongoClient's `minPoolSize` / `maxPoolSize`, both inside `createDefaultDatasourceDriverFactory`'s per-driver arms. LOUDLY REJECTED on `memory` / `sqlite` / `sqlite-wasm` / `turso` (#7243): `POOL_UNSUPPORTED_DRIVER_IDS` (packages/services/service-datasource/src/datasource-pool-support.ts) with one explanation per arm in `POOL_UNSUPPORTED_REASONS`, thrown by `assertDatasourcePoolSupported` at every door a `pool` block can come in through — the Setup wizard's `createDatasource` / `updateDatasource`, the boot-time pre-pass (`DatasourceConnectionService.assertDeclaredPoolsAreHonoured` via `unsupportedPoolIssue`, and `attemptConnect` before the factory call), and the factory's last door in `createDefaultDatasourceDriverFactory`. 2026-08-28 (commit 9ee2dcfbd): every line reference in this block note was re-read and replaced with the symbol it meant — they had drifted with the same files their sibling `evidence` citations did (`:188-198` landed in an import-error docblock, `:374` inside `resolveSslOption`, `datasource-pool-support.ts:79` on a docblock line), and a line inside a `note` is worse than one in `evidence` because no check reads `note` at all. Two arms, two reasons, one verdict: knex's better-sqlite3 dialect pins `{min:1,max:1}` on purpose because a second connection to `:memory:` opens a SEPARATE, empty database, so sizing the pool would split one datasource's data across several stores (#5714, maintainer ruling 2026-08-06 option B); `memory` opens no connection at all — its store is a plain data structure inside this process, reached by a direct call — so `min` / `max` and the timeouts have nothing to configure (#5931, maintainer ruling 2026-08-07, which also set the default that a silently-dropped key JOINS an existing rejection set rather than queueing for its own ruling, #6140). Measured through the real factory before the rejection existed (#5931): `postgres + pool{min:3,max:9}` → live `{min:3,max:9}`; `sqlite + pool{min:3,max:9}` → live `{min:1,max:1}`; `memory + pool{min:3,max:9}` → driver config `{\"persistence\":false}`, `pool` undefined. `examples/app-crm` was the live specimen — `CrmDatasource` declared `pool: { min: 1, max: 5 }` and ran on `{min:1,max:1}`. NOTHING IS SILENT HERE ANY MORE, as of #7243 (maintainer ruling 2026-08-11, both halves). The two faces this note recorded as still-dropped are now loud. (a) `turso` JOINS the rejected set whole-arm, with no fork by url mode: `TursoDriverConfig` has no `min` / `max`, a `file:` / `:memory:` url runs the very better-sqlite3 engine the set already rejects for, and a `libsql://` url is a remote request transport with no persistent connections, capped by `config.concurrency`. Measured before the change: the turso arm never referenced `spec.pool` at all. (b) `mongodb`'s two timeout keys are REJECTED BY NAME rather than wired — a new per-KEY shape (`POOL_UNREAD_KEYS_BY_DRIVER`, datasource-pool-support.ts), because rejecting the whole block on that arm would throw away the `min` / `max` it does honour. Measured before the change: `mongodb + pool{max:20,idleTimeoutMillis:30000,connectionTimeoutMillis:3000}` built a driver whose config was url + database + maxPoolSize:20 and nothing else — half the block honoured, which reads to the author as all of it. MongoClient does expose `maxIdleTimeMS` / `connectTimeoutMS`, so this one COULD have been implemented; the ruling declined for want of a measured consumer and left the wiring as a one-line pulled change for the day demand appears. A driver id the platform ships no contract for (`com.vendor.snowflake`) is deliberately NOT judged — \"we validate what we can construct\" (the docblock over `POOL_UNSUPPORTED_DRIVER_IDS` in datasource-pool-support.ts)." }, "ssl": { "children": { @@ -63,34 +63,34 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`if (block.enabled === false) return false` — the short-circuit, ahead of the option assembly)", - "note": "`enabled: false` short-circuits to `ssl: false`; otherwise the block is assembled into client TLS options. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." + "note": "`enabled: false` short-circuits to `ssl: false`; otherwise the block is assembled into client TLS options. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — see the block note." }, "rejectUnauthorized": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.rejectUnauthorized !== undefined ? { rejectUnauthorized: block.rejectUnauthorized } : {}` — present-or-absent, so `false` is carried rather than defaulted away); packages/services/service-datasource/src/default-datasource-driver-factory.ts#mysqlSslOption (re-reads the resolved option into the spelling mysql2 accepts, #8874)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — see the block note." }, "ca": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.ca ? { ca: block.ca } : {}`)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — see the block note." }, "cert": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.cert ? { cert: block.cert } : {}`)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — see the block note." }, "key": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/default-datasource-driver-factory.ts#resolveSslOption (`block.key ? { key: block.key } : {}`)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — see the block note." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — see the block note." } }, - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED, whole block, and this is the batch's sharpest case of a security-shaped entry pointing at nothing. All five citations (`:111` / `:114` / `:115` / `:116` / `:117`) landed in a docblock about `OS_DATABASE_URL` and which host knob a connect-failure remedy should name — a contiguous run of five wrong pointers into a 1314-line file, in range, in a file that names `ssl` on dozens of lines, so nothing in the gate could see it. The real reads are all in ONE function, `resolveSslOption` at ~:357, which is why five separate lines collapse to one anchor apiece. The block's whole history is that it was silently inert once before (#4465); an evidence pointer that had quietly stopped resolving to the code is the same failure one level up. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465. Before that the whole block stopped at the record — nothing put it on the connection spec, so a TLS configuration with a CA certificate in it configured nothing while looking identical to one that worked. A security-shaped property that was silently inert; exactly the ADR-0078 class this ledger exists to catch, and it was found by hand rather than by a gate." + "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED, whole block, and this is the batch's sharpest case of a security-shaped entry pointing at nothing. All five citations (`:111` / `:114` / `:115` / `:116` / `:117`) landed in a docblock about `OS_DATABASE_URL` and which host knob a connect-failure remedy should name — a contiguous run of five wrong pointers into a 1314-line file, in range, in a file that names `ssl` on dozens of lines, so nothing in the gate could see it. The real reads are all in ONE function, `resolveSslOption` at ~:357, which is why five separate lines collapse to one anchor apiece. The block's whole history is that it was silently inert once before (#4465); an evidence pointer that had quietly stopped resolving to the code is the same failure one level up. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465. Before that the whole block stopped at the record — nothing put it on the connection spec, so a TLS configuration with a CA certificate in it configured nothing while looking identical to one that worked. A security-shaped property that was silently inert; exactly the ADR-0078 class this ledger exists to catch, and it was found by hand rather than by a gate." }, "description": { "status": "live", @@ -104,19 +104,19 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#connectDeclared (`if (ds.active === false) continue` — the boot auto-connect sweep skips it); packages/services/service-datasource/src/datasource-connection-service.ts#assertDeclaredPoolsAreHonoured (the same skip on the pre-pass, so an inactive datasource is not refused for a pool it will never build); packages/services/service-datasource/src/datasource-admin-plugin.ts#rehydratePools (`r.origin === 'runtime' && (r.active ?? true)` — excluded from the runtime re-registration set)", - "note": "`active: false` skips the datasource in the boot auto-connect sweep, and `rehydratePools` excludes it from the runtime re-registration set. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `datasource-connection-service.ts:296` had rotted onto a bare closing brace between `getConnectionState` and `listConnectionStates`, and the note's `datasource-admin-plugin.ts:424` onto `this.config = config`. The entry also gains a second in-file consumer the single line hid: the pre-pass honours the same skip. Re-closed by hand against c459da6bc; DATED for the first time. Genuinely enforced — unlike `flow.active` / `tool.active`, both of which were retired in v17 for claiming this and not delivering it." + "note": "`active: false` skips the datasource in the boot auto-connect sweep, and `rehydratePools` excludes it from the runtime re-registration set. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `datasource-connection-service.ts:296` had rotted onto a bare closing brace between `getConnectionState` and `listConnectionStates`, and the note's `datasource-admin-plugin.ts:424` onto `this.config = config`. The entry also gains a second in-file consumer the single line hid: the pre-pass honours the same skip. Re-closed by hand against c459da6bc; DATED for the first time. Genuinely enforced — unlike `flow.active` / `tool.active`, both of which were retired in v17 for claiming this and not delivering it." }, "autoConnect": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#isDatasourceAddressed (`if (ds.autoConnect === true) return true` — limb (c) of the three-way addressed test the boot sweep gates on)", - "note": "ADR-0062 D2(c): opts a managed, unrouted datasource into the boot connect sweep. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:233` had rotted onto a comment about operators finding a mis-routed datasource by going to look in the database, ~26 lines above the predicate. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "ADR-0062 D2(c): opts a managed, unrouted datasource into the boot connect sweep. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:233` had rotted onto a comment about operators finding a mis-routed datasource by going to look in the database, ~26 lines above the predicate. Re-closed by hand against c459da6bc; DATED for the first time." }, "schemaMode": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#toSpec (`...(record.schemaMode ? { schemaMode: record.schemaMode } : {})` — the #4410 repair, carried onto the connection spec); packages/services/service-datasource/src/datasource-connection-service.ts#isDatasourceAddressed (limb (a): a non-managed datasource is addressed whatever else it declares); packages/objectql/src/engine.ts#assertWriteAllowed (`if (!ds || !ds.schemaMode || ds.schemaMode === 'managed') return` — the federation write gate's first test); packages/objectql/src/engine.ts#registerDatasourceDef (what captures `schemaMode` into `datasourceDefs` so that gate has anything to read)", - "note": "carried onto the connection spec (#4410) and gates DDL at the driver; also read by the engine's federation write gate. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `datasource-connection-service.ts:679` had rotted onto `const names = Array.from(this.states.entries())`, and the note's `engine.ts:620` onto a bare docblock continuation line in a 13593-line file; the write gate is at ~:5267. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465 — before that it was dropped between record and spec, so an `external` database ObjectStack must never run DDL against was constructed as `managed`. Security-shaped and silently inert; the second reason this ledger was seeded." + "note": "carried onto the connection spec (#4410) and gates DDL at the driver; also read by the engine's federation write gate. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `datasource-connection-service.ts:679` had rotted onto `const names = Array.from(this.states.entries())`, and the note's `engine.ts:620` onto a bare docblock continuation line in a 13593-line file; the write gate is at ~:5267. Re-closed by hand against c459da6bc; DATED for the first time. Live only since #4465 — before that it was dropped between record and spec, so an `external` database ObjectStack must never run DDL against was constructed as `managed`. Security-shaped and silently inert; the second reason this ledger was seeded." }, "external": { "children": { @@ -124,31 +124,31 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/external-datasource-service.ts#listRemoteTables (`const allowed = ds?.external?.allowedSchemas` — and the filter only bites on tables whose schema can be attributed, which the code says beside the read)", - "note": "restricts which remote schemas browse/introspect will surface (ADR-0015). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:145` had rotted onto a docblock about the `col.primaryKey` spelling, ~226 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "restricts which remote schemas browse/introspect will surface (ADR-0015). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:145` had rotted onto a docblock about the `col.primaryKey` spelling, ~226 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time." }, "allowWrites": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/engine.ts#assertWriteAllowed (`const dsAllows = ds.external?.allowWrites ?? false` — Gate 3, ANDed with the object's own `external.writable`, called from `insert` / `update` / `delete`); packages/objectql/src/engine.ts#registerDatasourceDef (captures `external` into `datasourceDefs` so the gate has a definition to read)", - "note": "the enforced datasource-wide write gate (Gate 3). This — not `capabilities.readOnly` — is how a federated datasource is made read-only. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:620` had rotted onto a bare docblock continuation in a 13593-line file; the gate is at ~:5267, roughly 4650 lines away. A security-shaped enforcement point whose only pointer had drifted that far is the reason this migration is not cosmetic. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "the enforced datasource-wide write gate (Gate 3). This — not `capabilities.readOnly` — is how a federated datasource is made read-only. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `engine.ts:620` had rotted onto a bare docblock continuation in a 13593-line file; the gate is at ~:5267, roughly 4650 lines away. A security-shaped enforcement point whose only pointer had drifted that far is the reason this migration is not cosmetic. Re-closed by hand against c459da6bc; DATED for the first time." }, "validation": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/external-validation-plugin.ts#resolveOnMismatch (`ds?.external?.validation?.onMismatch ?? 'fail'` — the drift policy, defaulted fail-closed and re-read per sweep); packages/runtime/src/external-validation-plugin.ts#scheduleDriftChecks (`const interval = def?.external?.validation?.checkIntervalMs` — arms one background timer per datasource that declares it)", - "note": "`onMismatch` selects the drift policy (default 'fail'); `checkIntervalMs` schedules the recurring drift check. Also read by the degraded-boot classifier (packages/types/src/degraded-boot.ts, `validation.onMismatch:'fail'` as one of the two degraded-boot causes). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and one PROSE CLAIM WITHDRAWN. Both citations had rotted in range (`:153` onto a `schemaMode?: string` field of a local `DatasourceDef` interface, `:231` onto a comment about vitest workers paying the bill for un-unref'd timers). The withdrawal: this note said \"`checkOnBoot` gates the boot-time one\", and the re-closure falsifies it — `checkOnBoot` has ZERO readers anywhere in this repo (its only non-test occurrences are the schema declaration, its two alias spellings, and one example app that authors it). `ExternalValidationPlugin.start` hooks `kernel:ready` and calls `runValidation` UNCONDITIONALLY, so `checkOnBoot: false` still runs the boot sweep — and with the default `onMismatch: 'fail'` that aborts boot. The container entry stays `live` on the two readers that do exist; the inert member is below this ledger's one-level walk and is filed rather than flipped here, per ADR-0049 and the #13003 dispatch's no-status-changes rule. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "`onMismatch` selects the drift policy (default 'fail'); `checkIntervalMs` schedules the recurring drift check. Also read by the degraded-boot classifier (packages/types/src/degraded-boot.ts, `validation.onMismatch:'fail'` as one of the two degraded-boot causes). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED, and one PROSE CLAIM WITHDRAWN. Both citations had rotted in range (`:153` onto a `schemaMode?: string` field of a local `DatasourceDef` interface, `:231` onto a comment about vitest workers paying the bill for un-unref'd timers). The withdrawal: this note said \"`checkOnBoot` gates the boot-time one\", and the re-closure falsifies it — `checkOnBoot` has ZERO readers anywhere in this repo (its only non-test occurrences are the schema declaration, its two alias spellings, and one example app that authors it). `ExternalValidationPlugin.start` hooks `kernel:ready` and calls `runValidation` UNCONDITIONALLY, so `checkOnBoot: false` still runs the boot sweep — and with the default `onMismatch: 'fail'` that aborts boot. The container entry stays `live` on the two readers that do exist; the inert member is below this ledger's one-level walk and is filed rather than flipped here, per ADR-0049 and the no-status-changes rule that re-anchoring ran under. Re-closed by hand against c459da6bc; DATED for the first time." }, "credentialsRef": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-connection-service.ts#attemptConnect (`const credentialsRef = record.external?.credentialsRef`, resolved through the configured secret resolver into a `secret` spread onto the factory call and never stored on the record — three distinct failure states are named rather than swallowed: no secret store configured, resolver threw, resolved to nothing)", - "note": "dereferenced through the SecretBinder to cleartext for the duration of one connect; never persisted or logged (ADR-0015 Addendum). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:457` had rotted onto a docblock about threading a value through an instance field rather than the ConnectResult, ~95 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "dereferenced through the SecretBinder to cleartext for the duration of one connect; never persisted or logged (ADR-0015 Addendum). 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:457` had rotted onto a docblock about threading a value through an instance field rather than the ConnectResult, ~95 lines above the read. Re-closed by hand against c459da6bc; DATED for the first time." }, "queryTimeoutMs": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-admin-service.ts#testConnection (`const queryTimeoutMs = (input.external as …)?.queryTimeoutMs`, spread onto the probe options as `timeoutMs` only when it is a number)", - "note": "carried into the external-datasource probe options as `timeoutMs`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:220` had rotted onto a BLANK LINE, which is the most legible form of this rot: in range, resolvable, and pointing at nothing at all. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "carried into the external-datasource probe options as `timeoutMs`. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:220` had rotted onto a BLANK LINE, which is the most legible form of this rot: in range, resolvable, and pointing at nothing at all. Re-closed by hand against c459da6bc; DATED for the first time." } }, "note": "5 of 7 live. The two dead ones are opposite in risk: `external.label` is cosmetic, `external.requirePermission` is security-shaped — it reads as an access gate and is not one." @@ -157,7 +157,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-datasource/src/datasource-admin-plugin.ts#rehydratePools (`records.filter((r) => r.origin === 'runtime' && (r.active ?? true))` — the value that decides which datasources are re-registered at boot); packages/services/service-datasource/src/datasource-admin-plugin.ts#DatasourceAdminServicePlugin (the metadata adapter installed in `init` defaults an artefact-loaded row's missing `origin` to `'code'`, and the connect path stamps `origin: record.origin ?? 'runtime'` onto the lifecycle context)", - "note": "server-stamped provenance (ADR-0015 Addendum). `code` marks a GitOps-owned datasource read-only in the UI; `runtime` marks one editable and re-registrable (`rehydratePools`). Never accepted from client input. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:244` had rotted onto `variant: 'secondary'`, a UI action definition in this plugin's own metadata, and the note's `:424` onto `this.config = config`. Re-closed by hand against c459da6bc; DATED for the first time." + "note": "server-stamped provenance (ADR-0015 Addendum). `code` marks a GitOps-owned datasource read-only in the UI; `runtime` marks one editable and re-registrable (`rehydratePools`). Never accepted from client input. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — `:244` had rotted onto `variant: 'secondary'`, a UI action definition in this plugin's own metadata, and the note's `:424` onto `this.config = config`. Re-closed by hand against c459da6bc; DATED for the first time." } } } diff --git a/packages/spec/liveness/email_template.json b/packages/spec/liveness/email_template.json index 782c46534cc..b023ef9e041 100644 --- a/packages/spec/liveness/email_template.json +++ b/packages/spec/liveness/email_template.json @@ -1,85 +1,85 @@ { "type": "email_template", - "_note": "EmailTemplateDefinitionSchema. THE WHOLE SURFACE WENT LIVE with the #4509 materializer bridge — this file previously recorded the webhook (#3461) disconnect shape, all 13 props dead, and it is kept as the worked example that a dead verdict is a worklist entry, not a tombstone: enforce-or-remove resolved this one by ENFORCING. What closed it: `bootstrapDeclaredEmailTemplates` (packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts) validates each declared item through `EmailTemplateDefinitionSchema.parse()` (in `upsertDeclaredEmailTemplate` — the spec schema finally has a real consumer, defaults and all) and materializes it into the `sys_email_template` ROW that the execution point reads, via `mapTemplateToRow` — the SAME mapping the built-in seeder uses (`EmailServicePlugin.upsertTemplate`), shared deliberately so the two doors cannot drift. The execution end is unchanged in mechanism but NOT in shape: `IEmailService.sendTemplate` resolves (name, locale) with an en-US fallback and honors active/variables/fromOverride/replyTo off the row — except that since #9225 the resolve-and-render half lives in `EmailService.resolveAndRenderTemplate`, shared with the render-only `renderTemplate`, and `sendTemplate` keeps only the envelope reads. THREE breaks had to close, not one: the engine never registered authored `emailTemplates:` into the registry at all (objectql/src/engine.ts metadataArrayKeys — the key was simply missing, so the bridge's source was empty); built-in seeds left `managed_by` at the column default 'admin', which made them masquerade as admin-authored and permanently outrank a declared template (`upsertTemplate` now stamps 'platform'); and only then did the bridge's write land. The ADR-0054 proof pins all three by asserting the AUTHORED wording is what sendTemplate actually renders. Runtime authoring is covered too: email_template is allowRuntimeCreate:true (unlike webhook), so a boot-only sweep would leave a Studio save inert until restart — the plugin also subscribes to `email_template` metadata changes and re-materializes the single changed item (`EmailServicePlugin.wireTemplateMutationProjection`). Seed-not-clobber mirrors sys_webhook (#3489): declared rows seed as `managed_by:'package'` and re-seed every boot, but an admin-authored ('admin') or admin-edited ('customized', stamped by email-template-provenance.ts) row is never overwritten — a reworded transactional mail survives redeploys. Nothing carries an authorWarn any more: authoring is no longer a no-op. `protection`/_lock*/_provenance are framework overlay fields, auto-live. 2026-08-28 (#13003): every citation in this file was re-anchored to its consuming symbol; the file-level pointers above were carrying line numbers of their own and are now named by symbol for the same reason.", + "_note": "EmailTemplateDefinitionSchema. THE WHOLE SURFACE WENT LIVE with the #4509 materializer bridge — this file previously recorded the webhook (#3461) disconnect shape, all 13 props dead, and it is kept as the worked example that a dead verdict is a worklist entry, not a tombstone: enforce-or-remove resolved this one by ENFORCING. What closed it: `bootstrapDeclaredEmailTemplates` (packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts) validates each declared item through `EmailTemplateDefinitionSchema.parse()` (in `upsertDeclaredEmailTemplate` — the spec schema finally has a real consumer, defaults and all) and materializes it into the `sys_email_template` ROW that the execution point reads, via `mapTemplateToRow` — the SAME mapping the built-in seeder uses (`EmailServicePlugin.upsertTemplate`), shared deliberately so the two doors cannot drift. The execution end is unchanged in mechanism but NOT in shape: `IEmailService.sendTemplate` resolves (name, locale) with an en-US fallback and honors active/variables/fromOverride/replyTo off the row — except that since #9225 the resolve-and-render half lives in `EmailService.resolveAndRenderTemplate`, shared with the render-only `renderTemplate`, and `sendTemplate` keeps only the envelope reads. THREE breaks had to close, not one: the engine never registered authored `emailTemplates:` into the registry at all (objectql/src/engine.ts metadataArrayKeys — the key was simply missing, so the bridge's source was empty); built-in seeds left `managed_by` at the column default 'admin', which made them masquerade as admin-authored and permanently outrank a declared template (`upsertTemplate` now stamps 'platform'); and only then did the bridge's write land. The ADR-0054 proof pins all three by asserting the AUTHORED wording is what sendTemplate actually renders. Runtime authoring is covered too: email_template is allowRuntimeCreate:true (unlike webhook), so a boot-only sweep would leave a Studio save inert until restart — the plugin also subscribes to `email_template` metadata changes and re-materializes the single changed item (`EmailServicePlugin.wireTemplateMutationProjection`). Seed-not-clobber mirrors sys_webhook (#3489): declared rows seed as `managed_by:'package'` and re-seed every boot, but an admin-authored ('admin') or admin-edited ('customized', stamped by email-template-provenance.ts) row is never overwritten — a reworded transactional mail survives redeploys. Nothing carries an authorWarn any more: authoring is no longer a no-op. `protection`/_lock*/_provenance are framework overlay fields, auto-live. 2026-08-28 (commit 8cb96ec41): every citation in this file was re-anchored to its consuming symbol; the file-level pointers above were carrying line numbers of their own and are now named by symbol for the same reason.", "props": { "name": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`name: tpl.name` — the row projection, and half the (name, locale) upsert key `upsertDeclaredEmailTemplate` matches on); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (`loader.load(input.template, wanted)` — the send-end lookup key, and `TEMPLATE_NOT_FOUND: ${input.template}` when it misses)", - "note": "The (name, locale) resolution key at both ends of the bridge. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE (`:78` still names the column), so that half is a pure grammar migration; the send-end leg had rotted. `email-service.ts:411` lands on a docblock line of the `TemplateLoader` interface (\"or `null` when none exists / inactive\"), ~780 lines above the read. ALL NINE of this file's `email-service.ts` citations rotted together and by the same cause: #9225 split the resolver out of `sendTemplate` into `resolveAndRenderTemplate`, and the reads moved with it from the 400s to the 1170-1310 band. That is the drift no existing check can see — the file exists, every cited line is still inside it, and the file still names each key, so the existence check, the line bound and the key-mention check all pass on a pointer that is ~750 lines wrong. Re-closed by hand against 93ea19bca." + "note": "The (name, locale) resolution key at both ends of the bridge. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE (`:78` still names the column), so that half is a pure grammar migration; the send-end leg had rotted. `email-service.ts:411` lands on a docblock line of the `TemplateLoader` interface (\"or `null` when none exists / inactive\"), ~780 lines above the read. ALL NINE of this file's `email-service.ts` citations rotted together and by the same cause: #9225 split the resolver out of `sendTemplate` into `resolveAndRenderTemplate`, and the reads moved with it from the 400s to the 1170-1310 band. That is the drift no existing check can see — the file exists, every cited line is still inside it, and the file still names each key, so the existence check, the line bound and the key-mention check all pass on a pointer that is ~750 lines wrong. Re-closed by hand against 93ea19bca." }, "label": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`label: tpl.label`)", - "note": "Materialized into the `label` column — the Studio row title (sys_email_template nameField). 2026-08-28: RE-ANCHORED (#13003) — `:79` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." + "note": "Materialized into the `label` column — the Studio row title (sys_email_template nameField). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) — `:79` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." }, "category": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`category: tpl.category`)", - "note": "Materialized into `category`; a Studio filter facet on the row, never send behavior. 2026-08-28: RE-ANCHORED (#13003) — `:80` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." + "note": "Materialized into `category`; a Studio filter facet on the row, never send behavior. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) — `:80` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." }, "locale": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`locale: tpl.locale` — the row projection, and the other half of the upsert key); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (the (name, locale) ladder: the caller's locale, then the documented default, then the loader's own no-locale answer); packages/plugins/plugin-email/src/email-service.ts#DEFAULT_TEMPLATE_LOCALE (the `en-US` the ladder's second rung asks for BY NAME — #7731 is what happened while that rung passed `undefined`)", - "note": "Load-bearing in both directions: the same template in two locales materializes as two rows, and the recipient's locale picks between them. The resolved row's locale is also the render locale for ADR-0053 format filters. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:411-416` had rotted onto the `TemplateLoader` docblock, the same #9225 resolver split that moved every `email-service.ts` citation in this file. The ladder is now anchored to the function that runs it plus the constant it names, so a rung deleted or the default renamed goes red instead of drifting. Re-closed by hand against 93ea19bca." + "note": "Load-bearing in both directions: the same template in two locales materializes as two rows, and the recipient's locale picks between them. The resolved row's locale is also the render locale for ADR-0053 format filters. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:411-416` had rotted onto the `TemplateLoader` docblock, the same #9225 resolver split that moved every `email-service.ts` citation in this file. The ladder is now anchored to the function that runs it plus the constant it names, so a rung deleted or the default renamed goes red instead of drifting. Re-closed by hand against 93ea19bca." }, "subject": { "status": "live", "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/email-template-materialization.dogfood.test.ts#email-template-materialization", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`subject: tpl.subject`); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (`renderTemplate(row.subject, data, renderOpts)` — the {{path}} holes filled with the resolved row's locale)", - "note": "ADR-0054 high-risk class (email-template-materialization): the authored subject is the representative check for the whole authoring→send pipeline, which crosses manifest-decomposition → the ObjectQL registry → the materializer → the sys_email_template row → the TemplateLoader → sendTemplate. Exactly the multi-layer seam that was broken here, and in three places at once. The proof authors a stack `emailTemplates:` entry overriding a BUILT-IN auth template and asserts the authored wording is what reaches the transport — the ADR-0078 false-compliance case #4509 named (an admin 'fixes' the password-reset mail and users keep receiving the built-in copy). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:444` had rotted onto an `EmailServiceOptions` docblock about sys_email PERSISTENCE, which is the most misleading rot in this file because it still reads as email-service configuration. The render moved with the #9225 resolver split. Re-closed by hand against 93ea19bca." + "note": "ADR-0054 high-risk class (email-template-materialization): the authored subject is the representative check for the whole authoring→send pipeline, which crosses manifest-decomposition → the ObjectQL registry → the materializer → the sys_email_template row → the TemplateLoader → sendTemplate. Exactly the multi-layer seam that was broken here, and in three places at once. The proof authors a stack `emailTemplates:` entry overriding a BUILT-IN auth template and asserts the authored wording is what reaches the transport — the ADR-0078 false-compliance case #4509 named (an admin 'fixes' the password-reset mail and users keep receiving the built-in copy). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:444` had rotted onto an `EmailServiceOptions` docblock about sys_email PERSISTENCE, which is the most misleading rot in this file because it still reads as email-service configuration. The render moved with the #9225 resolver split. Re-closed by hand against 93ea19bca." }, "bodyHtml": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`body_html: tpl.bodyHtml` — the camelCase→snake_case remap); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (`renderTemplate(row.body_html, data, renderOpts)`)", - "note": "The camelCase→snake_case remap the proof asserts alongside the subject. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:445` had rotted onto `persistence?: EmailPersistence;`, an options field, ~800 lines above the render. Same #9225 resolver split. Re-closed by hand against 93ea19bca." + "note": "The camelCase→snake_case remap the proof asserts alongside the subject. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:445` had rotted onto `persistence?: EmailPersistence;`, an options field, ~800 lines above the render. Same #9225 resolver split. Re-closed by hand against 93ea19bca." }, "bodyText": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`...(tpl.bodyText ? { body_text: tpl.bodyText } : {})` — omitted, never nulled); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (`row.body_text ? renderTemplate(row.body_text, …) : htmlToText(html)` — the auto-derive fallback)", - "note": "Omitted from the row rather than nulled when unset, so a re-seed never blanks it; the send path then auto-derives text from HTML (htmlToText). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:446-448` had rotted onto the `templateLoader` / `retries` option docblocks. Same #9225 resolver split. Re-closed by hand against 93ea19bca." + "note": "Omitted from the row rather than nulled when unset, so a re-seed never blanks it; the send path then auto-derives text from HTML (htmlToText). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:446-448` had rotted onto the `templateLoader` / `retries` option docblocks. Same #9225 resolver split. Re-closed by hand against 93ea19bca." }, "variables": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`...(tpl.variables?.length ? { variables_json: JSON.stringify(tpl.variables) } : {})`); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (parses `row.variables_json`, filters to the `required` ones and calls `requireVars` — a missing one throws MISSING_VARIABLES and the send never happens)", - "note": "All four child keys share this verdict — not drilled. `required` is a real runtime gate, not a hint. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — BOTH legs were wrong, and the bootstrap one is the sharpest rot in this file: `bootstrap-declared-email-templates.ts:91` lands on `is_system: tpl.isSystem`, i.e. on the line `isSystem` cites for ITSELF. Two entries citing one line for two different keys is a state the ledger cannot detect from the inside — the file names both keys, so key-mention anchors both, and the row projection really is 15 lines of near-identical shape. The real mapping is the last line of the same object literal. `email-service.ts:427-431` had rotted onto the `EmailTemplateRow` interface — the row SHAPE rather than the gate that reads it, which is the plausible-sibling failure key-mention was built for and cannot catch inside one file. Re-closed by hand against 93ea19bca." + "note": "All four child keys share this verdict — not drilled. `required` is a real runtime gate, not a hint. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — BOTH legs were wrong, and the bootstrap one is the sharpest rot in this file: `bootstrap-declared-email-templates.ts:91` lands on `is_system: tpl.isSystem`, i.e. on the line `isSystem` cites for ITSELF. Two entries citing one line for two different keys is a state the ledger cannot detect from the inside — the file names both keys, so key-mention anchors both, and the row projection really is 15 lines of near-identical shape. The real mapping is the last line of the same object literal. `email-service.ts:427-431` had rotted onto the `EmailTemplateRow` interface — the row SHAPE rather than the gate that reads it, which is the plausible-sibling failure key-mention was built for and cannot catch inside one file. Re-closed by hand against 93ea19bca." }, "fromOverride": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (fromOverride.address/.name → from_address/from_name, address-gated); packages/plugins/plugin-email/src/email-service.ts#sendTemplate (`input.from ?? (row.from_address ? { address: row.from_address, … } : undefined)` — the caller still wins); packages/plugins/plugin-email/src/email-service.ts#rowEnvelope (the generic row→message path, which REFUSES a row with no from_address)", - "note": "Both child keys share this verdict. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:450-453` had rotted onto the `logger` / `defaultTemplateContext` option fields. The send-end read stayed in `sendTemplate` across the #9225 split (it is an envelope read, not a render read), which is exactly why anchoring is worth doing here: the two halves of this entry moved apart and only the symbol names say which is which. `rowEnvelope` is added as the second local consumer — it was never cited, and it is the one that turns a missing from_address into a refusal rather than a silent send. Re-closed by hand against 93ea19bca." + "note": "Both child keys share this verdict. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:450-453` had rotted onto the `logger` / `defaultTemplateContext` option fields. The send-end read stayed in `sendTemplate` across the #9225 split (it is an envelope read, not a render read), which is exactly why anchoring is worth doing here: the two halves of this entry moved apart and only the symbol names say which is which. `rowEnvelope` is added as the second local consumer — it was never cited, and it is the one that turns a missing from_address into a refusal rather than a silent send. Re-closed by hand against 93ea19bca." }, "replyTo": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`...(tpl.replyTo ? { reply_to: tpl.replyTo } : {})`); packages/plugins/plugin-email/src/email-service.ts#sendTemplate (`input.replyTo ?? row.reply_to` — the row's value used only when the caller passes none); packages/plugins/plugin-email/src/email-service.ts#rowEnvelope (`if (row.reply_to) msg.replyTo = String(row.reply_to)` — the persisted row's own projection onto the outbound message)", - "note": "Honored on the outbound message when the caller passes no explicit replyTo. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:139` had rotted onto a docblock about the sys_email outbox id contract, ~1150 lines above the read and about a completely different mechanism. That is the largest single drift measured in this batch, and it passed every check for the usual reason. Re-closed by hand against 93ea19bca." + "note": "Honored on the outbound message when the caller passes no explicit replyTo. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:139` had rotted onto a docblock about the sys_email outbox id contract, ~1150 lines above the read and about a completely different mechanism. That is the largest single drift measured in this batch, and it passed every check for the usual reason. Re-closed by hand against 93ea19bca." }, "active": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`active: tpl.active`); packages/plugins/plugin-email/src/email-service.ts#resolveAndRenderTemplate (`if (row.active === false) throw new Error('TEMPLATE_INACTIVE: …')` — the send refuses before rendering); packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#deactivateDeclaredEmailTemplate (the withdrawal path: a deleted declared template flips `active: false` on its package-managed rows rather than destroying them)", - "note": "Also the withdrawal mechanism: deleting a declared template deactivates its rows rather than destroying them. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:418-419` had rotted onto the closing lines of the `TemplateLoader` docblock (`:419` is the bare `*/`), and the note's `bootstrap-declared-email-templates.ts:201` pointer — never in `evidence`, so nothing ever bounded it — lands on `if (row?.id) {` inside the seed-not-clobber branch, a different mechanism in the same file. The withdrawal path is now cited in `evidence` under its own symbol, where the checks can reach it. Re-closed by hand against 93ea19bca." + "note": "Also the withdrawal mechanism: deleting a declared template deactivates its rows rather than destroying them. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-service.ts:418-419` had rotted onto the closing lines of the `TemplateLoader` docblock (`:419` is the bare `*/`), and the note's `bootstrap-declared-email-templates.ts:201` pointer — never in `evidence`, so nothing ever bounded it — lands on `if (row?.id) {` inside the seed-not-clobber branch, a different mechanism in the same file. The withdrawal path is now cited in `evidence` under its own symbol, where the checks can reach it. Re-closed by hand against 93ea19bca." }, "isSystem": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`is_system: tpl.isSystem`); packages/plugins/plugin-email/src/email-plugin.ts#upsertTemplate (`if (existingRow.is_system === false) return;` — the built-in seeder stands down rather than reclaiming the row)", - "note": "The BUILT-IN seeder's provenance axis, distinct from the declared bridge's managed_by/customized pair — a declared template lands is_system:false, which is what stops the built-in auth seed reclaiming the row on the next boot. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the bootstrap leg was ACCURATE; `email-plugin.ts:544` had rotted onto `const result = await target.sendInline({` inside the operator's TEST-MAIL button, ~980 lines from the gate and about sending rather than seeding. `email-plugin.ts` is 1550 lines and names `is_system` on exactly four of them, three of which are prose — a file that large is where a line citation is worth least and an anchor most. Re-closed by hand against 93ea19bca." + "note": "The BUILT-IN seeder's provenance axis, distinct from the declared bridge's managed_by/customized pair — a declared template lands is_system:false, which is what stops the built-in auth seed reclaiming the row on the next boot. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — the bootstrap leg was ACCURATE; `email-plugin.ts:544` had rotted onto `const result = await target.sendInline({` inside the operator's TEST-MAIL button, ~980 lines from the gate and about sending rather than seeding. `email-plugin.ts` is 1550 lines and names `is_system` on exactly four of them, three of which are prose — a file that large is where a line citation is worth least and an anchor most. Re-closed by hand against 93ea19bca." }, "description": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts#mapTemplateToRow (`...(tpl.description ? { description: tpl.description } : {})`)", - "note": "Materialized into `description`; docs-shaped — read by humans in Studio, never by the send path. 2026-08-28: RE-ANCHORED (#13003) — `:92` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." + "note": "Materialized into `description`; docs-shaped — read by humans in Studio, never by the send path. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) — `:92` was ACCURATE, so this is a grammar migration only. Re-closed by hand against 93ea19bca." } } } diff --git a/packages/spec/liveness/manifest.json b/packages/spec/liveness/manifest.json index c10bc68a692..d61fa598d11 100644 --- a/packages/spec/liveness/manifest.json +++ b/packages/spec/liveness/manifest.json @@ -63,7 +63,7 @@ "evidence": "packages/plugins/plugin-security/src/suggested-audience-bindings.ts#collectDeclaredSuggestions (declared permission strings from every enabled installed package feed the suggested audience bindings — `Array.isArray(manifest?.permissions) ? manifest.permissions : []`, which is also where the legacy-arm-only reading below is measured)", "verifiedAt": "2026-08-28", "evidenceScope": "cross-repo", - "note": "LIVE ON ONE ARM ONLY, and the split matters. `ManifestPermissionsSchema` (manifest.zod.ts:54) is a union of the legacy flat `string[]` and the structured `PluginPermissionsSchema` (services / hooks / network / fs, ADR-0025 §3.2). The single reader is guarded by `Array.isArray(manifest?.permissions)`, so it reads the LEGACY arm and skips the structured one entirely; no other reader exists in objectstack or objectui. PluginPermissionEnforcer (packages/core/src/security/plugin-permission-enforcer.ts:94-104) is not the missing consumer — it registers the set the install-time consent flow persisted to `sys_package_installation.granted_permissions`, explicitly \"independent of whatever the manifest *requested*\", and nothing in this repo feeds the manifest declaration into it. The verdict is `live` because the key is read; the structured arm's zero is recorded apart rather than being flattened into this one-level row. Cloud unmeasured (see `_note`) — the consent flow ADR-0025 §3.5 describes lives there. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — the citation was still ACCURATE (`suggested-audience-bindings.ts:252` names the read), so this is a grammar migration. What the anchor buys here is specific to this row's shape: the whole `live` verdict rests on ONE reader, so if `collectDeclaredSuggestions` is deleted or renamed the entry now goes red instead of pointing at whatever line 252 has become. Re-closed by hand against c459da6bc." + "note": "LIVE ON ONE ARM ONLY, and the split matters. `ManifestPermissionsSchema` (manifest.zod.ts:54) is a union of the legacy flat `string[]` and the structured `PluginPermissionsSchema` (services / hooks / network / fs, ADR-0025 §3.2). The single reader is guarded by `Array.isArray(manifest?.permissions)`, so it reads the LEGACY arm and skips the structured one entirely; no other reader exists in objectstack or objectui. PluginPermissionEnforcer (packages/core/src/security/plugin-permission-enforcer.ts:94-104) is not the missing consumer — it registers the set the install-time consent flow persisted to `sys_package_installation.granted_permissions`, explicitly \"independent of whatever the manifest *requested*\", and nothing in this repo feeds the manifest declaration into it. The verdict is `live` because the key is read; the structured arm's zero is recorded apart (commit aaacf1d5c, the `PluginPermissionsSchema` docblock) rather than being flattened into this one-level row. Cloud unmeasured (see `_note`) — the consent flow ADR-0025 §3.5 describes lives there. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — the citation was still ACCURATE (`suggested-audience-bindings.ts:252` names the read), so this is a grammar migration. What the anchor buys here is specific to this row's shape: the whole `live` verdict rests on ONE reader, so if `collectDeclaredSuggestions` is deleted or renamed the entry now goes red instead of pointing at whatever line 252 has become. Re-closed by hand against c459da6bc." }, "objects": { "status": "live", diff --git a/packages/spec/liveness/mapping.json b/packages/spec/liveness/mapping.json index 4add57c87a4..e5fe32cc373 100644 --- a/packages/spec/liveness/mapping.json +++ b/packages/spec/liveness/mapping.json @@ -1,12 +1,12 @@ { "type": "mapping", - "_note": "MappingSchema (#2611 reusable import mapping). Consumers: the REST import path — resolveNamedMapping fetches the artifact by name and validates it against the request (packages/rest/src/import-mapping.ts:60-107), applyMappingToRows runs the fieldMapping pipeline (:115-167), and import-prepare adopts the artifact's mode/upsertKey as request defaults (packages/rest/src/import-prepare.ts:321-326); objectui's ImportWizard offers registered mappings in a saved-mapping picker (@940ba24 packages/plugin-grid/src/ImportWizard.tsx:979). Seeded 2026-08-01 (#4488) at 8 of 11 live; 7 of 7 live since #4509 removed the three that were not. The IMPORT half of the schema is real and loudly enforced (unsupported transforms/formats are 400s, not silent skips — Prime Directive #10). What left in 17.0.0: the EXPORT half (`extractQuery` — no exporter reads a mapping artifact at all) and the two tuning knobs (`errorPolicy`, `batchSize` — error handling belongs to the import REQUEST, and the write path sizes its own batches). Rows DELETED rather than kept: MappingSchema is strict, so the keys left the walked shape and a retained row would report ORPHAN. Both knobs were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied) — removal was the only channel that could reach an author, which is why they went out inside the 17.0.0 window rather than after a deprecation cycle. 2026-08-28 (#13003): all seven `path:NNN` citations in this file were re-anchored to their consuming symbols. This is the one file in batch 4 whose lines were mostly RIGHT — repaired and re-measured by #11210 on 2026-08-23 — and it is kept as the counter-example: five of the seven landed on the read or inside the statement that performs it, and the two that did not (`mode`, `upsertKey`) had already drifted five lines in five days, onto a guard and a comment inside the right function. Correctness of a line is a property of the day it was measured; the anchor is what makes it a property of the code.", + "_note": "MappingSchema (#2611 reusable import mapping). Consumers: the REST import path — resolveNamedMapping fetches the artifact by name and validates it against the request (packages/rest/src/import-mapping.ts:60-107), applyMappingToRows runs the fieldMapping pipeline (:115-167), and import-prepare adopts the artifact's mode/upsertKey as request defaults (packages/rest/src/import-prepare.ts:321-326); objectui's ImportWizard offers registered mappings in a saved-mapping picker (@940ba24 packages/plugin-grid/src/ImportWizard.tsx:979). Seeded 2026-08-01 (#4488) at 8 of 11 live; 7 of 7 live since #4509 removed the three that were not. The IMPORT half of the schema is real and loudly enforced (unsupported transforms/formats are 400s, not silent skips — Prime Directive #10). What left in 17.0.0: the EXPORT half (`extractQuery` — no exporter reads a mapping artifact at all) and the two tuning knobs (`errorPolicy`, `batchSize` — error handling belongs to the import REQUEST, and the write path sizes its own batches). Rows DELETED rather than kept: MappingSchema is strict, so the keys left the walked shape and a retained row would report ORPHAN. Both knobs were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied) — removal was the only channel that could reach an author, which is why they went out inside the 17.0.0 window rather than after a deprecation cycle. 2026-08-28 (commit 8f10a79f7): all seven `path:NNN` citations in this file were re-anchored to their consuming symbols. This is the one file in batch 4 whose lines were mostly RIGHT — repaired and re-measured by commit 905019b1b on 2026-08-23 — and it is kept as the counter-example: five of the seven landed on the read or inside the statement that performs it, and the two that did not (`mode`, `upsertKey`) had already drifted five lines in five days, onto a guard and a comment inside the right function. Correctness of a line is a property of the day it was measured; the anchor is what makes it a property of the code.", "props": { "name": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (the artifact is read by `{ type: 'mapping', name: mappingName }`; a miss is 404 MAPPING_NOT_FOUND)", - "note": "artifact resolution key for the request's `mappingName` (missing → 404 MAPPING_NOT_FOUND). 2026-08-28: RE-ANCHORED (#13003) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because #11210 re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." + "note": "artifact resolution key for the request's `mappingName` (missing → 404 MAPPING_NOT_FOUND). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because commit 905019b1b re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", @@ -18,31 +18,31 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (`const declared = artifact.sourceFormat` — `xml`/`sql` are refused outright with MAPPING_FORMAT_UNSUPPORTED, a csv-declared mapping applies to xlsx too, and any other mismatch is MAPPING_FORMAT_MISMATCH; never a silent reinterpretation)", - "note": "declared-format gate: `xml`/`sql` are rejected outright (the import endpoint accepts csv/json/xlsx), and a csv-declared mapping applies to xlsx too; a mismatch is a 400, never a silent reinterpretation. 2026-08-28: RE-ANCHORED (#13003) — the enclosing function is unchanged, but the cited RANGE's endpoint (`:97`) had drifted onto the sibling `transform === 'javascript'` refusal, a different key's check inside the same function. That is precisely why the anchor names the function and not a span. Re-closed by hand against 8cb96ec41." + "note": "declared-format gate: `xml`/`sql` are rejected outright (the import endpoint accepts csv/json/xlsx), and a csv-declared mapping applies to xlsx too; a mismatch is a 400, never a silent reinterpretation. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the enclosing function is unchanged, but the cited RANGE's endpoint (`:97`) had drifted onto the sibling `transform === 'javascript'` refusal, a different key's check inside the same function. That is precisely why the anchor names the function and not a span. Re-closed by hand against 8cb96ec41." }, "targetObject": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (`if (artifact.targetObject !== objectName)` → 400 MAPPING_TARGET_MISMATCH)", - "note": "must equal the URL object or the import 400s (MAPPING_TARGET_MISMATCH). 2026-08-28: RE-ANCHORED (#13003) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because #11210 re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." + "note": "must equal the URL object or the import 400s (MAPPING_TARGET_MISMATCH). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because commit 905019b1b re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." }, "fieldMapping": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (the `transform === 'javascript'` refusal — implement-or-reject-loudly, there is no server sandbox); packages/rest/src/import-mapping.ts#applyMappingToRows (the pipeline itself: source/target/transform/params, with none/constant/map/split/join applied here and `lookup` copied through for the pipeline's reference resolution)", - "note": "Lines re-measured 2026-08-23 (#11210): the second range ended at :167 in a 164-line file — the file shrank under the citation and no gate could see it, since the FILE still resolved. The consumer never moved: the javascript rejection is the loop at :95-102 (was :98-105) and the pipeline is applyMappingToRows at :112-164 (was :115-167); the three inline line refs below were off by the same three lines and are corrected with them. the pipeline itself: source/target/transform/params all consumed. none/constant/map/split/join applied in applyMappingToRows (`params.separator` :121, `.value` :129, `.valueMap` :134); `lookup` copies through for the pipeline's metaMap reference resolution; `javascript` is REJECTED with a 400 (no server sandbox — implement-or-reject-loudly). SUB-WALK BOUNDARY, resolved: `params`' lookup-specific keys (`object`/`fromField`/`toField`/`autoCreate`) were read by nothing — reference resolution comes from the target object's own field definitions, not from these — and were REMOVED in the 17.x line (#10329, ADR-0049 enforce-or-remove; strict deletion, so nothing dead remains one level below the drill). Every spelling — the four canonical keys and their eleven ex-aliases — now lands on a guidance prescription at parse; the mapping-lookup-params-removed conversion (protocol 18) strips them from stored sources. The surviving params keys (`value`/`valueMap`/`separator`) are all consumed by applyMappingToRows. 2026-08-28: RE-ANCHORED (#13003) — both ranges land inside their functions (their endpoints `:102` and `:164` are the functions' own closing braces), so this is the grammar migration. Worth recording beside the 2026-08-23 entry above: that repair moved these pointers three lines and they were right for five days; the anchor is the version that does not need re-measuring. Re-closed by hand against 8cb96ec41." + "note": "Lines re-measured 2026-08-23 (commit 905019b1b): the second range ended at :167 in a 164-line file — the file shrank under the citation and no gate could see it, since the FILE still resolved. The consumer never moved: the javascript rejection is the loop at :95-102 (was :98-105) and the pipeline is applyMappingToRows at :112-164 (was :115-167); the three inline line refs below were off by the same three lines and are corrected with them. the pipeline itself: source/target/transform/params all consumed. none/constant/map/split/join applied in applyMappingToRows (`params.separator` :121, `.value` :129, `.valueMap` :134); `lookup` copies through for the pipeline's metaMap reference resolution; `javascript` is REJECTED with a 400 (no server sandbox — implement-or-reject-loudly). SUB-WALK BOUNDARY, resolved: `params`' lookup-specific keys (`object`/`fromField`/`toField`/`autoCreate`) were read by nothing — reference resolution comes from the target object's own field definitions, not from these — and were REMOVED in the 17.x line (commit 15d58dbf1, ADR-0049 enforce-or-remove; strict deletion, so nothing dead remains one level below the drill). Every spelling — the four canonical keys and their eleven ex-aliases — now lands on a guidance prescription at parse; the mapping-lookup-params-removed conversion (protocol 18) strips them from stored sources. The surviving params keys (`value`/`valueMap`/`separator`) are all consumed by applyMappingToRows. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — both ranges land inside their functions (their endpoints `:102` and `:164` are the functions' own closing braces), so this is the grammar migration. Worth recording beside the 2026-08-23 entry above: that repair moved these pointers three lines and they were right for five days; the anchor is the version that does not need re-measuring. Re-closed by hand against 8cb96ec41." }, "mode": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/import-prepare.ts#prepareImportRequest (`if (body?.writeMode === undefined && (mappingArtifact.mode === 'update' || mappingArtifact.mode === 'upsert')) writeMode = mappingArtifact.mode` — a DEFAULT; an explicit request `writeMode` still wins)", - "note": "an artifact declaring update/upsert sets the import's writeMode default (an explicit request `writeMode` still wins). 2026-08-28: RE-ANCHORED (#13003) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because #11210 re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." + "note": "an artifact declaring update/upsert sets the import's writeMode default (an explicit request `writeMode` still wins). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because commit 905019b1b re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." }, "upsertKey": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/import-prepare.ts#prepareImportRequest (`if (matchFields.length === 0 && Array.isArray(mappingArtifact.upsertKey))` — adopted as the upsert match fields only when the request names none)", - "note": "adopted as the upsert match fields when the request names none. 2026-08-28: RE-ANCHORED (#13003) — `:325` sat on a comment line two lines above the read, inside the right function. The grammar migration, not a repair. Re-closed by hand against 8cb96ec41." + "note": "adopted as the upsert match fields when the request names none. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — `:325` sat on a comment line two lines above the read, inside the right function. The grammar migration, not a repair. Re-closed by hand against 8cb96ec41." } } } diff --git a/packages/spec/liveness/object.json b/packages/spec/liveness/object.json index 5fba85e41a6..a7770470eca 100644 --- a/packages/spec/liveness/object.json +++ b/packages/spec/liveness/object.json @@ -47,7 +47,7 @@ }, "editMode": { "status": "live", - "note": "objectui record-edit routing: app-shell recordFormNavigation branches on editMode !== 'page' and AppContent handleEdit routes modal form vs dedicated /record/:id/edit route. Declared by the #11408 ruling (#10144 declare-or-rule-out family)." + "note": "objectui record-edit routing: app-shell recordFormNavigation branches on editMode !== 'page' and AppContent handleEdit routes modal form vs dedicated /record/:id/edit route. Declared by the maintainer ruling that commit f11fc61c5 executes (#10144 declare-or-rule-out family)." }, "fieldGroups": { "status": "live", @@ -67,7 +67,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/engine.ts#resolveDatasourceBinding (limb 1 of the routing precedence — `object?.datasource && object.datasource !== 'default'` returns `{ datasource, via: 'explicit' }`, which getDriver then resolves to a driver); packages/objectql/src/engine.ts#assertWriteAllowed (reads the same key to find the datasource DEFINITION whose `external.allowWrites` gates the write)", - "note": "driver routing. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED in the same pass — the citation was `engine.ts:1147`, which is IN RANGE of this 13593-line file and lands in a docblock about `assertWriteAllowed`'s placement, not on any read of this key. That is the #12516 class exactly: file present, line inside it, and the file names `datasource` on dozens of lines that all mean the engine's own DatasourceDef/driver registry rather than the object key. Re-closed by hand against c459da6bc." + "note": "driver routing. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED in the same pass — the citation was `engine.ts:1147`, which is IN RANGE of this 13593-line file and lands in a docblock about `assertWriteAllowed`'s placement, not on any read of this key. That is the #12516 class exactly: file present, line inside it, and the file names `datasource` on dozens of lines that all mean the engine's own DatasourceDef/driver registry rather than the object key. Re-closed by hand against c459da6bc." }, "lifecycle": { "status": "live", @@ -84,13 +84,13 @@ "status": "live", "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#syncTableIndexes (takes the object's declared indexes, unions them with the field-derived uniques and hands the result to the DDL writer); packages/drivers/driver-sql/src/sql-driver.ts#syncDeclaredIndexes (the DDL writer named in this note — knex `table.index()` / `table.unique()` per declared entry); packages/drivers/driver-sql/src/sql-driver.ts#initObjects (registration-time read of `(obj as any).indexes`)", "verifiedAt": "2026-08-28", - "note": "DDL — but only through `name`/`fields`/`unique`. `syncDeclaredIndexes` creates every declared index via knex's `table.index()` / `table.unique()`, and the differ's `DeclaredIndexInput` carries `name`/`fields`/`unique`/`nullSafeColumns`. The other two child keys were RETIRED 2026-08-06 (#5248, #4943, ADR-0049): `indexes[].type` selected no access method (and its `.default('btree')` materialized an inert knob into every parse output) and `indexes[].partial` produced a FULL index with the predicate discarded. Both are `retiredKey()` tombstones at `IndexSchema` and are stripped from sources by the protocol-17 conversion `object-index-type-partial-removed`. The container stays `live` because the surviving keys drive real DDL. ⚠ Not to be confused with driver-sql's own `partial: boolean`, which is parsed back out of the database's own CREATE INDEX DDL for drift detection and is unrelated to the declaration surface. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the citation was `sql-driver.ts:1181`, in range of this 16289-line file but landing in a docblock about aggregate-function lowering (`count_distinct`), nowhere near an index read. The note already named `syncDeclaredIndexes` in prose and that pointer stayed true the whole time while the evidence line rotted — the same note-survives/evidence-rots split recorded on action.requiredPermissions. Re-closed by hand against c459da6bc." + "note": "DDL — but only through `name`/`fields`/`unique`. `syncDeclaredIndexes` creates every declared index via knex's `table.index()` / `table.unique()`, and the differ's `DeclaredIndexInput` carries `name`/`fields`/`unique`/`nullSafeColumns`. The other two child keys were RETIRED 2026-08-06 (#5248, #4943, ADR-0049): `indexes[].type` selected no access method (and its `.default('btree')` materialized an inert knob into every parse output) and `indexes[].partial` produced a FULL index with the predicate discarded. Both are `retiredKey()` tombstones at `IndexSchema` and are stripped from sources by the protocol-17 conversion `object-index-type-partial-removed`. The container stays `live` because the surviving keys drive real DDL. ⚠ Not to be confused with driver-sql's own `partial: boolean`, which is parsed back out of the database's own CREATE INDEX DDL for drift detection and is unrelated to the declaration surface. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — the citation was `sql-driver.ts:1181`, in range of this 16289-line file but landing in a docblock about aggregate-function lowering (`count_distinct`), nowhere near an index read. The note already named `syncDeclaredIndexes` in prose and that pointer stayed true the whole time while the evidence line rotted — the same note-survives/evidence-rots split recorded on action.requiredPermissions. Re-closed by hand against c459da6bc." }, "validations": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`const rules = objectSchema?.validations` — the write-path evaluator); packages/objectql/src/validation/rule-validator.ts#legalNextStates (the same read, for the `state_machine` member this note names); packages/objectql/src/validation/rule-validator.ts#needsPriorRecord (reads the rule list to decide whether the prior record must be loaded)", - "note": "incl. state_machine. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the citation was `rule-validator.ts:154`, in range of this 2228-line file and landing inside a docblock about `requiredWhen` root binding; the three real reads of `objectSchema.validations` are at the symbols above. Re-closed by hand against c459da6bc." + "note": "incl. state_machine. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — the citation was `rule-validator.ts:154`, in range of this 2228-line file and landing inside a docblock about `requiredWhen` root binding; the three real reads of `objectSchema.validations` are at the symbols above. Re-closed by hand against c459da6bc." }, "activityMilestones": { "status": "live", @@ -101,19 +101,19 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/app-plugin.ts#collectBundleActions (walks `bundle.objects[*].actions` and `bundle.manifest.objects[*].actions`, stamping each entry with its parent object name — the list the action body-runner binding and the /meta surface are both built from)", - "note": "served on /meta/object/:name. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the citation was `app-plugin.ts:929`, in range of this 1840-line file but landing in the JOB scheduling block (`svc.schedule(...)`), which reads nothing of this key. The file mentions `actions` on plenty of lines, so the #11457 key-mention check anchors and passes; only a hand read separates the per-object action walk from the unrelated `bundle.actions` and job paths. Re-closed by hand against c459da6bc." + "note": "served on /meta/object/:name. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — the citation was `app-plugin.ts:929`, in range of this 1840-line file but landing in the JOB scheduling block (`svc.schedule(...)`), which reads nothing of this key. The file mentions `actions` on plenty of lines, so the #11457 key-mention check anchors and passes; only a hand read separates the per-object action walk from the unrelated `bundle.actions` and job paths. Re-closed by hand against c459da6bc." }, "managedBy": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/data/object.zod.ts#resolveCrudAffordances (`obj?.managedBy ?? 'platform'` selects the bucket's default CRUD affordance row — the `ui crudAffordances` half of this note); packages/plugins/plugin-security/src/system-write-guard.ts#assertEngineOwnedWriteAllowed (`const bucket = schema?.managedBy` — engine-owned buckets refuse user-context writes the bucket's affordances forbid); packages/spec/src/data/injected-system-columns.ts#resolveInjectedSystemColumns (a set `managedBy` withholds the ownership anchors, and `'better-auth'` withholds every injected column); packages/objectql/src/secret-fields.ts#collectMaskedReadFields (a `'better-auth'` table's own columns are exempted from password/secret masking, sibling `collectMaskedPasswordFields` the same)", - "note": "default perms; ui crudAffordances. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the citation was `registry.ts:208`, in range of that 3921-line file and landing on `merged.indexes = [...]`, the INDEX merge of a sibling key. registry.ts names `managedBy` on ten lines so the key-mention check passes, but nine of them are prose in comments and the tenth (:986) interpolates the value into a WARNING string — a diagnostic, not a decision. The decisions moved into @objectstack/spec (`resolveCrudAffordances`, `resolveInjectedSystemColumns`) and plugin-security: the consumer-promoted-into-another-package shape the gate's own guidance names. Re-closed by hand against c459da6bc." + "note": "default perms; ui crudAffordances. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — the citation was `registry.ts:208`, in range of that 3921-line file and landing on `merged.indexes = [...]`, the INDEX merge of a sibling key. registry.ts names `managedBy` on ten lines so the key-mention check passes, but nine of them are prose in comments and the tenth (:986) interpolates the value into a WARNING string — a diagnostic, not a decision. The decisions moved into @objectstack/spec (`resolveCrudAffordances`, `resolveInjectedSystemColumns`) and plugin-security: the consumer-promoted-into-another-package shape the gate's own guidance names. Re-closed by hand against c459da6bc." }, "ownership": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/data/injected-system-columns.ts#resolveInjectedSystemColumns (`typeof obj.ownership === 'string'` then the POSITIVE per-tier list — `'user'`/omitted takes both anchors, `'business_unit'` takes only `owning_business_unit_id`, `'org'`/`'none'` take neither); packages/objectql/src/registry.ts#applySystemFields (materializes that plan's columns onto the schema — it consults the plan and does NOT read the key itself)", - "note": "#3175 record-ownership model. applySystemFields injects the reassignable owner_id lookup by default (ownership:'user'); 'org'|'none' opt out of BOTH ownership anchors (Dataverse-style catalog/junction tables); ADR-0117 D1's 'business_unit' tier (engine #5677, enum member #5678) takes owning_business_unit_id and deliberately no owner_id. Per-tier authority is resolveInjectedSystemColumns (spec/data). Proven in objectql/src/registry.test.ts. Evidence line refreshed 2026-07-30 (was :272, drifted). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `registry.ts:292` had drifted again (it now lands in a docblock about the `multiTenant` env var), and the re-closure falsified the parenthetical as well: `applySystemFields` reads `ownership` NOWHERE in code. Its nine occurrences in that function are all comments explaining a decision it delegates; the read is `resolveInjectedSystemColumns` in @objectstack/spec, which this note already named as \"per-tier authority\". So the entry stays `live` on stronger evidence, and the drift it demonstrates is the one the line bound cannot see twice over — a line refreshed 2026-07-30 rotting again inside the same file, with the prose beside it pointing at a reader that had already moved packages. Re-closed by hand against c459da6bc." + "note": "#3175 record-ownership model. applySystemFields injects the reassignable owner_id lookup by default (ownership:'user'); 'org'|'none' opt out of BOTH ownership anchors (Dataverse-style catalog/junction tables); ADR-0117 D1's 'business_unit' tier (engine #5677, enum member #5678) takes owning_business_unit_id and deliberately no owner_id. Per-tier authority is resolveInjectedSystemColumns (spec/data). Proven in objectql/src/registry.test.ts. Evidence line refreshed 2026-07-30 (was :272, drifted). 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `registry.ts:292` had drifted again (it now lands in a docblock about the `multiTenant` env var), and the re-closure falsified the parenthetical as well: `applySystemFields` reads `ownership` NOWHERE in code. Its nine occurrences in that function are all comments explaining a decision it delegates; the read is `resolveInjectedSystemColumns` in @objectstack/spec, which this note already named as \"per-tier authority\". So the entry stays `live` on stronger evidence, and the drift it demonstrates is the one the line bound cannot see twice over — a line refreshed 2026-07-30 rotting again inside the same file, with the prose beside it pointing at a reader that had already moved packages. Re-closed by hand against c459da6bc." }, "access": { "status": "live", @@ -124,7 +124,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-security/src/security-plugin.ts#getObjectSecurityMeta (the read — `normalizeRequiredPermissions((obj as any)?.requiredPermissions)` onto the cached per-object posture); packages/plugins/plugin-security/src/security-plugin.ts#normalizeRequiredPermissions (folds the `string[] | per-op map` authoring shape into per-CRUD buckets); packages/plugins/plugin-security/src/security-plugin.ts#requiredCapsForOperation (the AND-gate lookup the middleware denies on); packages/plugins/plugin-security/src/permission-evaluator.ts#crudBucketForOperation (keeps the buckets in lockstep with OPERATION_TO_PERMISSION)", - "note": "ADR-0066 D3 object capability contract — the security middleware denies unless the caller's systemPermissions union covers it (AND-gate, before the CRUD grant). Mirrors App.requiredPermissions. Unit + full-middleware proven in plugin-security/security-plugin.test.ts. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `security-plugin.ts:132` is in range of this 6862-line file but lands in a docblock about platform-admin-EXCLUSIVE capabilities; the type it claimed to name (`NormalizedRequiredPermissions`) is declared at :190 and the actual key read is ~6400 lines further down, in `getObjectSecurityMeta`. Re-closed by hand against c459da6bc." + "note": "ADR-0066 D3 object capability contract — the security middleware denies unless the caller's systemPermissions union covers it (AND-gate, before the CRUD grant). Mirrors App.requiredPermissions. Unit + full-middleware proven in plugin-security/security-plugin.test.ts. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `security-plugin.ts:132` is in range of this 6862-line file but lands in a docblock about platform-admin-EXCLUSIVE capabilities; the type it claimed to name (`NormalizedRequiredPermissions`) is declared at :190 and the actual key read is ~6400 lines further down, in `getObjectSecurityMeta`. Re-closed by hand against c459da6bc." }, "userActions": { "status": "live", @@ -134,20 +134,20 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/data/injected-system-columns.ts#resolveInjectedSystemColumns (`sf?.tenant !== false` decides the organization_id column and `sf?.audit !== false` the audit family — the injection authority); packages/objectql/src/registry.ts#applySystemFields (materializes that plan); packages/plugins/plugin-security/src/security-plugin.ts#getObjectSecurityMeta (`systemFields?.tenant === false` read as the tenancy opt-out on the per-object posture); packages/plugins/plugin-security/src/security-plugin.ts#loadObjectFieldNames (the same opt-out, cached for the RLS wildcard-policy applicability decision)", - "note": "organization_id auto-inject gate. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `security-plugin.ts:3281` had rotted in range onto a bootstrap `catalogSeedPasses()` call; both real reads live ~3200 lines later. The `registry.ts (column injection)` half was a citation with no line at all, which no line bound could ever falsify: the injection DECISION is `resolveInjectedSystemColumns` in @objectstack/spec and registry only applies it, so the plan is now cited beside the applier. Re-closed by hand against c459da6bc." + "note": "organization_id auto-inject gate. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `security-plugin.ts:3281` had rotted in range onto a bootstrap `catalogSeedPasses()` call; both real reads live ~3200 lines later. The `registry.ts (column injection)` half was a citation with no line at all, which no line bound could ever falsify: the injection DECISION is `resolveInjectedSystemColumns` in @objectstack/spec and registry only applies it, so the plan is now cited beside the applier. Re-closed by hand against c459da6bc." }, "sharingModel": { "status": "live", "verifiedAt": "2026-08-28", "proof": "packages/qa/dogfood/test/controlled-by-parent.dogfood.test.ts#cbp-controlled-by-parent", "evidence": "packages/plugins/plugin-sharing/src/sharing-service.ts#effectiveSharingModel (`schema?.sharingModel ?? schema?.security?.sharingModel`, collapsed onto the three behaviours the service enforces; `controlled_by_parent` folds to the master-derived branch and an unrecognised stored value fails CLOSED to `private`)", - "note": "ADR-0055 high-risk class (sharing): the `controlled_by_parent` value derives a detail object's access from its master — the security layer injects `masterFK IN (accessible master ids)` on reads and requires master edit-access on by-id writes. The proof asserts a member who cannot read the master can neither read nor by-id-write the detail, and is not over-blocked on a master they own. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sharing-service.ts:54` had rotted onto a blank line between an interface and a `makeShareId` docblock. Re-closed by hand against c459da6bc." + "note": "ADR-0055 high-risk class (sharing): the `controlled_by_parent` value derives a detail object's access from its master — the security layer injects `masterFK IN (accessible master ids)` on reads and requires master edit-access on by-id writes. The proof asserts a member who cannot read the master can neither read nor by-id-write the detail, and is not over-blocked on a master they own. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `sharing-service.ts:54` had rotted onto a blank line between an interface and a `makeShareId` docblock. Re-closed by hand against c459da6bc." }, "publicSharing": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-sharing/src/share-link-service.ts#getPolicy (`const raw = schema?.publicSharing` — the policy extraction: enabled, allowedAudiences, allowedPermissions, maxExpiryDays, redactFields, eligibility); packages/plugins/plugin-sharing/src/share-link-service.ts#createLink (mint authority — an object that has not enabled publicSharing is rejected 422)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — BOTH line citations had rotted in range. `:48` landed in a docblock about `sys_share_link` being `managedBy: 'engine-owned'` (the extraction is at ~:97), and the `:170` the prose gave for the 422 is now inside `createLink` ~250 lines further down. Re-closed by hand against c459da6bc." + "note": "2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — BOTH line citations had rotted in range. `:48` landed in a docblock about `sys_share_link` being `managedBy: 'engine-owned'` (the extraction is at ~:97), and the `:170` the prose gave for the 422 is now inside `createLink` ~250 lines further down. Re-closed by hand against c459da6bc." }, "tenancy": { "children": { @@ -155,7 +155,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/data/object.zod.ts#isTenancyDisabled (the predicate itself — `tenancy?.enabled === false`); packages/drivers/driver-sql/src/sql-driver.ts#computeTenantField (calls it as the explicit opt-out that wins over any column-presence heuristic, returning `null` so no driver-level org scope is applied); packages/plugins/plugin-security/src/security-plugin.ts#getObjectSecurityMeta (reads `tenancy?.enabled === false` directly onto the posture's `tenancyDisabled`); packages/spec/src/data/injected-system-columns.ts#resolveInjectedSystemColumns (the same predicate withholds the organization_id column)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sql-driver.ts:1081` is in range of this 16289-line file and lands in a docblock about withheld cross-field filter diagnostics, unrelated to tenancy. The predicate itself has since been promoted into @objectstack/spec (`isTenancyDisabled`), which the driver imports; citing only the driver left the actual read uncited. Re-closed by hand against c459da6bc." + "note": "2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `sql-driver.ts:1081` is in range of this 16289-line file and lands in a docblock about withheld cross-field filter diagnostics, unrelated to tenancy. The predicate itself has since been promoted into @objectstack/spec (`isTenancyDisabled`), which the driver imports; citing only the driver left the actual read uncited. Re-closed by hand against c459da6bc." }, "tenantField": { "status": "live", @@ -184,7 +184,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/metadata-protocol/src/protocol.ts#searchAll (`const enable = obj.enable ?? {}` then `if (enable.searchable === false) continue` — the object is skipped for cross-object search)", - "note": "Global Search (searchAll, M10.5): `enable.searchable === false` opts the object out of cross-object search. Behavior-changing reader — the 2026-06 audit mis-classified as dead (search executor not re-verified). default(true), so still not authorWarn'd. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `protocol.ts:2967` is in range of this 20255-line file and lands inside `normalizeSortNodes`, ~7100 lines from the read; the note already named `searchAll` in prose. Note what the key-mention check sees here: the file's `searchable` hits are almost all `searchableFields`, a DIFFERENT object key, which `\\bsearchable\\b` does not match — the check passes on standalone `searchable` in comment prose instead. Re-closed by hand against c459da6bc." + "note": "Global Search (searchAll, M10.5): `enable.searchable === false` opts the object out of cross-object search. Behavior-changing reader — the 2026-06 audit mis-classified as dead (search executor not re-verified). default(true), so still not authorWarn'd. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `protocol.ts:2967` is in range of this 20255-line file and lands inside `normalizeSortNodes`, ~7100 lines from the read; the note already named `searchAll` in prose. Note what the key-mention check sees here: the file's `searchable` hits are almost all `searchableFields`, a DIFFERENT object key, which `\\bsearchable\\b` does not match — the check passes on standalone `searchable` in comment prose instead. Re-closed by hand against c459da6bc." }, "files": { "status": "live", @@ -203,7 +203,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/metadata-protocol/src/protocol.ts#cloneData (`if (schema?.enable?.clone === false)` ⇒ 403 CLONE_DISABLED; an absent block or absent flag stays enabled, matching the spec default)", - "note": "cloneData() gates on schema.enable.clone (explicit false ⇒ 403 CLONE_DISABLED); exposed at POST /data/:object/:id/clone (rest-server.ts registerDataActionEndpoints). POINTER REPAIRED 2026-07 — cited packages/objectql/src/protocol.ts:2259 until the protocol moved to @objectstack/metadata-protocol. The claim never stopped being true, but the stale-evidence check could not surface the rot: 47 parse-artefact false positives were burying it (fixed in the same pass). Behaviour guarded by packages/objectql/src/protocol-clone-real-engine.test.ts. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED AGAIN — the 2026-07 repair moved the citation to the right FILE and it has been wrong ever since inside it: `:2938` lands in `normalizeSortNodes` while `cloneData` is at ~:9648, a 6700-line in-range drift. This entry is therefore the worked example of why the anchor exists — its pointer has now been repaired twice, once for a cross-package move that the existence check could see, and once for an in-file move that nothing in the gate could. Re-closed by hand against c459da6bc." + "note": "cloneData() gates on schema.enable.clone (explicit false ⇒ 403 CLONE_DISABLED); exposed at POST /data/:object/:id/clone (rest-server.ts registerDataActionEndpoints). POINTER REPAIRED 2026-07 — cited packages/objectql/src/protocol.ts:2259 until the protocol moved to @objectstack/metadata-protocol. The claim never stopped being true, but the stale-evidence check could not surface the rot: 47 parse-artefact false positives were burying it (fixed in the same pass). Behaviour guarded by packages/objectql/src/protocol-clone-real-engine.test.ts. 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED AGAIN — the 2026-07 repair moved the citation to the right FILE and it has been wrong ever since inside it: `:2938` lands in `normalizeSortNodes` while `cloneData` is at ~:9648, a 6700-line in-range drift. This entry is therefore the worked example of why the anchor exists — its pointer has now been repaired twice, once for a cross-package move that the existence check could see, and once for an in-file move that nothing in the gate could. Re-closed by hand against c459da6bc." } } }, @@ -211,7 +211,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-sharing/src/sharing-service.ts#effectiveSharingModel (`schema?.isSystem === true || String(schema?.name ?? '').startsWith('sys_')` on the no-declared-model branch)", - "note": "effectiveSharingModel: an object with no sharingModel and isSystem===true defaults to 'public' (else 'private') — fail-closed org-wide-default posture; ORed with the sys_ name-prefix fallback. Also read by lint/src/validate-security-posture.ts:98 (isSystemObject exempts system objects from the master-detail CRUD-grant lint) and mirrored in metadata-protocol/src/protocol.ts:106. The 2026-06 audit mis-classified as dead (sharing/lint readers not re-verified). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sharing-service.ts:75` had rotted onto `const OWNER_FIELD = 'owner_id'`, a neighbouring constant. Both this entry and `sharingModel` cite the same symbol because one function reads both keys, which the two rotted lines (:54 and :75) obscured by pointing at different unrelated places. Re-closed by hand against c459da6bc." + "note": "effectiveSharingModel: an object with no sharingModel and isSystem===true defaults to 'public' (else 'private') — fail-closed org-wide-default posture; ORed with the sys_ name-prefix fallback. Also read by lint/src/validate-security-posture.ts:98 (isSystemObject exempts system objects from the master-detail CRUD-grant lint) and mirrored in metadata-protocol/src/protocol.ts:106. The 2026-06 audit mis-classified as dead (sharing/lint readers not re-verified). 2026-08-28: RE-ANCHORED (commit 93ea19bca) and REPOINTED — `sharing-service.ts:75` had rotted onto `const OWNER_FIELD = 'owner_id'`, a neighbouring constant. Both this entry and `sharingModel` cite the same symbol because one function reads both keys, which the two rotted lines (:54 and :75) obscured by pointing at different unrelated places. Re-closed by hand against c459da6bc." }, "searchableFields": { "status": "live", diff --git a/packages/spec/liveness/query.json b/packages/spec/liveness/query.json index c7e0735da6d..4141135a06e 100644 --- a/packages/spec/liveness/query.json +++ b/packages/spec/liveness/query.json @@ -1,60 +1,60 @@ { "type": "query", - "_note": "QuerySchema (BaseQuerySchema + expand) — packages/spec/src/data/query.zod.ts. NOT a registered metadata type: QueryAST is the REQUEST surface (the client SDK QueryBuilder's output; the POST /data/:object/query body), governed via the gate's SPEC_ONLY_SCHEMAS override like webhook (#4286). It is authorable by every API caller yet never stored as stack metadata, which is why no other gate reached it: this ledger read the metadata-type registry, check:authorable-surface walks the same registry, and check:exported-any is a type-resolution gate. Seeded 2026-07-31 from the #4286 sweep; closed out same day — joins/windowFunctions/cursor/distinct REMOVED (tombstoned; protocol-17 semantic migrations; the cursor/distinct SDK producers deleted with their keys), having ENFORCED engine-side, search sub-flags + aggregations.filter marked experimental at the schema. Re-opened 2026-08-09 for ONE key the #4286 sweep did not reach: `aggregations[].distinct` (#6815) — dispositioned REMOVED. #4286 swept the query's own members plus `aggregations.filter`; the per-aggregation `distinct` was neither a top-level key nor an unread one, so it passed both filters of that sweep while diverging by BACKEND rather than by declaration. The main executor evidence below: objectql engine find/aggregate and the drivers' find paths. 2026-08-28 (#13003): every citation in this file was re-anchored, and this file carried a second defect class besides drift — most of its `live` entries cited a path with NO line at all (unfalsifiable by the line bound by construction), and `expand`'s pointer was written `engine.ts:2519+`, whose trailing `+` stops the token matching PATH_RE entirely, so it degraded to PROSE and was never resolved, bounded or key-checked by anything. Both are now repo-rooted anchors.", + "_note": "QuerySchema (BaseQuerySchema + expand) — packages/spec/src/data/query.zod.ts. NOT a registered metadata type: QueryAST is the REQUEST surface (the client SDK QueryBuilder's output; the POST /data/:object/query body), governed via the gate's SPEC_ONLY_SCHEMAS override like webhook (#4286). It is authorable by every API caller yet never stored as stack metadata, which is why no other gate reached it: this ledger read the metadata-type registry, check:authorable-surface walks the same registry, and check:exported-any is a type-resolution gate. Seeded 2026-07-31 from the #4286 sweep; closed out same day — joins/windowFunctions/cursor/distinct REMOVED (tombstoned; protocol-17 semantic migrations; the cursor/distinct SDK producers deleted with their keys), having ENFORCED engine-side, search sub-flags + aggregations.filter marked experimental at the schema. Re-opened 2026-08-09 for ONE key the #4286 sweep did not reach: `aggregations[].distinct` (#6815) — dispositioned REMOVED. #4286 swept the query's own members plus `aggregations.filter`; the per-aggregation `distinct` was neither a top-level key nor an unread one, so it passed both filters of that sweep while diverging by BACKEND rather than by declaration. The main executor evidence below: objectql engine find/aggregate and the drivers' find paths. 2026-08-28 (commit 8cb96ec41): every citation in this file was re-anchored, and this file carried a second defect class besides drift — most of its `live` entries cited a path with NO line at all (unfalsifiable by the line bound by construction), and `expand`'s pointer was written `engine.ts:2519+`, whose trailing `+` stops the token matching PATH_RE entirely, so it degraded to PROSE and was never resolved, bounded or key-checked by anything. Both are now repo-rooted anchors.", "props": { "object": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/engine.ts#fillQueryAstDefaults (`const ast: QueryAST = { ...this.fillQueryAstDefaults(findQuery), object }` — the resolved name is spread LAST so a stray body `object` cannot split `ast.object` from the table actually read); packages/metadata-protocol/src/protocol.ts#QUERY_AST_KEYS (reserves it at the REST boundary so a POST body's `object` is never read as a field filter, and a body/route mismatch is refused rather than resolved by picking a winner)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `protocol.ts:780` had rotted onto `if (seen.has(key)) return;` in an unrelated walker; `QUERY_AST_KEYS` is at ~:2426 today, ~1,650 lines away, in a 20,255-line file. The engine leg was PATH-ONLY (`engine.ts` with no line), which the line bound cannot falsify at all — it is now the narrowest symbol that assembles the AST. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `protocol.ts:780` had rotted onto `if (seen.has(key)) return;` in an unrelated walker; `QUERY_AST_KEYS` is at ~:2426 today, ~1,650 lines away, in a 20,255-line file. The engine leg was PATH-ONLY (`engine.ts` with no line), which the line bound cannot falsify at all — it is now the narrowest symbol that assembles the AST. Re-closed by hand against 93ea19bca." }, "fields": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#findRows (`builder.select(query.fields.map(...))`, else `select('*')`, with the unknown-column ladder that retries all-columns rather than answering an empty page); packages/objectql/src/engine.ts#planFormulaProjection (rewrites `ast.fields` to drop virtual formula fields before the driver sees them, and adds back the stored fields they read)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sql-driver.ts:1353-1354` had rotted onto `) as Error & { code?: string; status?: number };` inside an error-constructor helper, ~4,200 lines above the projection. sql-driver.ts is 16,289 lines; the whole find path moved into `findRows` under a citation nothing re-read. The `driver-memory projectFields` / `objectql formula projection` halves were prose with no path at all and are now a repo-rooted anchor on the formula side. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `sql-driver.ts:1353-1354` had rotted onto `) as Error & { code?: string; status?: number };` inside an error-constructor helper, ~4,200 lines above the projection. sql-driver.ts is 16,289 lines; the whole find path moved into `findRows` under a citation nothing re-read. The `driver-memory projectFields` / `objectql formula projection` halves were prose with no path at all and are now a repo-rooted anchor on the formula side. Re-closed by hand against 93ea19bca." }, "where": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#findRows (`if (query.where) this.applyFilters(b, query.where)` — re-applied on every rung of the recovery ladder, which is why the ladder can drop a projection or a sort but never the predicate); packages/drivers/driver-sql/src/sql-driver.ts#applyFilters (the compiler); packages/plugins/plugin-security/src/predicate-guard.ts#collectQueryFields (`collectConditionFields(ast.where, out)` — the FLS guard walks it so a predicate cannot read a field the caller may not)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sql-driver.ts:1330-1331` had rotted onto a docblock about `UNSUPPORTED_QUERY_PARAM` being a 400 on the client-mistake list. The parenthetical named `applyFilters`, which is real and ~11,250 lines from the cited position — the largest name-vs-position gap measured in this batch. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `sql-driver.ts:1330-1331` had rotted onto a docblock about `UNSUPPORTED_QUERY_PARAM` being a 400 on the client-mistake list. The parenthetical named `applyFilters`, which is real and ~11,250 lines from the cited position — the largest name-vs-position gap measured in this batch. Re-closed by hand against 93ea19bca." }, "search": { "status": "live", "verifiedAt": "2026-08-28", - "note": "Union since #3899's search-contract repair: the canonical bare query STRING (ADR-0061 D1 — what every surface sends and the dogfood HTTP proof pins) | the structured FullTextSearch form. A LEAF here, no longer a container: the walker does not descend into union members, so the object form's six engine-affordance flags (fuzzy/operator/boost/minScore/language/highlight) are audited where they are declared — FullTextSearchSchema's own `[EXPERIMENTAL — not enforced]` describe markers (#4286 step 1). The previously-listed children `query`/`fields` remain live at the same executor site. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:2885-2911` had rotted onto the comment `// this row sees. Never a copy.`, ~5,650 lines above `expandSearchOnAst`. engine.ts is 13,593 lines and the cited range sat in the low 2,000s; a range citation into a file that size ages out of usefulness long before it ages out of RANGE, which is why nothing flagged it. Re-closed by hand against 93ea19bca.", + "note": "Union since #3899's search-contract repair: the canonical bare query STRING (ADR-0061 D1 — what every surface sends and the dogfood HTTP proof pins) | the structured FullTextSearch form. A LEAF here, no longer a container: the walker does not descend into union members, so the object form's six engine-affordance flags (fuzzy/operator/boost/minScore/language/highlight) are audited where they are declared — FullTextSearchSchema's own `[EXPERIMENTAL — not enforced]` describe markers (#4286 step 1). The previously-listed children `query`/`fields` remain live at the same executor site. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `engine.ts:2885-2911` had rotted onto the comment `// this row sees. Never a copy.`, ~5,650 lines above `expandSearchOnAst`. engine.ts is 13,593 lines and the cited range sat in the low 2,000s; a range citation into a file that size ages out of usefulness long before it ages out of RANGE, which is why nothing flagged it. Re-closed by hand against 93ea19bca.", "evidence": "packages/objectql/src/engine.ts#expandSearchOnAst (ADR-0061: the string or object form is expanded on the AST before the driver sees it); packages/objectql/src/search-filter.ts#expandSearchToFilter (builds the server-resolved cross-field `$or`; the object form's `fields` scopes the expansion); proof showcase-search.dogfood.test.ts" }, "searchFields": { "status": "live", "verifiedAt": "2026-08-28", - "note": "Formally declared by #3899 (ADR-0061 P1, 'formalize $searchFields'): the per-query narrowing override the executor has served since the ADR landed — server-intersected with the object's allowed searchable set, can only narrow, never widen. The schema-side declaration caught up with the executor when #3899's entry validation started rejecting bodies the schema did not describe. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `engine.ts:2893-2898` had rotted onto `const row = rows[index];`, and the `search-filter.ts` leg was PATH-ONLY. Both halves of a two-hop claim (`requestedFields hand-off` → `resolveSearchFields`) were unfalsifiable in different ways: one bounded to a line that had stopped meaning anything, the other bounded to nothing. Re-closed by hand against 93ea19bca.", + "note": "Formally declared by #3899 (ADR-0061 P1, 'formalize $searchFields'): the per-query narrowing override the executor has served since the ADR landed — server-intersected with the object's allowed searchable set, can only narrow, never widen. The schema-side declaration caught up with the executor when #3899's entry validation started rejecting bodies the schema did not describe. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `engine.ts:2893-2898` had rotted onto `const row = rows[index];`, and the `search-filter.ts` leg was PATH-ONLY. Both halves of a two-hop claim (`requestedFields hand-off` → `resolveSearchFields`) were unfalsifiable in different ways: one bounded to a line that had stopped meaning anything, the other bounded to nothing. Re-closed by hand against 93ea19bca.", "evidence": "packages/objectql/src/engine.ts#expandSearchOnAst (the hand-off — the requested fields ride into the expansion); packages/objectql/src/search-filter.ts#resolveSearchFields (the intersection with the object's allowed searchable set: narrows only, never widens); proof showcase-search.dogfood.test.ts + search-conformance.ledger.ts row search-fields-override" }, "orderBy": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#orderKeysFor (the whole ORDER BY decided once — the caller's keys plus what a deterministic paged read requires, #4363); packages/drivers/driver-sql/src/sql-driver.ts#findRows (`b.orderBy(this.remoteColumn(...), key.direction)`, droppable by the unknown-column ladder so an unsortable column returns rows unordered rather than none); packages/plugins/plugin-security/src/predicate-guard.ts#collectQueryFields (the FLS guard walks each sort key's field)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sql-driver.ts:1335-1342` had rotted onto a docblock sentence about a throw site's job, and `every driver's find() sorts by it` was prose naming no path. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `sql-driver.ts:1335-1342` had rotted onto a docblock sentence about a throw site's job, and `every driver's find() sorts by it` was prose naming no path. Re-closed by hand against 93ea19bca." }, "limit": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#findRows (`if (query.limit !== undefined) b.limit(query.limit)` — inside `buildBase`, so every rung of the recovery ladder re-applies it)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sql-driver.ts:1345` had rotted onto a bare `*/` closing a docblock. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `sql-driver.ts:1345` had rotted onto a bare `*/` closing a docblock. Re-closed by hand against 93ea19bca." }, "offset": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#findRows (`if (query.offset !== undefined) b.offset(query.offset)` — applied beside `limit` in the same pagination block)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `sql-driver.ts:1344` had rotted onto the docblock line `* log.`, one line above `limit`'s rotted `*/`. The pair moved together and landed together, which is what makes a whole-file drift read as coherent right up to the moment someone opens the file. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `sql-driver.ts:1344` had rotted onto the docblock line `* log.`, one line above `limit`'s rotted `*/`. The pair moved together and landed together, which is what makes a whole-file drift read as coherent right up to the moment someone opens the file. Re-closed by hand against 93ea19bca." }, "top": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/engine.ts#ENGINE_QUERY_SLOTS (the engine's slot set — the `where` and `limit` canonicals, of which `top` is the OData alias); packages/objectql/src/engine.ts#foldEngineOptionAliases (applied on `find`/`findOne` before anything reads the AST, so the driver only ever sees `limit`); packages/metadata-protocol/src/protocol.ts#WIRE_DOLLAR_ALIASES (`['$top', 'top']` — the wire spelling folded on the REST list path)", - "note": "2026-08-28: RE-ANCHORED (#13003), REPOINTED, and the MECHANISM had moved too — this is the one entry in the batch where re-closing the call graph changed the story rather than just the coordinates. `engine.ts:2850-2853` claimed an inline `top → limit` normalization; both citations had rotted onto docblocks (`:2853` in engine.ts, `:3486` in protocol.ts), and the inline fold no longer exists: the alias now lives in the spec's ONE alias table (`RPC_QUERY_ALIAS_SLOTS`), filtered into `ENGINE_QUERY_SLOTS` and applied by `foldEngineOptionAliases` at every engine entry point (#4346/#4371). The verdict `live` was never in doubt; the pointer had stopped describing how. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41), REPOINTED, and the MECHANISM had moved too — this is the one entry in the batch where re-closing the call graph changed the story rather than just the coordinates. `engine.ts:2850-2853` claimed an inline `top → limit` normalization; both citations had rotted onto docblocks (`:2853` in engine.ts, `:3486` in protocol.ts), and the inline fold no longer exists: the alias now lives in the spec's ONE alias table (`RPC_QUERY_ALIAS_SLOTS`), filtered into `ENGINE_QUERY_SLOTS` and applied by `foldEngineOptionAliases` at every engine entry point (#4346/#4371). The verdict `live` was never in doubt; the pointer had stopped describing how. Re-closed by hand against 93ea19bca." }, "cursor": { "status": "dead", @@ -73,25 +73,25 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/in-memory-aggregation.ts#aggregateBucket (`const aggFilter = agg.filter` — a non-vacuous predicate narrows the rows THIS aggregation reads while its siblings keep the full bucket); packages/objectql/src/having-filter.ts#matchesAggregationFilter (the per-row evaluation); packages/objectql/src/engine.ts#aggregate (forces the in-memory lowering when any aggregation carries a non-empty filter); packages/drivers/driver-sql/src/sql-driver.ts#unsupportedAggregationFilterError, packages/drivers/driver-turso/src/remote-transport.ts#refusePerAggregationFilter, packages/drivers/driver-mongodb/src/mongodb-aggregation.ts#refusePerAggregationFilter and packages/drivers/driver-memory/src/memory-driver.ts#performAggregation (the four native faces REFUSE the key NOT_IMPLEMENTED/501 when reached directly, rather than silently aggregating the unfiltered rows); packages/plugins/plugin-security/src/predicate-guard.ts#collectQueryFields (the FLS guard walks the predicate's fields); pinned by packages/objectql/src/engine-aggregate-filter.test.ts", - "note": "ENFORCED 2026-08-21 (#10576, the contract half of #10413's ruling — maintainer 「其他接受」 accepting option A 「给引擎聚合契约加逐聚合过滤,一次修对所有驱动」). SQL FILTER (WHERE …) semantics: the predicate narrows the SOURCE rows the one aggregation reads while siblings keep the full group. Engine-owned via the in-memory lowering (the dateGranularity/having correct-first two-tier shape — a driver that grows native conditional aggregation must advertise a capability flag); a driver face reached directly refuses rather than silently aggregating the unfiltered rows, which was #10413's measured defect on the ObjectQL analytics path. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — nothing here had ROTTED (this entry was re-verified 2026-08-21, the youngest in the file), but nothing here was CHECKABLE either: the three repo-rooted legs were path-only, and the four driver faces plus the FLS guard were written as bare package+filename prose (`driver-sql sql-driver.ts`, `plugin-security predicate-guard.ts:89`), which PATH_RE cannot parse — so the ledger contributed no resolvable pointer for five of this entry's eight consumers and the gate was never asked about them. That is the webhook.json class, on the file's best-verified entry. All eight are now repo-rooted anchors. Re-closed by hand against 93ea19bca." + "note": "ENFORCED 2026-08-21 (#10576, the contract half of #10413's ruling — maintainer 「其他接受」 accepting option A 「给引擎聚合契约加逐聚合过滤,一次修对所有驱动」). SQL FILTER (WHERE …) semantics: the predicate narrows the SOURCE rows the one aggregation reads while siblings keep the full group. Engine-owned via the in-memory lowering (the dateGranularity/having correct-first two-tier shape — a driver that grows native conditional aggregation must advertise a capability flag); a driver face reached directly refuses rather than silently aggregating the unfiltered rows, which was #10413's measured defect on the ObjectQL analytics path. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — nothing here had ROTTED (this entry was re-verified 2026-08-21, the youngest in the file), but nothing here was CHECKABLE either: the three repo-rooted legs were path-only, and the four driver faces plus the FLS guard were written as bare package+filename prose (`driver-sql sql-driver.ts`, `plugin-security predicate-guard.ts:89`), which PATH_RE cannot parse — so the ledger contributed no resolvable pointer for five of this entry's eight consumers and the gate was never asked about them. That is the webhook.json class, on the file's best-verified entry. All eight are now repo-rooted anchors. Re-closed by hand against 93ea19bca." }, "function": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/in-memory-aggregation.ts#aggregateBucket (`const fn = agg.function` then the count / count_distinct / sum / avg / min / max arms, each with the platform's ruled empty-group value); packages/objectql/src/engine.ts#aggregate (forwards the aggregations to the driver or to the in-memory fallback)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `in-memory-aggregation.ts:167` had rotted onto `return v ?? null;` inside `projectGroupValue`, a DIFFERENT function three lines above `aggregateBucket`, and the engine leg was path-only. Three lines is the smallest miss in this batch and it is still a miss: the function it landed in projects a GROUP value, not an aggregate. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `in-memory-aggregation.ts:167` had rotted onto `return v ?? null;` inside `projectGroupValue`, a DIFFERENT function three lines above `aggregateBucket`, and the engine leg was path-only. Three lines is the smallest miss in this batch and it is still a miss: the function it landed in projects a GROUP value, not an aggregate. Re-closed by hand against 93ea19bca." }, "field": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/in-memory-aggregation.ts#aggregateBucket (`const field = agg.field` → `collectValues(rows, field)`, with the `'*'` count-all sentinel handled before it); packages/objectql/src/in-memory-aggregation.ts#collectValues (the per-row read); packages/plugins/plugin-security/src/predicate-guard.ts#collectQueryFields (the FLS guard adds each aggregation's field, skipping `'*'`)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `in-memory-aggregation.ts:204-206` had rotted onto `if (!field) {`, the fieldless guard rather than the read; `SQL builders render FUNC(field)` was prose naming no path. The near-miss here is instructive in the other direction from `function`'s: the cited line genuinely mentions `field`, in the right function, and still describes the branch taken when the key is ABSENT. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `in-memory-aggregation.ts:204-206` had rotted onto `if (!field) {`, the fieldless guard rather than the read; `SQL builders render FUNC(field)` was prose naming no path. The near-miss here is instructive in the other direction from `function`'s: the cited line genuinely mentions `field`, in the right function, and still describes the branch taken when the key is ABSENT. Re-closed by hand against 93ea19bca." }, "alias": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/in-memory-aggregation.ts#aggregateBucket (`const alias = agg.alias` — every arm writes its result to `out[alias]`, so the alias IS the emitted column name); packages/objectql/src/in-memory-aggregation.ts#applyInMemoryAggregation (merges those aliased values onto each bucket's group key)", - "note": "2026-08-28: RE-ANCHORED (#13003) — the citation was PATH-ONLY (`result column naming in both the SQL builders and packages/…/in-memory-aggregation.ts`), so no line bound could ever apply to it and the only check it faced was that the file exists. Now anchored to the two symbols that name the column. The `SQL builders` half stays prose deliberately: it names no path today and inventing one would be a guess. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) — the citation was PATH-ONLY (`result column naming in both the SQL builders and packages/…/in-memory-aggregation.ts`), so no line bound could ever apply to it and the only check it faced was that the file exists. Now anchored to the two symbols that name the column. The `SQL builders` half stays prose deliberately: it names no path today and inventing one would be a guess. Re-closed by hand against 93ea19bca." }, "distinct": { "status": "dead", @@ -104,13 +104,13 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/in-memory-aggregation.ts#applyInMemoryAggregation (`const groupBy = (ast.groupBy ?? [])` then the bucketing loop — one bucket per type-preserving key tuple); packages/objectql/src/in-memory-aggregation.ts#projectGroupValue (each group key's value, including the dateGranularity lowering); packages/objectql/src/engine.ts#aggregate (forwards it to the driver or the fallback); packages/plugins/plugin-security/src/predicate-guard.ts#collectQueryFields (the FLS guard walks the string and object group forms alike)", - "note": "2026-08-28: RE-ANCHORED (#13003) — both citations were PATH-ONLY, so this entry has never faced any check but file existence. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) — both citations were PATH-ONLY, so this entry has never faced any check but file existence. Re-closed by hand against 93ea19bca." }, "having": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/having-filter.ts#applyHaving (the evaluator entry point) and packages/objectql/src/having-filter.ts#matchesHaving (the per-row predicate over the AGGREGATED row's namespace — aliases plus groupBy projections; an unknown operator throws rather than being ignored); packages/objectql/src/engine.ts#aggregate (applies it after BOTH the native-driver path and the in-memory fallback); packages/metadata-protocol/src/protocol.ts#findData (the aggregate branch forwards it, and strips it off the find-path bag because that path cannot serve it); packages/spec/src/data/data-engine.zod.ts#EngineAggregateOptionsSchema (the declaration leg — this one is the option's DECLARATION, not a consumer, and is cited as such); packages/plugins/plugin-security/src/predicate-guard.ts#collectQueryFields (`collectConditionFields(ast.having, out)` — the FLS guard walks it beside `where`)", - "note": "ENFORCED 2026-07-31 (#4286 step 3, resolved to enforce): engine-owned, applied AFTER aggregation over the aggregated row's namespace (aggregation aliases + groupBy projections); an unknown operator rejects loudly instead of being ignored. Was #4286 finding 1 — aggregate() rebuilt the driver AST without it, so even a HAVING-implementing driver could never receive it. Native SQL pushdown can come later behind a driver capability flag (the dateGranularity two-tier pattern) without changing semantics. The FLS predicate guard walks it. 2026-08-28: RE-ANCHORED (#13003) — all four citations were PATH-ONLY. This is the entry where that mattered most: `having`'s whole history is a key that was declared, forwarded and then dropped one layer before it could act, and four unbounded file pointers are exactly the evidence shape that could not have caught it. Re-closed by hand against 93ea19bca." + "note": "ENFORCED 2026-07-31 (#4286 step 3, resolved to enforce): engine-owned, applied AFTER aggregation over the aggregated row's namespace (aggregation aliases + groupBy projections); an unknown operator rejects loudly instead of being ignored. Was #4286 finding 1 — aggregate() rebuilt the driver AST without it, so even a HAVING-implementing driver could never receive it. Native SQL pushdown can come later behind a driver capability flag (the dateGranularity two-tier pattern) without changing semantics. The FLS predicate guard walks it. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) — all four citations were PATH-ONLY. This is the entry where that mattered most: `having`'s whole history is a key that was declared, forwarded and then dropped one layer before it could act, and four unbounded file pointers are exactly the evidence shape that could not have caught it. Re-closed by hand against 93ea19bca." }, "windowFunctions": { "status": "dead", @@ -126,7 +126,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/engine.ts#expandRelatedRecords (the post-process: batch `$in` resolution of lookup / master_detail expands, default max depth 3 — no driver reads `ast.expand`, the engine expands after the fetch)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED, and this pointer was WORSE than rotted — it was written `packages/objectql/src/engine.ts:2519+`, and the trailing `+` stops the token matching PATH_RE, so the scanner classified the whole thing as PROSE. The path was never resolved, the line never bounded, the key-mention never asked: `expand` contributed ZERO checkable pointers while looking like the most precise citation in the file. That is the webhook.json defect (batch 2) reproduced by a single character rather than by a missing prefix, and it argues the same way — a citation grammar that degrades silently to prose needs the anchor to be the load-bearing half. The consumer itself had also moved ~5,520 lines, to `expandRelatedRecords`. Re-closed by hand against 93ea19bca." + "note": "2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED, and this pointer was WORSE than rotted — it was written `packages/objectql/src/engine.ts:2519+`, and the trailing `+` stops the token matching PATH_RE, so the scanner classified the whole thing as PROSE. The path was never resolved, the line never bounded, the key-mention never asked: `expand` contributed ZERO checkable pointers while looking like the most precise citation in the file. That is the webhook.json defect (batch 2) reproduced by a single character rather than by a missing prefix, and it argues the same way — a citation grammar that degrades silently to prose needs the anchor to be the load-bearing half. The consumer itself had also moved ~5,520 lines, to `expandRelatedRecords`. Re-closed by hand against 93ea19bca." } } } diff --git a/packages/spec/liveness/webhook.json b/packages/spec/liveness/webhook.json index 974f4699c3f..e867d8cf543 100644 --- a/packages/spec/liveness/webhook.json +++ b/packages/spec/liveness/webhook.json @@ -1,72 +1,72 @@ { "type": "webhook", - "_note": "WebhookSchema (outbound webhook — packages/spec/src/automation/webhook.zod.ts). Governed via a spec-only schema override in the gate (SPEC_ONLY_SCHEMAS): webhook is still NOT a registered metadata type (absent from kernel/metadata-type-schemas.ts) — registering it would turn on Studio webhook CRUD + saveMetaItem overlay + create-seeds; that reassessment is tracked in #3490 and deliberately deferred. TWO THINGS CLOSED THE OLD 'entire surface is dead' classification: (1) #3494 PRUNED the aspirational dead props outright — body / payloadFields / includeSession / retryPolicy / tags / authentication are gone from the schema; (2) the #3461 materializer bridge (PR #3489) makes every REMAINING prop live. `bootstrapDeclaredWebhooks` (plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts) materializes each stack/connector-authored webhook into a `sys_webhook` DATA row on boot — WebhookSchema.parse (:114) → mapWebhookToRow (:191): `object`→`object_name` (:195), `isActive`→`active` (:202), same-named `name`/`label`/`triggers`/`url`/`method`/`description`, `secret`→the encrypted `signing_secret` column (#7799), `headers`→the encrypted `headers_secret` column (#7986), and the REST of the envelope → `definition_json`. The dispatcher (AutoEnqueuer) reads those rows (auto-enqueuer.ts:175 `where:{active:true}`) and fans out on data.record.* events (plus the aggregate data.records.* a predicate write publishes, dispatched under the opt-in bulk_update/bulk_delete triggers — #4639), reading `object_name`/`name`/`url`/`method`/`triggers` off the row, `timeoutMs` back out of `definition_json`, and BOTH credentials through `engine.resolveSecretField()` against their encrypted columns — the signing key from `signing_secret` (#7799) and the custom headers from `headers_secret` (#7986). So all 11 remaining props are LIVE; there is no dead/experimental prop left, so nothing carries an authorWarn (the old per-webhook `url` heads-up is gone — authoring is no longer a no-op). Seed-not-clobber: an admin-edited row (`customized`) is never re-seeded (bootstrap-declared-webhooks.ts:143). The `object` prop carries the ADR-0054 runtime proof for the whole materialization pipeline (bound high-risk class `webhook-materialization`). Field-level line refs: materializer bootstrap-declared-webhooks.ts, runtime object plugins/plugin-webhooks/src/sys-webhook.object.ts, dispatcher auto-enqueuer.ts. 2026-08-28 — WHOLE FILE RE-ANCHORED (#13003, adoption of the #12516 symbol-anchor grammar), and this file was the batch's worst starting position by a distance. Every citation in it was written as a BARE FILENAME with a line (`bootstrap-declared-webhooks.ts:193`, `auto-enqueuer.ts:266`, `sys-webhook.object.ts:160`), which `PATH_RE` cannot parse: a citation must be repo-rooted to be a citation at all. So all eleven `live` entries scanned as ZERO local paths and ZERO line citations — no existence check, no line bound, no key-mention anchoring, nothing. The gate was not passing this file; it was never asked about it, and the ledger's own counters could not say so. Combined with the second fact — no entry carried `verifiedAt`, which the #13003 ACCEPT ruling sorts as OLDEST — these rows were unfalsifiable and unclocked at once. Re-closed by hand against c459da6bc: every consumer read on the current tree, no line carried forward. What the re-closure found: the claims were all TRUE and the pointers were all merely unresolvable — the opposite of `object.json` in batch 1, where the pointers resolved and the claims were wrong. Both are the same defect at different ends. Two prose repairs fell out of it: the parenthetical line numbers are gone in favour of the symbols that own each read, and the dispatcher's credential legs are named as `attachSecret` / `attachHeaders`, which REFUSE a subscription whose credential cannot be recovered rather than delivering without it — a fail-closed behaviour the old prose did not record.", + "_note": "WebhookSchema (outbound webhook — packages/spec/src/automation/webhook.zod.ts). Governed via a spec-only schema override in the gate (SPEC_ONLY_SCHEMAS): webhook is still NOT a registered metadata type (absent from kernel/metadata-type-schemas.ts) — registering it would turn on Studio webhook CRUD + saveMetaItem overlay + create-seeds; that reassessment is tracked in #3490 and deliberately deferred. TWO THINGS CLOSED THE OLD 'entire surface is dead' classification: (1) #3494 PRUNED the aspirational dead props outright — body / payloadFields / includeSession / retryPolicy / tags / authentication are gone from the schema; (2) the #3461 materializer bridge (PR #3489) makes every REMAINING prop live. `bootstrapDeclaredWebhooks` (plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts) materializes each stack/connector-authored webhook into a `sys_webhook` DATA row on boot — WebhookSchema.parse (:114) → mapWebhookToRow (:191): `object`→`object_name` (:195), `isActive`→`active` (:202), same-named `name`/`label`/`triggers`/`url`/`method`/`description`, `secret`→the encrypted `signing_secret` column (#7799), `headers`→the encrypted `headers_secret` column (#7986), and the REST of the envelope → `definition_json`. The dispatcher (AutoEnqueuer) reads those rows (auto-enqueuer.ts:175 `where:{active:true}`) and fans out on data.record.* events (plus the aggregate data.records.* a predicate write publishes, dispatched under the opt-in bulk_update/bulk_delete triggers — #4639), reading `object_name`/`name`/`url`/`method`/`triggers` off the row, `timeoutMs` back out of `definition_json`, and BOTH credentials through `engine.resolveSecretField()` against their encrypted columns — the signing key from `signing_secret` (#7799) and the custom headers from `headers_secret` (#7986). So all 11 remaining props are LIVE; there is no dead/experimental prop left, so nothing carries an authorWarn (the old per-webhook `url` heads-up is gone — authoring is no longer a no-op). Seed-not-clobber: an admin-edited row (`customized`) is never re-seeded (bootstrap-declared-webhooks.ts:143). The `object` prop carries the ADR-0054 runtime proof for the whole materialization pipeline (bound high-risk class `webhook-materialization`). Field-level line refs: materializer bootstrap-declared-webhooks.ts, runtime object plugins/plugin-webhooks/src/sys-webhook.object.ts, dispatcher auto-enqueuer.ts. 2026-08-28 — WHOLE FILE RE-ANCHORED (commit 9ee2dcfbd, adoption of the #12516 symbol-anchor grammar), and this file was the batch's worst starting position by a distance. Every citation in it was written as a BARE FILENAME with a line (`bootstrap-declared-webhooks.ts:193`, `auto-enqueuer.ts:266`, `sys-webhook.object.ts:160`), which `PATH_RE` cannot parse: a citation must be repo-rooted to be a citation at all. So all eleven `live` entries scanned as ZERO local paths and ZERO line citations — no existence check, no line bound, no key-mention anchoring, nothing. The gate was not passing this file; it was never asked about it, and the ledger's own counters could not say so. Combined with the second fact — no entry carried `verifiedAt`, which the review that accepted the re-anchoring sorts as OLDEST — these rows were unfalsifiable and unclocked at once. Re-closed by hand against c459da6bc: every consumer read on the current tree, no line carried forward. What the re-closure found: the claims were all TRUE and the pointers were all merely unresolvable — the opposite of `object.json` in batch 1, where the pointers resolved and the claims were wrong. Both are the same defect at different ends. Two prose repairs fell out of it: the parenthetical line numbers are gone in favour of the symbols that own each read, and the dispatcher's credential legs are named as `attachSecret` / `attachHeaders`, which REFUSE a subscription whose credential cannot be recovered rather than delivering without it — a fail-closed behaviour the old prose did not record.", "props": { "name": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`name: wh.name` — materialized to sys_webhook.name, #3489); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (the same value is the idempotency key: `where: { name: wh.name }` decides seed vs patch vs skip); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`name: (row.name as string) ?? row.id` — the dispatcher's dedup/log key)", - "note": "Was dead pre-bridge (admin-CRUD-only). Now fed by authoring. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge (admin-CRUD-only). Now fed by authoring. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "label": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`label: wh.label ?? wh.name` — materialized to sys_webhook.label, #3489); packages/plugins/plugin-webhooks/src/sys-webhook.object.ts#SysWebhook (every one of the object's four listViews leads with the `label` column)", - "note": "Display-only — surfaced in Studio, never sent on the wire. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Display-only — surfaced in Studio, never sent on the wire. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "object": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`object_name: wh.object ?? null` — the spec→runtime remap this entry's proof exists for); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`objectName: row.object_name ? String(row.object_name) : undefined` — what the per-object subscription keys on)", "proof": "packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts#webhook-materialization", - "note": "ADR-0054 high-risk class (webhook-materialization): the spec `object`→runtime `object_name` remap is the representative check for the whole authoring→dispatcher pipeline that crosses manifest-decomposition → materializer → sys_webhook row → dispatcher subscription — the exact multi-layer seam that broke in integration (the materializer was first gated behind the realtime guard) despite green unit tests. The proof authors a stack `webhooks:` entry against the real in-process stack and asserts the sys_webhook row materialized with object_name set. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "ADR-0054 high-risk class (webhook-materialization): the spec `object`→runtime `object_name` remap is the representative check for the whole authoring→dispatcher pipeline that crosses manifest-decomposition → materializer → sys_webhook row → dispatcher subscription — the exact multi-layer seam that broke in integration (the materializer was first gated behind the realtime guard) despite green unit tests. The proof authors a stack `webhooks:` entry against the real in-process stack and asserts the sys_webhook row materialized with object_name set. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "triggers": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`triggers: wh.triggers ?? []`); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (accepts array / comma-string / JSON-string shapes, maps them onto the dispatchable set, warns by name on values the engine never emits (#3196), and refuses a subscription whose set is empty under rule `webhook/without-triggers`)", - "note": "Was dead pre-bridge. Salvaged 1:1. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge. Salvaged 1:1. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "url": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`url: wh.url`); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`if (!row?.id || !row?.url) return null` — a row without one is not a subscription at all — then `url: String(row.url)` for delivery)", - "note": "Was dead pre-bridge and carried the per-webhook authorWarn; the bridge (#3489) makes authoring live, so the warning is removed. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge and carried the per-webhook authorWarn; the bridge (#3489) makes authoring live, so the warning is removed. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "method": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`method: String(wh.method ?? 'POST').toLowerCase()` — stored lowercase to match the object's select option values); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`String(row.method ?? defn.method ?? 'POST').toUpperCase()` — upper-cased back for the wire, so both spellings survive the select change)", - "note": "Was dead pre-bridge. Salvaged 1:1. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge. Salvaged 1:1. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "headers": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (the envelope it returns is the authored one MINUS its two credential passengers — `splitWebhookHeaders` lifts this map out before `definition_json` is serialized, #7986); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (`headersPatch` / the `WEBHOOK_HEADERS_FIELD` insert write the map into the encrypted `headers_secret` column); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#attachHeaders (recovers and re-parses it per cache refresh, and REFUSES the subscription when recovery fails rather than delivering header-less)", - "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7986: the same column and the same absent `enable` block as #7799's signing secret, one key of the blob later. It no longer rides in that blob; the authored map itself is unchanged. The WHOLE map moves because only some entries are credentials and the platform cannot tell which. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7986: the same column and the same absent `enable` block as #7799's signing secret, one key of the blob later. It no longer rides in that blob; the authored map itself is unchanged. The WHOLE map moves because only some entries are credentials and the platform cannot tell which. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "timeoutMs": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`definition_json: JSON.stringify(envelope)` — this key rides the envelope rather than a column of its own); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#parseRow (`timeoutMs: defn.timeoutMs`, read back out of that JSON onto the cached subscription and passed to the outbox on both enqueue paths); packages/services/service-messaging/src/http-outbox.ts#assertEnqueueDeliverable (the outbox side of the same field)", - "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows). Now materialized from authoring. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows). Now materialized from authoring. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "secret": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`splitWebhookSecret` lifts the key out of the envelope before serialization, #7799); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (`secretPatch` / the `WEBHOOK_SECRET_FIELD` insert write cleartext exactly once into the `secret`-typed `signing_secret` column, which the engine wraps into sys_secret); packages/plugins/plugin-webhooks/src/auto-enqueuer.ts#attachSecret (recovers the plaintext per cache refresh and hands it to the outbox as `signingSecret`, consumed for one HMAC and never persisted, #7722)", - "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7799. It no longer rides in that blob; the authored key itself is unchanged. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge (definition_json only reachable via hand-authored rows), then materialized from authoring into definition_json as CLEARTEXT — recoverable over the ordinary data API, which is #7799. It no longer rides in that blob; the authored key itself is unchanged. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "isActive": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`active: wh.isActive !== false` — the NAME MISMATCH remap, spec `isActive` → runtime `active`); packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#bootstrapDeclaredWebhooks (seed-not-clobber: an admin-authored (`managed_by === 'admin'`) or admin-edited (`customized === true`) row is skipped, so an operator's `active: false` survives redeploys)", - "note": "Was dead pre-bridge (NAME MISMATCH spec `isActive` vs runtime `active`, now remapped). Seed-not-clobber preserves an admin's `active:false` across redeploys. The dispatcher gates delivery on the REMAPPED column — `AutoEnqueuer.doRefresh` loads `where: { active: true }`, so only armed rows are ever cached — and that call site is kept here in PROSE rather than as a citation, on the `object.tenancy.organizationField` precedent: a consumer that never names the key belongs in the note, not in `evidence`. 2026-08-28: RE-ANCHORED (#13003) — and this row is the one that PAID for the migration inside the same run. Rewriting this file's bare-filename citations as repo-rooted anchors made them askable for the first time, and the key-mention check immediately answered: `webhook/isActive -> auto-enqueuer.ts` UNANCHORED, because that file names `active` and never `isActive` in any case fold. The check was right — the remap happens at the materializer and nothing downstream of it sees the authored spelling — so the citation moved to prose rather than being exempted. Recording the direction because it is the useful one: an unparseable citation does not fail, it is simply never asked, and eleven of those had been sitting here reading as evidence. Re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Was dead pre-bridge (NAME MISMATCH spec `isActive` vs runtime `active`, now remapped). Seed-not-clobber preserves an admin's `active:false` across redeploys. The dispatcher gates delivery on the REMAPPED column — `AutoEnqueuer.doRefresh` loads `where: { active: true }`, so only armed rows are ever cached — and that call site is kept here in PROSE rather than as a citation, on the `object.tenancy.organizationField` precedent: a consumer that never names the key belongs in the note, not in `evidence`. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — and this row is the one that PAID for the migration inside the same run. Rewriting this file's bare-filename citations as repo-rooted anchors made them askable for the first time, and the key-mention check immediately answered: `webhook/isActive -> auto-enqueuer.ts` UNANCHORED, because that file names `active` and never `isActive` in any case fold. The check was right — the remap happens at the materializer and nothing downstream of it sees the authored spelling — so the citation moved to prose rather than being exempted. Recording the direction because it is the useful one: an unparseable citation does not fail, it is simply never asked, and eleven of those had been sitting here reading as evidence. Re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" }, "description": { "status": "live", "evidence": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts#mapWebhookToRow (`description: wh.description ?? null` — materialized to sys_webhook.description, #3489); packages/plugins/plugin-webhooks/src/sys-webhook.object.ts#SysWebhook (the column the Studio editor renders and edits)", - "note": "Display-only — surfaced/editable in Studio, inert in dispatch. 2026-08-28: RE-ANCHORED (#13003) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", + "note": "Display-only — surfaced/editable in Studio, inert in dispatch. 2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) — see the file `_note` for what the re-closure found across this ledger; re-closed by hand against c459da6bc, DATED for the first time.", "verifiedAt": "2026-08-28" } }