From 3fc249165e73340972dc9563131fd33c2098bf49 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 07:35:31 +0000 Subject: [PATCH] docs(spec): twenty-one more liveness ledgers cite the deciding commit, or state the decision in words, in place of dead tracker numbers 121 sites across 22 liveness ledgers cited tracker numbers that answer 404. 120 sit in note/_note strings: each changed note now names the commit that decided it, or says the decision in words where the number alone carried the meaning. The 121st is the prose parenthesis of the datasource ssl.rejectUnauthorized evidence string, which now names the commit that added mysqlSslOption; no path#symbol token moves. Status, proof, verifiedAt, producer and evidenceScope are unchanged in every row, no evidence leaf other than that one differs, and no number is added. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude --- ...34-liveness-ledger-provenance-anchors-3.md | 19 ++++++++++++++++++ packages/spec/liveness/action.json | 10 +++++----- packages/spec/liveness/agent.json | 2 +- packages/spec/liveness/analytics_cube.json | 2 +- packages/spec/liveness/batch_endpoints.json | 6 +++--- packages/spec/liveness/book.json | 20 +++++++++---------- packages/spec/liveness/capability.json | 12 +++++------ packages/spec/liveness/crud_endpoints.json | 6 +++--- packages/spec/liveness/dashboard.json | 8 ++++---- packages/spec/liveness/datasource.json | 2 +- packages/spec/liveness/doc.json | 18 ++++++++--------- packages/spec/liveness/flow.json | 10 +++++----- packages/spec/liveness/hook.json | 18 ++++++++--------- packages/spec/liveness/job.json | 14 ++++++------- .../spec/liveness/metadata_endpoints.json | 6 +++--- packages/spec/liveness/qa.json | 10 +++++----- packages/spec/liveness/rest_api.json | 8 ++++---- packages/spec/liveness/route_generation.json | 10 +++++----- packages/spec/liveness/seed.json | 12 +++++------ packages/spec/liveness/skill.json | 2 +- packages/spec/liveness/tool.json | 6 +++--- packages/spec/liveness/translation.json | 18 ++++++++--------- packages/spec/liveness/validation.json | 18 ++++++++--------- 23 files changed, 128 insertions(+), 109 deletions(-) create mode 100644 .changeset/20234-liveness-ledger-provenance-anchors-3.md diff --git a/.changeset/20234-liveness-ledger-provenance-anchors-3.md b/.changeset/20234-liveness-ledger-provenance-anchors-3.md new file mode 100644 index 00000000000..0f44127c7bc --- /dev/null +++ b/.changeset/20234-liveness-ledger-provenance-anchors-3.md @@ -0,0 +1,19 @@ +--- +'@objectstack/spec': patch +--- + +Notes in twenty-one more liveness ledgers, and one `datasource` evidence string, 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 `book`, `doc`, `job`, `validation`, `translation`, `hook`, `seed`, `flow`, +`capability`, `qa`, `dashboard`, `action`, `agent`, `skill`, `tool`, `rest_api`, +`route_generation`, `crud_endpoints`, `metadata_endpoints`, `batch_endpoints` and +`analytics_cube` ledgers cited tracker numbers that no longer resolve on GitHub, 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 +`datasource` ledger's `ssl.rejectUnauthorized` evidence string cited one such number in its +prose; it now names the commit that made the fix, and its code anchors are unchanged. The +`liveness/` ledgers ship in this package's tarball, which is why this is a release note at all. +Note text and that one evidence parenthesis only: no row's status, proof or date changes, and +no schema, export or runtime behaviour changes. diff --git a/packages/spec/liveness/action.json b/packages/spec/liveness/action.json index b1a5cf1200f..ede93bd351b 100644 --- a/packages/spec/liveness/action.json +++ b/packages/spec/liveness/action.json @@ -1,6 +1,6 @@ { "type": "action", - "_note": "ActionSchema. Seeded from docs/audits/2026-06-actionschema-property-liveness.md. Renderers live in objectui (evidence cited as prose, not framework paths); framework-side: service-ai action-tools, runtime body-runner/http-dispatcher. Containers (params/resultDialog/ai/aria) classified at top level — no divergent sub-statuses in the audit. ⚠ EVIDENCE LIVES IN CLOUD/EE: the `cloud @: packages/service-ai/...` paths cited below are the closed `@objectstack/service-ai` runtime in the CLOUD repo, NOT git-tracked framework code. 2026-08-30 (#13272): the previous wording of this sentence was false in BOTH halves. (a) It called the framework's own service-ai tree “a stale build artifact with no src/” — there is no tree at all: `packages/services/` holds every sibling service EXCEPT service-ai, and `git ls-files | grep -ic service-ai` returns 0. Absent, not stale. (b) The citations spelled `packages/services/service-ai/...`, a path present in NEITHER repo; cloud's real layout, measured at cloud@15f55df (#13042), is `packages/service-ai/...`. Every citation below now carries the explicit `cloud` realm marker, so it is attributed by the marker `scanEvidence` reads rather than riding the `FOREIGN_PATH_PREFIXES` special case that silently exempted the stale spelling from resolution — which is why 22 dead pointers sat green. ✅ RE-VERIFIED 2026-09-15 (#13272): the re-verification half this entry parked is discharged. All three cloud consumers cited below were re-read in a cloud checkout at cloud @cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8 and all three are CONFIRMED; every row now carries `verifiedAt`, `evidenceScope: cross-repo`, and `#symbol` anchors pinned to the consuming functions instead of a line number — the one cited line (`action-tools.ts:535`) had already drifted to 545 in a 961-line file that is actively edited. The anchors are the load-bearing half, and they are why the DATE matters: `scanEvidence` never collects an anchor while a foreign realm marker is in force, so CI cannot re-derive a single one of the cloud anchors — each rests on that dated reading alone. ⛔ Never re-stamp `verifiedAt` here without re-reading cloud; a bare re-stamp restores exactly the unfalsifiable pointer this entry's history is made of. These props are `live` because that cloud runtime consumes them; the OPEN framework edition does not — see content/docs/ai for the open/cloud boundary. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent.", + "_note": "ActionSchema. Seeded from docs/audits/2026-06-actionschema-property-liveness.md. Renderers live in objectui (evidence cited as prose, not framework paths); framework-side: service-ai action-tools, runtime body-runner/http-dispatcher. Containers (params/resultDialog/ai/aria) classified at top level — no divergent sub-statuses in the audit. ⚠ EVIDENCE LIVES IN CLOUD/EE: the `cloud @: packages/service-ai/...` paths cited below are the closed `@objectstack/service-ai` runtime in the CLOUD repo, NOT git-tracked framework code. 2026-08-30 (#13272): the previous wording of this sentence was false in BOTH halves. (a) It called the framework's own service-ai tree “a stale build artifact with no src/” — there is no tree at all: `packages/services/` holds every sibling service EXCEPT service-ai, and `git ls-files | grep -ic service-ai` returns 0. Absent, not stale. (b) The citations spelled `packages/services/service-ai/...`, a path present in NEITHER repo; cloud's real layout, measured at cloud@15f55df (commit c19035e97), is `packages/service-ai/...`. Every citation below now carries the explicit `cloud` realm marker, so it is attributed by the marker `scanEvidence` reads rather than riding the `FOREIGN_PATH_PREFIXES` special case that silently exempted the stale spelling from resolution — which is why 22 dead pointers sat green. ✅ RE-VERIFIED 2026-09-15 (#13272): the re-verification half this entry parked is discharged. All three cloud consumers cited below were re-read in a cloud checkout at cloud @cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8 and all three are CONFIRMED; every row now carries `verifiedAt`, `evidenceScope: cross-repo`, and `#symbol` anchors pinned to the consuming functions instead of a line number — the one cited line (`action-tools.ts:535`) had already drifted to 545 in a 961-line file that is actively edited. The anchors are the load-bearing half, and they are why the DATE matters: `scanEvidence` never collects an anchor while a foreign realm marker is in force, so CI cannot re-derive a single one of the cloud anchors — each rests on that dated reading alone. ⛔ Never re-stamp `verifiedAt` here without re-reading cloud; a bare re-stamp restores exactly the unfalsifiable pointer this entry's history is made of. These props are `live` because that cloud runtime consumes them; the OPEN framework edition does not — see content/docs/ai for the open/cloud boundary. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent.", "props": { "name": { "status": "live", @@ -41,14 +41,14 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/sandbox/body-runner.ts#actionBodyRunnerFactory (the #4352 gate — `const type = action.type ?? 'script'` decides whether a `body` binds an executable handler at all); packages/runtime/src/action-execution.ts#invokeBusinessAction (`action.type === 'flow'` routes to dispatchFlowAction); packages/runtime/src/action-execution.ts#isHeadlessInvokableAction (gates headless dispatch on the type); packages/runtime/src/action-execution.ts#headlessActionTypeError (names the type it refuses); packages/runtime/src/action-execution.ts#summarizeAction (MCP action summary projects it); packages/runtime/src/domains/actions.ts#handleActionsRequest (the REST route's own `actionType` resolution, read again at the flow branch); objectui @732b1bf core ActionRunner.ts:513-541 switches every variant to its own executor (executeScript/Url/Modal/Flow/API/Form/Navigation)", - "note": "api/script/flow wired; url thinner; modal PARTIAL (maps to serverActionHandler, not a real modal); form LIVE via objectui ActionRunner.executeForm (routes a type:'form' action to the FormView at /forms/:target, forwarding the current record id) — the 2026-06 audit mis-classified as dead (objectui renderer not re-verified; fixed the 'Log Time does nothing' report). Build-time lint-view-refs.ts validates the form target resolves to a form view. RE-VERIFIED 2026-07 (#3714 follow-up): `api` -> executeAPI (:974, string-or-ApiConfig endpoint, method/headers/queryParams/responseType) and `form` -> executeForm (:920) both resolve; content/docs/ui/actions.mdx had been telling authors the opposite (\"no runtime executor / renderer today\") and was corrected in the same pass. 2026-08-26: REPOINTED (framework half only) — the evidence led with packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. That file's 9 word-`type` occurrences are ALL something else: four are the TypeScript `import type` keyword (:9, :13, :14, :21) and the rest are other domains' prose or data (`error.type` :862, a field-type→JSON-Schema mapper :926, `details.type` :1156, a metadata type list :1634, an inbox query param :1765). The action consumer was extracted into domains/actions.ts + action-execution.ts (+ the sandbox body-runner, which is where the type gate has always lived) and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT — and this is the part that differs from the `target`/`requiredPermissions` siblings repointed the day before: those were caught because their cited file had 0 occurrences of the key, whereas `type` is a common English word AND a TypeScript keyword, so the word-bounded key-mention check anchors on the coincidence and PASSES. This entry was repaired by a hand call-graph read, not by tooling; the check's designed, honest limit is what left it standing. The objectui half is unchanged and stays as measured @732b1bf (2026-07-28) — it was not re-graded here. 2026-08-28: RE-ANCHORED (#13003, adoption of the #12516 grammar) — re-closed by hand against c459da6bc. All six framework consumers stand and none had left its file, so this is a grammar migration rather than a repair; what it DOES repair is a second, quieter gap: `:524`/`:552`/`:906` were written as bare line suffixes with no path in front of them, which the evidence scanner cannot parse as citations at all (`PATH_RE` needs a repo-rooted token), so three of this entry's six consumers were unfalsifiable prose that no check has ever resolved. They are now anchors of their own." + "note": "api/script/flow wired; url thinner; modal PARTIAL (maps to serverActionHandler, not a real modal); form LIVE via objectui ActionRunner.executeForm (routes a type:'form' action to the FormView at /forms/:target, forwarding the current record id) — the 2026-06 audit mis-classified as dead (objectui renderer not re-verified; fixed the 'Log Time does nothing' report). Build-time lint-view-refs.ts validates the form target resolves to a form view. RE-VERIFIED 2026-07 (#3714 follow-up): `api` -> executeAPI (:974, string-or-ApiConfig endpoint, method/headers/queryParams/responseType) and `form` -> executeForm (:920) both resolve; content/docs/ui/actions.mdx had been telling authors the opposite (\"no runtime executor / renderer today\") and was corrected in the same pass. 2026-08-26: REPOINTED (framework half only) — the evidence led with packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. That file's 9 word-`type` occurrences are ALL something else: four are the TypeScript `import type` keyword (:9, :13, :14, :21) and the rest are other domains' prose or data (`error.type` :862, a field-type→JSON-Schema mapper :926, `details.type` :1156, a metadata type list :1634, an inbox query param :1765). The action consumer was extracted into domains/actions.ts + action-execution.ts (+ the sandbox body-runner, which is where the type gate has always lived) and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT — and this is the part that differs from the `target`/`requiredPermissions` siblings repointed the day before: those were caught because their cited file had 0 occurrences of the key, whereas `type` is a common English word AND a TypeScript keyword, so the word-bounded key-mention check anchors on the coincidence and PASSES. This entry was repaired by a hand call-graph read, not by tooling; the check's designed, honest limit is what left it standing. The objectui half is unchanged and stays as measured @732b1bf (2026-07-28) — it was not re-graded here. 2026-08-28: RE-ANCHORED (commit 93ea19bca, adoption of the #12516 grammar) — re-closed by hand against c459da6bc. All six framework consumers stand and none had left its file, so this is a grammar migration rather than a repair; what it DOES repair is a second, quieter gap: `:524`/`:552`/`:906` were written as bare line suffixes with no path in front of them, which the evidence scanner cannot parse as citations at all (`PATH_RE` needs a repo-rooted token), so three of this entry's six consumers were unfalsifiable prose that no check has ever resolved. They are now anchors of their own." }, "operation": { "status": "live", "verifiedAt": "2026-09-08", "evidenceScope": "in-repo", "evidence": "packages/runtime/src/action-execution.ts#isDeclarativeUpdateAction (the executor's own discriminator — a bare `action?.operation === DECLARATIVE_UPDATE_OPERATION`, deliberately with no `type` clause so data at rest whose `type` contradicts its `operation` is still routed by `operation`); packages/runtime/src/domains/actions.ts#handleActionsRequest (the REST `/actions` door: the branch runs ahead of the `type` switch and ahead of the trusted-mode plumbing, and hands off to `executeDeclarativeUpdateAction`); packages/runtime/src/action-execution.ts#invokeBusinessAction (the MCP `run_action` door takes the same branch into the same executor — one implementation, two doors); packages/runtime/src/action-execution.ts#isHeadlessInvokableAction (asked FIRST, so a declarative update is listed as invokable although it carries neither `target` nor `body`); packages/runtime/src/action-execution.ts#headlessActionTypeError (asked FIRST, so the key suppresses the `type` prescription that would otherwise be wrong for it); packages/runtime/src/action-execution.ts#summarizeAction (the MCP listing face projects the declared `operation` and forces `requiresRecord`); packages/spec/src/ui/action.zod.ts#refuseDeclarativeUpdateContradictions (authoring-time: every executor-binding key beside `operation: 'update'` is refused at its own path, and `patch` without it); packages/spec/src/stack.zod.ts#collectGlobalUpdateActionErrors (defineStack refuses a standalone `operation: 'update'` action with no `objectName`)", - "note": "The discriminator of the declarative single-record field write — one member, `'update'`; `'delete'`/`'custom'` are refused with the reason. FLIPPED `planned` -> `live` 2026-09-08 (#15080) on the runtime half #15079 (PR #15448, merged 2026-09-04): the key now decides dispatch at both server doors, and it is read BEFORE `type` at every one of them — contract point 1 of the executor contract PR #15077 pinned here, and a rule about ORDER, not merely a new branch. Declared by the #14092 maintainer ruling (2026-09-01): the row-level counterpart of a list view's `bulkActionDefs` `operation: 'update'`, spelled with the same words. The EXECUTOR CONTRACT is now honoured in tree rather than promised: the write is a single data-plane update of the CURRENT record executed AS THE CALLER under `wiring.ec` and never `buildActionExecutionContext` (the #14010 `runAs: 'user'` direction), so object/row/field permissions, hooks and validations fire as for a user edit and a caller who cannot read the row is refused (the #14143 class); `patch` merges UNDER the collected `params`; `undoable` captures the prior values of exactly the fields written; an action with no current record is a located 400, never a silent no-op. Pinned end-to-end in packages/runtime/src/action-declarative-update.test.ts, one describe per contract point plus a reverse pin that a handler-less `type: 'script'` action WITHOUT `operation` is unchanged. `evidenceScope: in-repo` is exact and deliberate: this verdict rests on the framework-side readers alone. The console half (objectui#7551) is a SECOND reader and the flip did not wait for it — objectui's `ActionRunner` still dispatches on `type` — so a console-side forward/undo pointer is a later widening of this row's scope, not a precondition of its verdict." + "note": "The discriminator of the declarative single-record field write — one member, `'update'`; `'delete'`/`'custom'` are refused with the reason. FLIPPED `planned` -> `live` 2026-09-08 (#15080) on the runtime half #15079 (PR #15448, merged 2026-09-04): the key now decides dispatch at both server doors, and it is read BEFORE `type` at every one of them — contract point 1 of the executor contract PR #15077 pinned here, and a rule about ORDER, not merely a new branch. Declared by the #14092 maintainer ruling (2026-09-01): the row-level counterpart of a list view's `bulkActionDefs` `operation: 'update'`, spelled with the same words. The EXECUTOR CONTRACT is now honoured in tree rather than promised: the write is a single data-plane update of the CURRENT record executed AS THE CALLER under `wiring.ec` and never `buildActionExecutionContext` (the #14010 `runAs: 'user'` direction), so object/row/field permissions, hooks and validations fire as for a user edit and a caller who cannot read the row is refused (the class commit f19475c0a closed); `patch` merges UNDER the collected `params`; `undoable` captures the prior values of exactly the fields written; an action with no current record is a located 400, never a silent no-op. Pinned end-to-end in packages/runtime/src/action-declarative-update.test.ts, one describe per contract point plus a reverse pin that a handler-less `type: 'script'` action WITHOUT `operation` is unchanged. `evidenceScope: in-repo` is exact and deliberate: this verdict rests on the framework-side readers alone. The console half (objectui#7551) is a SECOND reader and the flip did not wait for it — objectui's `ActionRunner` still dispatches on `type` — so a console-side forward/undo pointer is a later widening of this row's scope, not a precondition of its verdict." }, "patch": { "status": "live", @@ -74,12 +74,12 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/sandbox/body-runner.ts#actionBodyRunnerFactory (`const raw = action.body`, in the factory whose own header calls it \"the ONE choke point where an `action.body` becomes an executable handler\" — both bind paths reach it: AppPlugin's collectBundleActions walk and engine.setDefaultActionRunner); packages/runtime/src/action-execution.ts#isHeadlessInvokableAction (gates headless dispatch on `action?.target || action?.body`)", - "note": "server script (L1/L2) via engine.executeAction→body-runner. 2026-08-26: REPOINTED — the evidence cited packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. Its 68 word-`body` occurrences are the inbound HTTP request body — the `body: any` parameter threaded through the handleX delegates (:890, :961, :1732, :1969 …) and the result envelope at :728 — plus the file's own \"Thin delegate — body extracted to ./domains/…\" extraction comments, where `body` means a function body. The consumer this note already NAMED in prose (body-runner) is where the read has always been; the surrounding action path was extracted into domains/actions.ts + action-execution.ts, and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT: `body` is both a common English word and the name of the HTTP request member this dispatcher is built around, so the word-bounded key-mention check added in #11457 anchors on 68 unrelated hits and passes — the designed, honest limit of that signal, which is why this needed a hand call-graph read. Note the shape the repoint exposes: the NOTE's pointer (\"via engine.executeAction→body-runner\") stayed true the whole time while the EVIDENCE pointer rotted — the same split recorded on `requiredPermissions`, one layer over. 2026-08-28: RE-ANCHORED (#13003) — re-closed by hand against c459da6bc; both consumers stand where the 08-26 repoint put them, so the lines had not yet rotted and this is the grammar migration, not a second repair. The anchor is what makes that claim re-testable next time: `actionBodyRunnerFactory` is the symbol the header already calls the ONE choke point, so a consumer that moves inside this 798-line file keeps the pointer true and one that is deleted turns it red." + "note": "server script (L1/L2) via engine.executeAction→body-runner. 2026-08-26: REPOINTED — the evidence cited packages/runtime/src/http-dispatcher.ts, which reads this key nowhere. Its 68 word-`body` occurrences are the inbound HTTP request body — the `body: any` parameter threaded through the handleX delegates (:890, :961, :1732, :1969 …) and the result envelope at :728 — plus the file's own \"Thin delegate — body extracted to ./domains/…\" extraction comments, where `body` means a function body. The consumer this note already NAMED in prose (body-runner) is where the read has always been; the surrounding action path was extracted into domains/actions.ts + action-execution.ts, and http-dispatcher.ts now only delegates (handleActions → handleActionsRequest at :1969-1970). WHY THE GATE COULD NOT SEE THE ROT: `body` is both a common English word and the name of the HTTP request member this dispatcher is built around, so the word-bounded key-mention check added in #11457 anchors on 68 unrelated hits and passes — the designed, honest limit of that signal, which is why this needed a hand call-graph read. Note the shape the repoint exposes: the NOTE's pointer (\"via engine.executeAction→body-runner\") stayed true the whole time while the EVIDENCE pointer rotted — the same split recorded on `requiredPermissions`, one layer over. 2026-08-28: RE-ANCHORED (commit 93ea19bca) — re-closed by hand against c459da6bc; both consumers stand where the 08-26 repoint put them, so the lines had not yet rotted and this is the grammar migration, not a second repair. The anchor is what makes that claim re-testable next time: `actionBodyRunnerFactory` is the symbol the header already calls the ONE choke point, so a consumer that moves inside this 798-line file keeps the pointer true and one that is deleted turns it red." }, "execute": { "status": "dead", "verifiedAt": "2026-08-29", - "note": "REMOVED 2026-07-28 in protocol 17 (#3855, PR #3883) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and renamed out of sources by the protocol-17 conversion `action-execute-to-target`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); use `target`, the only handler slot — rename the key, the value (a handler / flow / URL ref) is unchanged, and `os migrate meta --from 16` lists the mechanical edits. The tombstone is packages/spec/src/ui/action.zod.ts#execute, and packages/cli/src/utils/lower-callables.ts#lowerActionCallable deliberately declines to bind a function-valued `execute` so the tombstone fires instead of the alias silently working in one authoring style and being rejected in every other. LEDGER CORRECTED 2026-08-29 (#13036) — the VERDICT was falsified, not the citation. This row read `live` on the claim that a .transform lowers execute -> target and DROPS the alias; no such transform exists. action.zod.ts has exactly two .transform calls (:526 and :1668) and both are lowerRequiresFeature, and the docblock above `target` (:559-562) says the alias was removed in protocol 17. The old pointer packages/spec/src/ui/action.zod.ts:581 was IN RANGE in an 1802-line file that names the key, so existence, line bound and key-mention were all green on it — what :581 holds today is a comment about the `global_nav` enum-VALUE retirement's declaration style, unrelated in every respect — and the entry carried no verifiedAt, so the re-verification clock never asked (the #12516 class in its purest form). HISTORY PRESERVED, because it is the argument for `target` being the single handler slot: DIVERGENCE RESOLVED in #3713 — before that fix three readers disagreed in two directions, the parse kept `target`, objectui ActionRunner did `execute || target`, and the CLI compile step (packages/cli/src/utils/lower-callables.ts) preferred a function on `execute`; #3713 made all three prefer `target`, and protocol 17 then removed the alias outright, so the both-declared conflict is unrepresentable rather than merely agreed-upon (mirrors agent.knowledge.topics -> sources, #1891). The server runtime never read `execute` at all — packages/runtime/src/action-execution.ts:525 gates on `target || body` and dispatches on `target`/`name` — so with the parse-time lowering gone there is no reader on any layer." + "note": "REMOVED 2026-07-28 in protocol 17 (#3855, PR #3883) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and renamed out of sources by the protocol-17 conversion `action-execute-to-target`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); use `target`, the only handler slot — rename the key, the value (a handler / flow / URL ref) is unchanged, and `os migrate meta --from 16` lists the mechanical edits. The tombstone is packages/spec/src/ui/action.zod.ts#execute, and packages/cli/src/utils/lower-callables.ts#lowerActionCallable deliberately declines to bind a function-valued `execute` so the tombstone fires instead of the alias silently working in one authoring style and being rejected in every other. LEDGER CORRECTED 2026-08-29 (commit cae2169cf) — the VERDICT was falsified, not the citation. This row read `live` on the claim that a .transform lowers execute -> target and DROPS the alias; no such transform exists. action.zod.ts has exactly two .transform calls (:526 and :1668) and both are lowerRequiresFeature, and the docblock above `target` (:559-562) says the alias was removed in protocol 17. The old pointer packages/spec/src/ui/action.zod.ts:581 was IN RANGE in an 1802-line file that names the key, so existence, line bound and key-mention were all green on it — what :581 holds today is a comment about the `global_nav` enum-VALUE retirement's declaration style, unrelated in every respect — and the entry carried no verifiedAt, so the re-verification clock never asked (the #12516 class in its purest form). HISTORY PRESERVED, because it is the argument for `target` being the single handler slot: DIVERGENCE RESOLVED in #3713 — before that fix three readers disagreed in two directions, the parse kept `target`, objectui ActionRunner did `execute || target`, and the CLI compile step (packages/cli/src/utils/lower-callables.ts) preferred a function on `execute`; #3713 made all three prefer `target`, and protocol 17 then removed the alias outright, so the both-declared conflict is unrepresentable rather than merely agreed-upon (mirrors agent.knowledge.topics -> sources, #1891). The server runtime never read `execute` at all — packages/runtime/src/action-execution.ts:525 gates on `target || body` and dispatches on `target`/`name` — so with the parse-time lowering gone there is no reader on any layer." }, "params": { "status": "live", diff --git a/packages/spec/liveness/agent.json b/packages/spec/liveness/agent.json index 77d2aa90012..481e09d15b2 100644 --- a/packages/spec/liveness/agent.json +++ b/packages/spec/liveness/agent.json @@ -1,6 +1,6 @@ { "type": "agent", - "_note": "AgentSchema. Seeded from docs/audits/2026-06-agentschema-property-liveness.md. agent-runtime.ts is the PRINCIPAL runtime consumer, not the only one: `routes/agent-routes.ts`, `routes/assistant-routes.ts`, `routes/agent-access.ts` and `eval/eval-runner.ts` each read keys classified here, and `planning` is read in three of those and in agent-runtime.ts NOT AT ALL — the citation this entry carried for it named the wrong file until 2026-09-15 (#13272). AgentPreview is display-only. ⚠ EVIDENCE LIVES IN CLOUD/EE: the `cloud @: packages/service-ai/...` paths cited below are the closed `@objectstack/service-ai` runtime in the CLOUD repo, NOT git-tracked framework code. 2026-08-30 (#13272): the previous wording of this sentence was false in BOTH halves. (a) It called the framework's own service-ai tree “a stale build artifact with no src/” — there is no tree at all: `packages/services/` holds every sibling service EXCEPT service-ai, and `git ls-files | grep -ic service-ai` returns 0. Absent, not stale. (b) The citations spelled `packages/services/service-ai/...`, a path present in NEITHER repo; cloud's real layout, measured at cloud@15f55df (#13042), is `packages/service-ai/...`. Every citation below now carries the explicit `cloud` realm marker, so it is attributed by the marker `scanEvidence` reads rather than riding the `FOREIGN_PATH_PREFIXES` special case that silently exempted the stale spelling from resolution — which is why 22 dead pointers sat green. ✅ RE-VERIFIED 2026-09-15 (#13272): the re-verification half this entry parked is discharged. Every cloud consumer cited below was re-read in a cloud checkout at cloud @cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8, and the ten rows the read confirmed now carry `verifiedAt`, `evidenceScope: cross-repo`, and a `#symbol` anchor pinned to the consumer instead of a line number — the two cited line numbers (`agent-runtime.ts:264`, `agent-access.ts:50`) had both drifted onto prose, which is the rot a line citation produces and a symbol does not. The anchors are the load-bearing half, and they are why the DATE matters: `scanEvidence` never collects an anchor while a foreign realm marker is in force, so CI cannot re-derive a single one of the cloud anchors — each rests on that dated reading alone. ⛔ Never re-stamp `verifiedAt` here without re-reading cloud; a bare re-stamp restores exactly the unfalsifiable pointer this entry's history is made of. ⛔ ONE ROW WAS DELIBERATELY NOT STAMPED, AND HAS SINCE BEEN RE-GRADED. `tools` was FALSIFIED by the same read: zero consumers in cloud at that ref, where the only two mentions are comments recording the removal of the branch, while this repo's own AgentSchema already declares the key `retiredKey(...)`. A `verifiedAt` beside `live` would have certified the wrong thing, so #13272 left that row's `status`, `note` and old evidence untouched and handed the liveness re-grade to triage as #18304; triage graded it (b) and the re-grade LANDED 2026-09-18 — `live` -> `dead`, the stale `evidence` pointer deleted, and the cloud reading attributed and dated inside the row's own `note`. Note the shape — that row sat `live` since the 2026-06 audit BECAUSE `FOREIGN_PATH_PREFIXES` exempted its citation from resolution, so the exemption this card was filed about had hidden a dead key, not only a misspelled path. These props are `live` because that cloud runtime consumes them; the OPEN framework edition does not — see content/docs/ai for the open/cloud boundary. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent. agent.knowledge (and AIKnowledgeSchema) removed; the topics→sources rename was absorbed into the removal pre-release.", + "_note": "AgentSchema. Seeded from docs/audits/2026-06-agentschema-property-liveness.md. agent-runtime.ts is the PRINCIPAL runtime consumer, not the only one: `routes/agent-routes.ts`, `routes/assistant-routes.ts`, `routes/agent-access.ts` and `eval/eval-runner.ts` each read keys classified here, and `planning` is read in three of those and in agent-runtime.ts NOT AT ALL — the citation this entry carried for it named the wrong file until 2026-09-15 (#13272). AgentPreview is display-only. ⚠ EVIDENCE LIVES IN CLOUD/EE: the `cloud @: packages/service-ai/...` paths cited below are the closed `@objectstack/service-ai` runtime in the CLOUD repo, NOT git-tracked framework code. 2026-08-30 (#13272): the previous wording of this sentence was false in BOTH halves. (a) It called the framework's own service-ai tree “a stale build artifact with no src/” — there is no tree at all: `packages/services/` holds every sibling service EXCEPT service-ai, and `git ls-files | grep -ic service-ai` returns 0. Absent, not stale. (b) The citations spelled `packages/services/service-ai/...`, a path present in NEITHER repo; cloud's real layout, measured at cloud@15f55df (commit c19035e97), is `packages/service-ai/...`. Every citation below now carries the explicit `cloud` realm marker, so it is attributed by the marker `scanEvidence` reads rather than riding the `FOREIGN_PATH_PREFIXES` special case that silently exempted the stale spelling from resolution — which is why 22 dead pointers sat green. ✅ RE-VERIFIED 2026-09-15 (#13272): the re-verification half this entry parked is discharged. Every cloud consumer cited below was re-read in a cloud checkout at cloud @cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8, and the ten rows the read confirmed now carry `verifiedAt`, `evidenceScope: cross-repo`, and a `#symbol` anchor pinned to the consumer instead of a line number — the two cited line numbers (`agent-runtime.ts:264`, `agent-access.ts:50`) had both drifted onto prose, which is the rot a line citation produces and a symbol does not. The anchors are the load-bearing half, and they are why the DATE matters: `scanEvidence` never collects an anchor while a foreign realm marker is in force, so CI cannot re-derive a single one of the cloud anchors — each rests on that dated reading alone. ⛔ Never re-stamp `verifiedAt` here without re-reading cloud; a bare re-stamp restores exactly the unfalsifiable pointer this entry's history is made of. ⛔ ONE ROW WAS DELIBERATELY NOT STAMPED, AND HAS SINCE BEEN RE-GRADED. `tools` was FALSIFIED by the same read: zero consumers in cloud at that ref, where the only two mentions are comments recording the removal of the branch, while this repo's own AgentSchema already declares the key `retiredKey(...)`. A `verifiedAt` beside `live` would have certified the wrong thing, so #13272 left that row's `status`, `note` and old evidence untouched and handed the liveness re-grade to triage as #18304; triage graded it (b) and the re-grade LANDED 2026-09-18 — `live` -> `dead`, the stale `evidence` pointer deleted, and the cloud reading attributed and dated inside the row's own `note`. Note the shape — that row sat `live` since the 2026-06 audit BECAUSE `FOREIGN_PATH_PREFIXES` exempted its citation from resolution, so the exemption this card was filed about had hidden a dead key, not only a misspelled path. These props are `live` because that cloud runtime consumes them; the OPEN framework edition does not — see content/docs/ai for the open/cloud boundary. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent. agent.knowledge (and AIKnowledgeSchema) removed; the topics→sources rename was absorbed into the removal pre-release.", "props": { "name": { "status": "live", diff --git a/packages/spec/liveness/analytics_cube.json b/packages/spec/liveness/analytics_cube.json index e6eaffcba14..85bbabb94e3 100644 --- a/packages/spec/liveness/analytics_cube.json +++ b/packages/spec/liveness/analytics_cube.json @@ -1,6 +1,6 @@ { "type": "analytics_cube", - "_note": "CubeSchema (packages/spec/src/data/analytics.zod.ts). Seeded 2026-09-17 (#18582): the LAST of the three PENDING_GOVERNANCE debts #18133 declared when PR #18581 widened the governance denominator from the registered kinds to `authorableTypes()`; `sharing_rule` was paid first (PR #18587) and `connector` is paid in the same diff as this file, which empties the map. NOT a registered metadata KIND — it is bound in `UNREGISTERED_KIND_SCHEMAS` (#10194) and reaches this walk through `getMetadataTypeSchema`'s unregistered-kind fallback, so the ledger governs it while `listMetadataTypeSchemaTypes()` still does not enumerate it. THE SHAPE FACT THAT DECIDES EVERY ROW BELOW: one Cube shape, THREE producers, one registry. `packages/services/service-analytics/src/cube-registry.ts` names them itself — (1) authored cubes (`defineStack({ analyticsCubes })` / `defineCube()`), threaded by the CLI into `AnalyticsServiceConfig.cubes` and registered by `registerAll`; (2) COMPILED DATASETS (ADR-0021), where `dataset-compiler.ts` MINTS a Cube from a `dataset` document; (3) ad-hoc query inference (`inferCubeFromQuery`). Only (1) is the authoring door this ledger governs, so a key whose only reader sits on path (2) is NOT live here however busy that reader is — that is the #4837 producer rule applied to a shape with three producers, and it is what kept `dimensions.granularities` and `measures.format` dead until 2026-09-29, when the query doors began reading both off whichever cube answers the name (their rows). Every `live` row therefore carries a `producer` naming the CLI threading site: a consumer citation alone would be the `seed.env` shape, where the mechanism was right and nobody supplied the input. #10238 IS NOT PREJUDGED: the PENDING_GOVERNANCE row this file discharges said whether cube authoring is live end-to-end is its own measurement and 'this row does not prejudge it'. This ledger does not answer that question either — it answers the per-key one (who reads this key?), and the answers below are mixed: the query path (`sql`, `measures.sql`/`.type`, `dimensions.sql`/`.type`, `joins.name`) is genuinely consumed, and so is the visibility key `public` (enforced at discovery and at every query door since 2026-09-27 — its row), and so are the measure display `format` and the dimension default bucket `granularities` (read on the query doors since 2026-09-29 — their rows), and so are the three `description` annotations (published on discovery by `getMeta` since 2026-09-29 — their rows), while the retired caching block is not. PREVIEW READ POINTS ENUMERATED (the #7131 mechanical rule, objectui @dda8f3815): `registerBuiltinPreviews()` in packages/app-shell/src/views/metadata-admin/previews/index.ts registers nineteen types and `analytics_cube` is NOT one of them — this type has no registered metadata-admin preview. Recorded rather than skipped, because 'the type has no registered preview' is the sentence a later sweep needs. What objectui DOES consume is the whole SHAPE: `clientValidation.ts` maps `analytics_cube` to `CubeSchema` itself, and unlike `sharing_rule` it is absent from `AUTHOR_SHAPE_ONLY_TYPES`, so both the CREATE and the EDIT door in metadata-admin refuse a cube this schema rejects. ADR-0054: no row here carries a `proof`, and none is owed — the `analytics` high-risk class binds `dataset/dimensions.dateGranularity` (the dataset door), not this type.", + "_note": "CubeSchema (packages/spec/src/data/analytics.zod.ts). Seeded 2026-09-17 (#18582): the LAST of the three PENDING_GOVERNANCE debts #18133 declared when PR #18581 widened the governance denominator from the registered kinds to `authorableTypes()`; `sharing_rule` was paid first (PR #18587) and `connector` is paid in the same diff as this file, which empties the map. NOT a registered metadata KIND — it is bound in `UNREGISTERED_KIND_SCHEMAS` (commit 2306a765c) and reaches this walk through `getMetadataTypeSchema`'s unregistered-kind fallback, so the ledger governs it while `listMetadataTypeSchemaTypes()` still does not enumerate it. THE SHAPE FACT THAT DECIDES EVERY ROW BELOW: one Cube shape, THREE producers, one registry. `packages/services/service-analytics/src/cube-registry.ts` names them itself — (1) authored cubes (`defineStack({ analyticsCubes })` / `defineCube()`), threaded by the CLI into `AnalyticsServiceConfig.cubes` and registered by `registerAll`; (2) COMPILED DATASETS (ADR-0021), where `dataset-compiler.ts` MINTS a Cube from a `dataset` document; (3) ad-hoc query inference (`inferCubeFromQuery`). Only (1) is the authoring door this ledger governs, so a key whose only reader sits on path (2) is NOT live here however busy that reader is — that is the #4837 producer rule applied to a shape with three producers, and it is what kept `dimensions.granularities` and `measures.format` dead until 2026-09-29, when the query doors began reading both off whichever cube answers the name (their rows). Every `live` row therefore carries a `producer` naming the CLI threading site: a consumer citation alone would be the `seed.env` shape, where the mechanism was right and nobody supplied the input. THE END-TO-END MEASUREMENT IS NOT PREJUDGED: the PENDING_GOVERNANCE row this file discharges said whether cube authoring is live end-to-end is its own measurement and 'this row does not prejudge it'. This ledger does not answer that question either — it answers the per-key one (who reads this key?), and the answers below are mixed: the query path (`sql`, `measures.sql`/`.type`, `dimensions.sql`/`.type`, `joins.name`) is genuinely consumed, and so is the visibility key `public` (enforced at discovery and at every query door since 2026-09-27 — its row), and so are the measure display `format` and the dimension default bucket `granularities` (read on the query doors since 2026-09-29 — their rows), and so are the three `description` annotations (published on discovery by `getMeta` since 2026-09-29 — their rows), while the retired caching block is not. PREVIEW READ POINTS ENUMERATED (the #7131 mechanical rule, objectui @dda8f3815): `registerBuiltinPreviews()` in packages/app-shell/src/views/metadata-admin/previews/index.ts registers nineteen types and `analytics_cube` is NOT one of them — this type has no registered metadata-admin preview. Recorded rather than skipped, because 'the type has no registered preview' is the sentence a later sweep needs. What objectui DOES consume is the whole SHAPE: `clientValidation.ts` maps `analytics_cube` to `CubeSchema` itself, and unlike `sharing_rule` it is absent from `AUTHOR_SHAPE_ONLY_TYPES`, so both the CREATE and the EDIT door in metadata-admin refuse a cube this schema rejects. ADR-0054: no row here carries a `proof`, and none is owed — the `analytics` high-risk class binds `dataset/dimensions.dateGranularity` (the dataset door), not this type.", "props": { "name": { "status": "live", diff --git a/packages/spec/liveness/batch_endpoints.json b/packages/spec/liveness/batch_endpoints.json index f476219a7e5..13682e36151 100644 --- a/packages/spec/liveness/batch_endpoints.json +++ b/packages/spec/liveness/batch_endpoints.json @@ -1,6 +1,6 @@ { "type": "batch_endpoints", - "_note": "BatchEndpointsConfigSchema — packages/spec/src/api/rest-server.zod.ts#BatchEndpointsConfigSchema, the `batch` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census filed with #14369, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Mixed: `maxBatchSize`, `enableBatchEndpoint` and three of the four `operations.*` switches are read; `operations.upsertMany` and `defaultAtomic` are not. This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY (#15543, measured 2026-09-08 on origin/main 8ccf7a1df): every `live` row in this file now carries a REACHABILITY sentence, and they all say the same thing because it is one measurement. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and there is exactly ONE door, programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site reaching `new RestServer(...)`. CORRECTED 2026-09-08: an earlier draft of this sentence named a second door, createHonoServerPlugin({ restConfig }) \u2014 NO SUCH FUNCTION EXISTS. A definition probe returns zero across the tree against a positive control that finds createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. The real shape: HonoServerPlugin is a CLASS (packages/plugins/plugin-hono-server/src/hono-plugin.ts:222) that declares a restConfig?: RestServerConfig option whose single reader (hono-plugin.ts:595) takes api.basePath for the SPA fallback; it never constructs a REST server, so it is not a door onto any key in this file. No shipped boot path opens the one real door with a config of its own: packages/cli/src/commands/serve.ts reads the stack config's own top-level `api:` block and forwards exactly two keys out of it (api.enableProjectScoping and api.projectResolution, serve.ts:3966-3968) into a fixed argument, through an `as any` cast; packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all. So the CLI does read a config file, it just forwards those two keys and nothing else, and every key in this file is EMBEDDER-ONLY. A CLI-started deployment therefore always gets the schema default for every key here. That is the RECORDED POSTURE, not a gap awaiting a fix: ruled 2026-09-07 (director seat, summon #17, decision batch #2) that the keys stay and keep their runtime reads, that threading a config through is not taken (a new authorable surface for no measured demand) and that retiring them is refused (they have an embedder consumer); the docblocks in packages/spec/src/api/rest-server.zod.ts state the reachability plainly, and this ledger is where the ADR-0049 question gets its written per-key answer. WHY REACHABILITY IS A SEPARATE AXIS FROM `status`: `live` means the runtime READS the key, which is the only thing this ledger's statuses classify, and it is correct here. Reachability answers who can WRITE it, which `live` never answered and which this card, #15542 and the three api-backend.rest-*-config-contract checklist items each had to re-derive from the boot paths because no file recorded it. The two never substitute for each other, and adding these sentences re-verified no call graph, so `verifiedAt` is deliberately NOT bumped by this edit.", + "_note": "BatchEndpointsConfigSchema — packages/spec/src/api/rest-server.zod.ts#BatchEndpointsConfigSchema, the `batch` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census recorded in commit a3d5724c8, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Mixed: `maxBatchSize`, `enableBatchEndpoint` and three of the four `operations.*` switches are read; `operations.upsertMany` and `defaultAtomic` are not. This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY (#15543, measured 2026-09-08 on origin/main 8ccf7a1df): every `live` row in this file now carries a REACHABILITY sentence, and they all say the same thing because it is one measurement. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and there is exactly ONE door, programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site reaching `new RestServer(...)`. CORRECTED 2026-09-08: an earlier draft of this sentence named a second door, createHonoServerPlugin({ restConfig }) \u2014 NO SUCH FUNCTION EXISTS. A definition probe returns zero across the tree against a positive control that finds createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. The real shape: HonoServerPlugin is a CLASS (packages/plugins/plugin-hono-server/src/hono-plugin.ts:222) that declares a restConfig?: RestServerConfig option whose single reader (hono-plugin.ts:595) takes api.basePath for the SPA fallback; it never constructs a REST server, so it is not a door onto any key in this file. No shipped boot path opens the one real door with a config of its own: packages/cli/src/commands/serve.ts reads the stack config's own top-level `api:` block and forwards exactly two keys out of it (api.enableProjectScoping and api.projectResolution, serve.ts:3966-3968) into a fixed argument, through an `as any` cast; packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all. So the CLI does read a config file, it just forwards those two keys and nothing else, and every key in this file is EMBEDDER-ONLY. A CLI-started deployment therefore always gets the schema default for every key here. That is the RECORDED POSTURE, not a gap awaiting a fix: ruled 2026-09-07 (director seat, summon #17, decision batch #2) that the keys stay and keep their runtime reads, that threading a config through is not taken (a new authorable surface for no measured demand) and that retiring them is refused (they have an embedder consumer); the docblocks in packages/spec/src/api/rest-server.zod.ts state the reachability plainly, and this ledger is where the ADR-0049 question gets its written per-key answer. WHY REACHABILITY IS A SEPARATE AXIS FROM `status`: `live` means the runtime READS the key, which is the only thing this ledger's statuses classify, and it is correct here. Reachability answers who can WRITE it, which `live` never answered and which this card, #15542 and the three api-backend.rest-*-config-contract checklist items each had to re-derive from the boot paths because no file recorded it. The two never substitute for each other, and adding these sentences re-verified no call graph, so `verifiedAt` is deliberately NOT bumped by this edit.", "props": { "maxBatchSize": { "status": "live", @@ -48,7 +48,7 @@ "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); there is no `POST /data/:object/upsertMany` and no protocol member behind it (createManyData / updateManyData / deleteManyData only); upsert is an operation TYPE of the generic `POST /data/:object/batch` endpoint (`BatchOperationType` 'upsert', keyed by `externalId`), which `enableBatchEndpoint` gates. Pre-retirement census note: 0 read sites at 2514d49f3. Its three siblings each gate a route mount; this one gates nothing, because there is no upsertMany route to gate — the switch was declared for a batch operation that was never built (`this.protocol` has createManyData / updateManyData / deleteManyData and no upsert counterpart). `operations.upsertMany: false` therefore disables nothing and `true` enables nothing. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); there is no `POST /data/:object/upsertMany` and no protocol member behind it (createManyData / updateManyData / deleteManyData only); upsert is an operation TYPE of the generic `POST /data/:object/batch` endpoint (`BatchOperationType` 'upsert', keyed by `externalId`), which `enableBatchEndpoint` gates. Pre-retirement census note: 0 read sites at 2514d49f3. Its three siblings each gate a route mount; this one gates nothing, because there is no upsertMany route to gate — the switch was declared for a batch operation that was never built (`this.protocol` has createManyData / updateManyData / deleteManyData and no upsert counterpart). `operations.upsertMany: false` therefore disables nothing and `true` enables nothing. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." } } }, @@ -56,7 +56,7 @@ "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); atomicity is decided per request by `options.atomic` in the batch body (`BatchOptionsSchema`, ADR-0119 D4 — opt-in, default false, deliberately aligned to what every caller already got); a server-side default that flipped it silently would change the failure semantics of callers who send nothing, the move ADR-0119 D4 refused, which is why this family resolved to REMOVE rather than ENFORCE. Pre-retirement census note: 0 read sites at 2514d49f3. Repo-wide there are no reads outside packages/spec and rest-server.ts either. The key is normalized into `this.config.batch.defaultAtomic` and no batch handler consults it, so `batch.defaultAtomic: false` changes no batch's transaction mode. A switch whose describe() promises a transaction default while the transaction default is decided elsewhere is the false-compliance shape this ledger exists to surface, which is why it is recorded rather than left to a reader's grep. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); atomicity is decided per request by `options.atomic` in the batch body (`BatchOptionsSchema`, ADR-0119 D4 — opt-in, default false, deliberately aligned to what every caller already got); a server-side default that flipped it silently would change the failure semantics of callers who send nothing, the move ADR-0119 D4 refused, which is why this family resolved to REMOVE rather than ENFORCE. Pre-retirement census note: 0 read sites at 2514d49f3. Repo-wide there are no reads outside packages/spec and rest-server.ts either. The key is normalized into `this.config.batch.defaultAtomic` and no batch handler consults it, so `batch.defaultAtomic: false` changes no batch's transaction mode. A switch whose describe() promises a transaction default while the transaction default is decided elsewhere is the false-compliance shape this ledger exists to surface, which is why it is recorded rather than left to a reader's grep. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." } } } diff --git a/packages/spec/liveness/book.json b/packages/spec/liveness/book.json index 07a35813ce5..e93db13fa3c 100644 --- a/packages/spec/liveness/book.json +++ b/packages/spec/liveness/book.json @@ -1,18 +1,18 @@ { "type": "book", - "_note": "BookSchema (ADR-0046 §6 documentation spine). Consumers: the REST `/meta/book/:name/tree` endpoint (`packages/rest/src/rest-server.ts`, the `book/:name/tree` branch of `registerMetadataEndpointsInner`) driving the spec's pure `resolveBookTree` / `audienceAllows` (packages/spec/src/system/book.zod.ts), and objectui's console docs portal (apps/console/src/pages/book-nav.ts @940ba24 — a faithful resolver port rendering the reader UI, plus portal-only consumers for slug/icon/order). `groups[].translations` is dead: an inline map that LOOKS like the doc-level mechanism that works (`doc.translations`, resolveDocLocale) but has no resolver anywhere. Its book-level twin was the same trap and was retired in the same pass (#4667) — that one is a strict deletion, so it left the walked shape and holds no row here, while the group-level key is TOMBSTONED (`retiredKey`; BookGroupSchema is a plain z.object with no `.strict()`) and stays in the shape this ledger walks. No total is restated in this header: `state-counts/book.md` publishes the counts and `check:liveness` proves that shard fresh on every run. Seeded 2026-08-01 (#4488). 2026-08-28 (#13003): every LOCAL citation in this file was re-anchored to its consuming symbol. The four objectui-only entries (`description`, `slug`, `icon`, `order`) are left BYTE-FOR-BYTE UNTOUCHED and undated-forward on purpose: their evidence is pinned at `objectui @940ba24`, a commit this container cannot reproduce, and the anchor grammar deliberately never collects foreign anchors — re-stamping `verifiedAt` on a call graph nobody re-closed is exactly the false confidence this ledger exists to prevent (the `tool.json` precedent).", + "_note": "BookSchema (ADR-0046 §6 documentation spine). Consumers: the REST `/meta/book/:name/tree` endpoint (`packages/rest/src/rest-server.ts`, the `book/:name/tree` branch of `registerMetadataEndpointsInner`) driving the spec's pure `resolveBookTree` / `audienceAllows` (packages/spec/src/system/book.zod.ts), and objectui's console docs portal (apps/console/src/pages/book-nav.ts @940ba24 — a faithful resolver port rendering the reader UI, plus portal-only consumers for slug/icon/order). `groups[].translations` is dead: an inline map that LOOKS like the doc-level mechanism that works (`doc.translations`, resolveDocLocale) but has no resolver anywhere. Its book-level twin was the same trap and was retired in the same pass (#4667) — that one is a strict deletion, so it left the walked shape and holds no row here, while the group-level key is TOMBSTONED (`retiredKey`; BookGroupSchema is a plain z.object with no `.strict()`) and stays in the shape this ledger walks. No total is restated in this header: `state-counts/book.md` publishes the counts and `check:liveness` proves that shard fresh on every run. Seeded 2026-08-01 (#4488). 2026-08-28 (commit 8cb96ec41): every LOCAL citation in this file was re-anchored to its consuming symbol. The four objectui-only entries (`description`, `slug`, `icon`, `order`) are left BYTE-FOR-BYTE UNTOUCHED and undated-forward on purpose: their evidence is pinned at `objectui @940ba24`, a commit this container cannot reproduce, and the anchor grammar deliberately never collects foreign anchors — re-stamping `verifiedAt` on a call graph nobody re-closed is exactly the false confidence this ledger exists to prevent (the `tool.json` precedent).", "props": { "name": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/rest/src/meta-item-read-gate.ts#deriveImplicitPackageBook (`bookNamed`: `books.find((b: any) => b && b.name === name) ?? deriveImplicitPackageBook(name, name)` — the tree route's `audience.bookNamed(req.params.name)` matches the book by name and, failing that, synthesizes the implicit per-package book from the same segment — §6.4); packages/spec/src/system/book.zod.ts#resolveBookTree (`return { name: book.name, … }` — the resolved tree carries it back out)", - "note": "tree-route identity; an unknown name is treated as a package id and resolved as the implicit per-package book (§6.4). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `rest-server.ts:3098` had rotted onto `patterns: crud.patterns` in the CRUD-endpoint config, ~1,800 lines from the book route. The `_note`'s file-level range (`:3078-3169`) had rotted with it and is replaced by a symbol too. Re-closed by hand against 93ea19bca." + "note": "tree-route identity; an unknown name is treated as a package id and resolved as the implicit per-package book (§6.4). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `rest-server.ts:3098` had rotted onto `patterns: crud.patterns` in the CRUD-endpoint config, ~1,800 lines from the book route. The `_note`'s file-level range (`:3078-3169`) had rotted with it and is replaced by a symbol too. Re-closed by hand against 93ea19bca." }, "label": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (`return { name: book.name, label: book.label, … }`)", - "note": "carried into the resolved tree; portal cards fall back to `name`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:303` lands on `.map((g, i) => ({ g, i }))`, the GROUP sort decoration at the top of `resolveBookTree`: the right function, the wrong read, and about a sibling key (`groups.order`) rather than this one. Right-function-wrong-read is the residual class an anchor cannot remove — the anchor is honest about which function, and the parenthetical now carries the expression, which is the part a reader can check. Re-closed by hand against 93ea19bca." + "note": "carried into the resolved tree; portal cards fall back to `name`. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:303` lands on `.map((g, i) => ({ g, i }))`, the GROUP sort decoration at the top of `resolveBookTree`: the right function, the wrong read, and about a sibling key (`groups.order`) rather than this one. Right-function-wrong-read is the residual class an anchor cannot remove — the anchor is honest about which function, and the parenthetical now carries the expression, which is the part a reader can check. Re-closed by hand against 93ea19bca." }, "description": { "status": "live", @@ -42,7 +42,7 @@ "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/spec/src/system/book.zod.ts#audienceAllows (the gate itself: `public` always, `org`/unset any authenticated principal, `{ permissionSet }` only a holder, unknown shape → false); packages/spec/src/system/book.zod.ts#resolveDocAudiences (`const audience = book.audience ?? 'org'` — every doc's effective audience is the UNION over the books that claim it, unclaimed docs falling back to `org`); packages/rest/src/meta-item-read-gate.ts#audienceAllows (`admitsBook: (book: Book) => audienceAllows(book?.audience, caller)` — the gate the tree route asks as `if (!audience.admitsBook(book))` → 401 anonymous / 403 non-holder, before any tree is built, and the book item read asks on both transports)", - "note": "ENFORCED access gate (§6.7), fail-closed: gates the whole tree (401 anonymous / 403 non-holder), and every doc's effective audience is the union over the books that claim it (resolveDocAudiences) — applied to both doc lists and tree entries. The one security-shaped property on this type, and it is real. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — ALL THREE legs were wrong. `rest-server.ts:3113` lands on the comment `// config to read).` and `:2969` on a docblock sentence about `'v1/beta'` splicing a path segment; `book.zod.ts:351` lands on `entries.push(entryFromDoc(d))` inside `resolveBookTree`'s `...` rest-expansion — tree ASSEMBLY, not the gate, ~83 lines from `audienceAllows`. A security-shaped property whose every pointer had drifted, one of them onto a comment, is the case this whole worklist is justified by: the verdict was right, and nothing cited could have shown it. Re-closed by hand against 93ea19bca." + "note": "ENFORCED access gate (§6.7), fail-closed: gates the whole tree (401 anonymous / 403 non-holder), and every doc's effective audience is the union over the books that claim it (resolveDocAudiences) — applied to both doc lists and tree entries. The one security-shaped property on this type, and it is real. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — ALL THREE legs were wrong. `rest-server.ts:3113` lands on the comment `// config to read).` and `:2969` on a docblock sentence about `'v1/beta'` splicing a path segment; `book.zod.ts:351` lands on `entries.push(entryFromDoc(d))` inside `resolveBookTree`'s `...` rest-expansion — tree ASSEMBLY, not the gate, ~83 lines from `audienceAllows`. A security-shaped property whose every pointer had drifted, one of them onto a comment, is the case this whole worklist is justified by: the verdict was right, and nothing cited could have shown it. Re-closed by hand against 93ea19bca." }, "groups": { "children": { @@ -50,13 +50,13 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (`d.group === group.key` places an explicit member, `derivedMembers.set(group.key, members)` buckets them, and `{ key: group.key, label: group.label, entries }` keys the resolved group)", - "note": "group identity: explicit `doc.group` placement matches on it, and it keys the resolved tree. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:238` is the docblock of `ResolvedEntrySchema` and `:290` a bare `*` continuation line inside `resolveBookTree`'s own docblock. Both sit close enough to be believable and neither is a read. Re-closed by hand against 93ea19bca." + "note": "group identity: explicit `doc.group` placement matches on it, and it keys the resolved tree. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:238` is the docblock of `ResolvedEntrySchema` and `:290` a bare `*` continuation line inside `resolveBookTree`'s own docblock. Both sit close enough to be believable and neither is a read. Re-closed by hand against 93ea19bca." }, "label": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (`resolvedGroups.push({ key: group.key, label: group.label, entries })` — the section title of the resolved group)", - "note": "section title in the resolved tree. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:290` is a bare `*` line in `resolveBookTree`'s docblock, shared with `groups.key`'s rotted second leg: one docblock line doing duty as evidence for two different keys. Re-closed by hand against 93ea19bca." + "note": "section title in the resolved tree. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:290` is a bare `*` line in `resolveBookTree`'s docblock, shared with `groups.key`'s rotted second leg: one docblock line doing duty as evidence for two different keys. Re-closed by hand against 93ea19bca." }, "translations": { "status": "dead", @@ -68,25 +68,25 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (`.sort((a, b) => (a.g.order ?? 0) - (b.g.order ?? 0) || a.i - b.i)` — 0 when absent, declaration index as the stable tiebreak)", - "note": "orders groups within the book (0 default, then declaration order). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:221` is `key: string;` in the `ResolvedGroup` INTERFACE: a citation for `order` landing on the type declaration of a sibling key, in a block that describes the RESOLVED shape rather than the authored one. Re-closed by hand against 93ea19bca." + "note": "orders groups within the book (0 default, then declaration order). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:221` is `key: string;` in the `ResolvedGroup` INTERFACE: a citation for `order` landing on the type declaration of a sibling key, in a block that describes the RESOLVED shape rather than the authored one. Re-closed by hand against 93ea19bca." }, "include": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#matchesInclude (both forms — a string is a name glob via `globToRegExp`, `{ tag }` tests `doc.tags`, and `scopePackage` short-circuits a foreign doc first); packages/spec/src/system/book.zod.ts#resolveBookTree (`group.include != null && matchesInclude(d, group.include, scope)` — called on the derived-membership pass AND again inside a `pages` group's `...` rest-expansion)", - "note": "the derived-membership rule — the heart of the §6.2.1 design. Both forms are live: the GLOB variant matches on name/group, and the `{ tag }` variant (matchesInclude reads doc.tags) started matching the moment DocSchema DECLARED `tags` in 17.0.0 (#4509, ADR-0049) — see doc.json's `tags` entry for the fix's full history. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:236` is a docblock line about #12038's describe-only transcription and `:193` a BLANK LINE in the resolver's section banner. The entry's prose already named `matchesInclude` correctly; it is only the positions that had rotted, which is the whole shape of this migration. Re-closed by hand against 93ea19bca." + "note": "the derived-membership rule — the heart of the §6.2.1 design. Both forms are live: the GLOB variant matches on name/group, and the `{ tag }` variant (matchesInclude reads doc.tags) started matching the moment DocSchema DECLARED `tags` in 17.0.0 (#4509, ADR-0049) — see doc.json's `tags` entry for the fix's full history. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `:236` is a docblock line about #12038's describe-only transcription and `:193` a BLANK LINE in the resolver's section banner. The entry's prose already named `matchesInclude` correctly; it is only the positions that had rotted, which is the whole shape of this migration. Re-closed by hand against 93ea19bca." }, "package": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (`const scope = group.package ?? bookPackage`, on both the derived pass and the `pages` rest-expansion); packages/spec/src/system/book.zod.ts#matchesInclude (`if (scopePackage && doc.packageId && doc.packageId !== scopePackage) return false` — where the scope is actually enforced)", - "note": "scopes the rule to a package id (cross-package books, ADR-0048). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:232` is the `/**` that OPENS `ResolvedEntrySchema`'s docblock. The entry now cites both halves separately: the read that computes the scope and the comparison that enforces it, which were one line apart when this was written and are ~40 lines apart today. Re-closed by hand against 93ea19bca." + "note": "scopes the rule to a package id (cross-package books, ADR-0048). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:232` is the `/**` that OPENS `ResolvedEntrySchema`'s docblock. The entry now cites both halves separately: the read that computes the scope and the comparison that enforces it, which were one line apart when this was written and are ~40 lines apart today. Re-closed by hand against 93ea19bca." }, "pages": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (`if (group.pages) continue` excludes the group from the derived pass, then the override walk: `'---'` → a separator entry, `'...'` → rest-expansion filtered by the same `include`/`group` rules, a string → `byName` lookup with a not-found placeholder, and a node object → `entryFromDoc` overlaid with `label`/`badge`/`icon`)", - "note": "explicit curated-order override; `---` separators and `...` rest-expansion both implemented, node label/badge/icon overrides carried into entries. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:248-286` was a RANGE, and the line bound checks only its END (a start inside the file with an end past EOF is the case that rule exists for), so the whole span was unbounded on one side by design; today it spans `ResolvedEntrySchema`'s field list into `ResolvedBookSchema`, i.e. the response contract rather than the resolver. A range citation is the weakest form the grammar allows for exactly this reason — it names more lines and therefore fewer things. Re-closed by hand against 93ea19bca." + "note": "explicit curated-order override; `---` separators and `...` rest-expansion both implemented, node label/badge/icon overrides carried into entries. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:248-286` was a RANGE, and the line bound checks only its END (a start inside the file with an end past EOF is the case that rule exists for), so the whole span was unbounded on one side by design; today it spans `ResolvedEntrySchema`'s field list into `ResolvedBookSchema`, i.e. the response contract rather than the resolver. A range citation is the weakest form the grammar allows for exactly this reason — it names more lines and therefore fewer things. Re-closed by hand against 93ea19bca." } }, "note": "Drilled because `translations` diverges (dead) from its six live siblings." diff --git a/packages/spec/liveness/capability.json b/packages/spec/liveness/capability.json index bdc997f0217..9c3a6a9850c 100644 --- a/packages/spec/liveness/capability.json +++ b/packages/spec/liveness/capability.json @@ -1,36 +1,36 @@ { "type": "capability", - "_note": "CapabilityDeclarationSchema (ADR-0066 D1). The DECLARATION side of the three-way separation — packages DEFINE a capability here, permission sets GRANT it via `systemPermissions`, resources REQUIRE it via `requiredPermissions`. Seeded 2026-08-08 with #5961, the PR that made `capability` a registered metadata kind; every property was call-graph-closed at that time against `packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts`, which is the one consumer that turns a declaration into a `sys_capability` row. The ADR-0010 envelope keys carry the same `null` verdict as `permission`/`position`: they are loader-stamped, not authored. 2026-08-28 (#13003): all six `path:NNN` citations in this file were re-anchored to their consuming symbols. Five of the six were wrong and every one was IN RANGE; the three row-field pointers had drifted as a block onto three consecutive lines of one unrelated docblock, which reads as precision right up until you open the file.", + "_note": "CapabilityDeclarationSchema (ADR-0066 D1). The DECLARATION side of the three-way separation — packages DEFINE a capability here, permission sets GRANT it via `systemPermissions`, resources REQUIRE it via `requiredPermissions`. Seeded 2026-08-08 with #5961, the PR that made `capability` a registered metadata kind; every property was call-graph-closed at that time against `packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts`, which is the one consumer that turns a declaration into a `sys_capability` row. The ADR-0010 envelope keys carry the same `null` verdict as `permission`/`position`: they are loader-stamped, not authored. 2026-08-28 (commit 8f10a79f7): all six `path:NNN` citations in this file were re-anchored to their consuming symbols. Five of the six were wrong and every one was IN RANGE; the three row-field pointers had drifted as a block onto three consecutive lines of one unrelated docblock, which reads as precision right up until you open the file.", "props": { "name": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#upsertPackageCapability (the name is the `sys_capability` upsert key and is matched against `PLATFORM_CAPABILITY_NAMES` to refuse a package shadowing a curated capability; it is also the string `systemPermissions` / `requiredPermissions` resolve a grant by); packages/lint/src/validate-capability-references.ts#validateCapabilityReferences (`known.add(cap.name)` — the authoring lint's known-name set)", - "note": "The whole identity of a capability. This is also why the kind's write door matters: the name is resolved by string from both the grant side and the requirement side, so an unvalidated row lands directly in the authorization namespace (#5961). 2026-08-28: RE-ANCHORED (#13003), and REPOINTED on the bootstrap leg — `:203-210` had rotted onto the opening `/**` of the unowned-declaration diagnostic's docblock; `upsertPackageCapability` begins ~43 lines below it. The lint leg was ACCURATE (`:99` is still `known.add(cap.name)`) and is migrated, not repaired. Re-closed by hand against 8cb96ec41." + "note": "The whole identity of a capability. This is also why the kind's write door matters: the name is resolved by string from both the grant side and the requirement side, so an unvalidated row lands directly in the authorization namespace (#5961). 2026-08-28: RE-ANCHORED (commit 8f10a79f7), and REPOINTED on the bootstrap leg — `:203-210` had rotted onto the opening `/**` of the unowned-declaration diagnostic's docblock; `upsertPackageCapability` begins ~43 lines below it. The lint leg was ACCURATE (`:99` is still `known.add(cap.name)`) and is migrated, not repaired. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#capabilityRowFields (`label: typeof cap.label === 'string' && cap.label ? cap.label : humanize(cap.name)` — written to `sys_capability.label`)", - "note": "display (Setup's capability list/detail, rendered from the sys_capability row). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:122` had rotted onto a docblock sentence about the `unchanged` outcome counter, ~45 lines above `capabilityRowFields`. Its two siblings cited `:123` and `:124`, the next two lines of that same docblock: the three row-field pointers had drifted as ONE block, which is why none of them looked wrong beside the others. Re-closed by hand against 8cb96ec41." + "note": "display (Setup's capability list/detail, rendered from the sys_capability row). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:122` had rotted onto a docblock sentence about the `unchanged` outcome counter, ~45 lines above `capabilityRowFields`. Its two siblings cited `:123` and `:124`, the next two lines of that same docblock: the three row-field pointers had drifted as ONE block, which is why none of them looked wrong beside the others. Re-closed by hand against 8cb96ec41." }, "description": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#capabilityRowFields (`description: … ? cap.description : `Capability ${cap.name}.`` — written to `sys_capability.description`)", - "note": "display. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:123` had rotted onto the closing `*/` of that docblock (see the `label` entry: all three of this file's row-field pointers moved together). Re-closed by hand against 8cb96ec41." + "note": "display. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:123` had rotted onto the closing `*/` of that docblock (see the `label` entry: all three of this file's row-field pointers moved together). Re-closed by hand against 8cb96ec41." }, "scope": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#capabilityRowFields (`scope: cap.scope === 'org' ? 'org' : 'platform'` — normalized on an exact `org` match, else `platform`)", - "note": "platform vs org decides whether holding the capability is a global power or one scoped to the caller's organization — the same distinction PLATFORM_CAPABILITIES carries on the curated side. Not display: it is the field that would be escalated if a tenant could overlay a package-shipped declaration, which is why the #5961 registry entry sets `supportsOverlay: false` / `allowOrgOverride: false`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:124` had rotted onto `unchanged: number;`, the counter field itself (see the `label` entry). Re-closed by hand against 8cb96ec41." + "note": "platform vs org decides whether holding the capability is a global power or one scoped to the caller's organization — the same distinction PLATFORM_CAPABILITIES carries on the curated side. Not display: it is the field that would be escalated if a tenant could overlay a package-shipped declaration, which is why the #5961 registry entry sets `supportsOverlay: false` / `allowOrgOverride: false`. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:124` had rotted onto `unchanged: number;`, the counter field itself (see the `label` entry). Re-closed by hand against 8cb96ec41." }, "packageId": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#bootstrapDeclaredCapabilities (`const packageId: string | undefined = cap._packageId ?? cap.packageId ?? undefined` — the ADR-0086 D3 author-declared FALLBACK provenance, consulted when the registry stamp is absent); packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#upsertPackageCapability (with neither, this takes the no-owning-package refusal and the capability is materialized with no package provenance)", - "note": "Deliberately a fallback and not the primary: #5870 added `capabilities` to the ObjectQL engine's stamped-collection list (packages/objectql/src/engine.ts:2393), so `_packageId` now reaches a declaration and takes precedence. The key stays live because the fallback branch is still read and still decides materialization for any declaration that arrives unstamped. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:294` had rotted onto a `//` continuation line inside the unowned-declaration refusal's comment block, and the read itself had moved OUT of `upsertPackageCapability` into its caller, which is the distinction the two anchors now hold apart. The note's `engine.ts:2393` pointer was stale in the same way; the stamped-collection list is `packages/objectql/src/engine.ts#METADATA_ARRAY_KEYS`. Re-closed by hand against 8cb96ec41." + "note": "Deliberately a fallback and not the primary: #5870 added `capabilities` to the ObjectQL engine's stamped-collection list (packages/objectql/src/engine.ts:2393), so `_packageId` now reaches a declaration and takes precedence. The key stays live because the fallback branch is still read and still decides materialization for any declaration that arrives unstamped. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:294` had rotted onto a `//` continuation line inside the unowned-declaration refusal's comment block, and the read itself had moved OUT of `upsertPackageCapability` into its caller, which is the distinction the two anchors now hold apart. The note's `engine.ts:2393` pointer was stale in the same way; the stamped-collection list is `packages/objectql/src/engine.ts#METADATA_ARRAY_KEYS`. Re-closed by hand against 8cb96ec41." }, "_lock": null, "_lockReason": null, diff --git a/packages/spec/liveness/crud_endpoints.json b/packages/spec/liveness/crud_endpoints.json index f1054bc4d6b..246f02e1f69 100644 --- a/packages/spec/liveness/crud_endpoints.json +++ b/packages/spec/liveness/crud_endpoints.json @@ -1,6 +1,6 @@ { "type": "crud_endpoints", - "_note": "CrudEndpointsConfigSchema — packages/spec/src/api/rest-server.zod.ts#CrudEndpointsConfigSchema, the `crud` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census filed with #14369, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Mixed: `operations.*` and `dataPrefix` gate and shape the mounted CRUD surface, while `patterns` and `objectParamStyle` are normalized and never read. This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY (#15543, measured 2026-09-08 on origin/main 8ccf7a1df): every `live` row in this file now carries a REACHABILITY sentence, and they all say the same thing because it is one measurement. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and there is exactly ONE door, programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site reaching `new RestServer(...)`. CORRECTED 2026-09-08: an earlier draft of this sentence named a second door, createHonoServerPlugin({ restConfig }) \u2014 NO SUCH FUNCTION EXISTS. A definition probe returns zero across the tree against a positive control that finds createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. The real shape: HonoServerPlugin is a CLASS (packages/plugins/plugin-hono-server/src/hono-plugin.ts:222) that declares a restConfig?: RestServerConfig option whose single reader (hono-plugin.ts:595) takes api.basePath for the SPA fallback; it never constructs a REST server, so it is not a door onto any key in this file. No shipped boot path opens the one real door with a config of its own: packages/cli/src/commands/serve.ts reads the stack config's own top-level `api:` block and forwards exactly two keys out of it (api.enableProjectScoping and api.projectResolution, serve.ts:3966-3968) into a fixed argument, through an `as any` cast; packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all. So the CLI does read a config file, it just forwards those two keys and nothing else, and every key in this file is EMBEDDER-ONLY. A CLI-started deployment therefore always gets the schema default for every key here. That is the RECORDED POSTURE, not a gap awaiting a fix: ruled 2026-09-07 (director seat, summon #17, decision batch #2) that the keys stay and keep their runtime reads, that threading a config through is not taken (a new authorable surface for no measured demand) and that retiring them is refused (they have an embedder consumer); the docblocks in packages/spec/src/api/rest-server.zod.ts state the reachability plainly, and this ledger is where the ADR-0049 question gets its written per-key answer. WHY REACHABILITY IS A SEPARATE AXIS FROM `status`: `live` means the runtime READS the key, which is the only thing this ledger's statuses classify, and it is correct here. Reachability answers who can WRITE it, which `live` never answered and which this card, #15542 and the three api-backend.rest-*-config-contract checklist items each had to re-derive from the boot paths because no file recorded it. The two never substitute for each other, and adding these sentences re-verified no call graph, so `verifiedAt` is deliberately NOT bumped by this edit.", + "_note": "CrudEndpointsConfigSchema — packages/spec/src/api/rest-server.zod.ts#CrudEndpointsConfigSchema, the `crud` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census recorded in commit a3d5724c8, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Mixed: `operations.*` and `dataPrefix` gate and shape the mounted CRUD surface, while `patterns` and `objectParamStyle` are normalized and never read. This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY (#15543, measured 2026-09-08 on origin/main 8ccf7a1df): every `live` row in this file now carries a REACHABILITY sentence, and they all say the same thing because it is one measurement. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and there is exactly ONE door, programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site reaching `new RestServer(...)`. CORRECTED 2026-09-08: an earlier draft of this sentence named a second door, createHonoServerPlugin({ restConfig }) \u2014 NO SUCH FUNCTION EXISTS. A definition probe returns zero across the tree against a positive control that finds createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. The real shape: HonoServerPlugin is a CLASS (packages/plugins/plugin-hono-server/src/hono-plugin.ts:222) that declares a restConfig?: RestServerConfig option whose single reader (hono-plugin.ts:595) takes api.basePath for the SPA fallback; it never constructs a REST server, so it is not a door onto any key in this file. No shipped boot path opens the one real door with a config of its own: packages/cli/src/commands/serve.ts reads the stack config's own top-level `api:` block and forwards exactly two keys out of it (api.enableProjectScoping and api.projectResolution, serve.ts:3966-3968) into a fixed argument, through an `as any` cast; packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all. So the CLI does read a config file, it just forwards those two keys and nothing else, and every key in this file is EMBEDDER-ONLY. A CLI-started deployment therefore always gets the schema default for every key here. That is the RECORDED POSTURE, not a gap awaiting a fix: ruled 2026-09-07 (director seat, summon #17, decision batch #2) that the keys stay and keep their runtime reads, that threading a config through is not taken (a new authorable surface for no measured demand) and that retiring them is refused (they have an embedder consumer); the docblocks in packages/spec/src/api/rest-server.zod.ts state the reachability plainly, and this ledger is where the ADR-0049 question gets its written per-key answer. WHY REACHABILITY IS A SEPARATE AXIS FROM `status`: `live` means the runtime READS the key, which is the only thing this ledger's statuses classify, and it is correct here. Reachability answers who can WRITE it, which `live` never answered and which this card, #15542 and the three api-backend.rest-*-config-contract checklist items each had to re-derive from the boot paths because no file recorded it. The two never substitute for each other, and adding these sentences re-verified no call graph, so `verifiedAt` is deliberately NOT bumped by this edit.", "props": { "operations": { "children": { @@ -50,7 +50,7 @@ "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); every CRUD route is mounted from the fixed method/path pairs in rest-server.ts#registerCrudEndpoints, which the client SDK, the discovery document and the served /openapi.json all describe, so a per-operation method/path knob was never enforceable without making those surfaces lie; `crud.dataPrefix` is the live knob that moves them, and an endpoint on a custom path or method is a declarative `api` endpoint (`type: 'object_operation'`). The four child rows this container carried (`method` / `path` / `summary` / `description` — the members of `CrudEndpointPatternSchema`) collapse into this one row: the tombstone is a leaf, the value def left the shape with it (RETIRED_DEFS_BY_MAJOR[18] `api/CrudEndpointPattern`), and rows for keys that left the walked shape would report ORPHAN. Closes #14365's question about the record's input type — there is no record left to reshape. Pre-retirement census note: 0 read sites at 2514d49f3. The `patterns` record is normalized into `this.config.crud.patterns` and never read: every CRUD route is mounted from the hard-coded method/path pairs in rest-server.ts#registerCrudEndpoints, so a custom pattern changes nothing. The four rows here are that one container's members — `patterns` is dead as a whole, and each member is recorded so the verdict is falsifiable per key rather than inherited. Related: #14365 asks a different question about this same key (its z.record input type demands all five operations); that is a declaration defect, not a liveness one, and is not re-derived here. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); every CRUD route is mounted from the fixed method/path pairs in rest-server.ts#registerCrudEndpoints, which the client SDK, the discovery document and the served /openapi.json all describe, so a per-operation method/path knob was never enforceable without making those surfaces lie; `crud.dataPrefix` is the live knob that moves them, and an endpoint on a custom path or method is a declarative `api` endpoint (`type: 'object_operation'`). The four child rows this container carried (`method` / `path` / `summary` / `description` — the members of `CrudEndpointPatternSchema`) collapse into this one row: the tombstone is a leaf, the value def left the shape with it (RETIRED_DEFS_BY_MAJOR[18] `api/CrudEndpointPattern`), and rows for keys that left the walked shape would report ORPHAN. Closes the open question about the record's input type — there is no record left to reshape. Pre-retirement census note: 0 read sites at 2514d49f3. The `patterns` record is normalized into `this.config.crud.patterns` and never read: every CRUD route is mounted from the hard-coded method/path pairs in rest-server.ts#registerCrudEndpoints, so a custom pattern changes nothing. The four rows here are that one container's members — `patterns` is dead as a whole, and each member is recorded so the verdict is falsifiable per key rather than inherited. Related: a different question was open about this same key (its z.record input type demands all five operations); that is a declaration defect, not a liveness one, and is not re-derived here. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." }, "dataPrefix": { "status": "live", @@ -64,7 +64,7 @@ "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); every CRUD route takes the object name as a PATH segment, so `'query'` was validated (since #11984) and mounted exactly what `'path'` mounts; there is no second routing shape to select. Pre-retirement census note: 0 read sites at 2514d49f3. Repo-wide there are no reads outside packages/spec and rest-server.ts either. Every CRUD route takes the object name as a PATH segment; `'query'` is accepted, validated against the enum since #11984, and mounts exactly what `'path'` mounts. Enforcing it would be a second routing shape for every data route, which is why the call is a follow-up and not a ledger decision. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); every CRUD route takes the object name as a PATH segment, so `'query'` was validated (since #11984) and mounted exactly what `'path'` mounts; there is no second routing shape to select. Pre-retirement census note: 0 read sites at 2514d49f3. Repo-wide there are no reads outside packages/spec and rest-server.ts either. Every CRUD route takes the object name as a PATH segment; `'query'` is accepted, validated against the enum since #11984, and mounts exactly what `'path'` mounts. Enforcing it would be a second routing shape for every data route, which is why the call is a follow-up and not a ledger decision. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." } } } diff --git a/packages/spec/liveness/dashboard.json b/packages/spec/liveness/dashboard.json index c52cdd06127..87a2a068baf 100644 --- a/packages/spec/liveness/dashboard.json +++ b/packages/spec/liveness/dashboard.json @@ -1,6 +1,6 @@ { "type": "dashboard", - "_note": "DashboardSchema (UI, ADR-0021 dataset-bound). Live path: objectui DashboardView → DashboardRenderer → DatasetWidget. Seeded from docs/audits/2026-06-dashboardschema-property-liveness.md and re-verified against objectui HEAD — several audit-era findings are superseded: the ADR-0021 widget migration shipped (Studio WidgetConfigPanel + DashboardRenderer on dataset/dimensions/values, framework#3251; DashboardWidgetSchema is now `.strict()`); `globalFilters`/`dateRange` are LIVE (dashboard-level filters, framework#2501); the `title`↔`label` drift is fixed (renderer falls back to `label`, objectui#2806); the undeclared widget props were reconciled (#1894). objectui paths cited as prose in `note` (not `evidence`) on the dashboard-level entries; the widget children added in #4956 use the realm-marked `evidence` form (`objectui @91757a7: …`) that the gate can attribute. Framework provenance/lock fields auto-classify live (ADR-0010). 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent. 2026-08-03 (#4876): `widgets[].responsive` REMOVED — tombstoned (retiredKey) and stripped by the protocol-17 `dashboard-widget-responsive-removed` conversion. 2026-08-03 (#4956, landed after #4876): the widget subtree is DRILLED — `widgets.children` classifies all 22 authorable DashboardWidgetSchema keys. This SUPERSEDES two sentences that stood here. The first, for a release: 'Widget-level props are classified in the DashboardWidgetSchema subtree, not drilled here' — FALSE in the only way that mattered, because no such subtree existed in any ledger file, the walk drills one level and only through an explicit `children`, and `widgets` declared none, so all 22 keys sat outside the map while the gate printed green; `widgets[].responsive` survived the #3896 sweep on that gap alone, not on evidence. The second, from #4876 itself: that `responsive` deliberately carries NO row here because one would be an ORPHAN. That was correct only while `widgets` was undrilled — the retiredKey tombstone KEEPS the key in the walked shape, so now that the drill has landed the row is REQUIRED (omitting it reports UNCLASSIFIED), and it is present below with the dead verdict the sweep never got to record. The gate now refuses an undeclared container inheritance outright (scripts/liveness/drill.mts), so this class of claim cannot be re-asserted in prose. 2026-08-04 (#5011): `widgets[].compareTo` CONVERGED — the widget's three-arm vocabulary is replaced by a thin projection of the executor's own `DatasetSelection.compareTo` (`{ kind, dimension? }`), and the `{ offset }` arm retires via the `dashboard-widget-compareto-converged` conversion. Note what did NOT change: the verdict stays `live`. This was never a declared-but-unread key — the consumer existed the whole time; what was missing was agreement about what it consumes, which is a failure class this ledger had no vocabulary for until now and which its own `compareTo` row had to describe in a paragraph of prose. 2026-08-28 (#13003): the four `path:NNN` citations in this file were re-anchored to their consuming symbols; all four were wrong and all four were IN RANGE.", + "_note": "DashboardSchema (UI, ADR-0021 dataset-bound). Live path: objectui DashboardView → DashboardRenderer → DatasetWidget. Seeded from docs/audits/2026-06-dashboardschema-property-liveness.md and re-verified against objectui HEAD — several audit-era findings are superseded: the ADR-0021 widget migration shipped (Studio WidgetConfigPanel + DashboardRenderer on dataset/dimensions/values, framework#3251; DashboardWidgetSchema is now `.strict()`); `globalFilters`/`dateRange` are LIVE (dashboard-level filters, framework#2501); the `title`↔`label` drift is fixed (renderer falls back to `label`, objectui#2806); the undeclared widget props were reconciled (#1894). objectui paths cited as prose in `note` (not `evidence`) on the dashboard-level entries; the widget children added in #4956 use the realm-marked `evidence` form (`objectui @91757a7: …`) that the gate can attribute. Framework provenance/lock fields auto-classify live (ADR-0010). 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent. 2026-08-03 (#4876): `widgets[].responsive` REMOVED — tombstoned (retiredKey) and stripped by the protocol-17 `dashboard-widget-responsive-removed` conversion. 2026-08-03 (#4956, landed after #4876): the widget subtree is DRILLED — `widgets.children` classifies all 22 authorable DashboardWidgetSchema keys. This SUPERSEDES two sentences that stood here. The first, for a release: 'Widget-level props are classified in the DashboardWidgetSchema subtree, not drilled here' — FALSE in the only way that mattered, because no such subtree existed in any ledger file, the walk drills one level and only through an explicit `children`, and `widgets` declared none, so all 22 keys sat outside the map while the gate printed green; `widgets[].responsive` survived the #3896 sweep on that gap alone, not on evidence. The second, from #4876 itself: that `responsive` deliberately carries NO row here because one would be an ORPHAN. That was correct only while `widgets` was undrilled — the retiredKey tombstone KEEPS the key in the walked shape, so now that the drill has landed the row is REQUIRED (omitting it reports UNCLASSIFIED), and it is present below with the dead verdict the sweep never got to record. The gate now refuses an undeclared container inheritance outright (scripts/liveness/drill.mts), so this class of claim cannot be re-asserted in prose. 2026-08-04 (#5011): `widgets[].compareTo` CONVERGED — the widget's three-arm vocabulary is replaced by a thin projection of the executor's own `DatasetSelection.compareTo` (`{ kind, dimension? }`), and the `{ offset }` arm retires via the `dashboard-widget-compareto-converged` conversion. Note what did NOT change: the verdict stays `live`. This was never a declared-but-unread key — the consumer existed the whole time; what was missing was agreement about what it consumes, which is a failure class this ledger had no vocabulary for until now and which its own `compareTo` row had to describe in a paragraph of prose. 2026-08-28 (commit 8f10a79f7): the four `path:NNN` citations in this file were re-anchored to their consuming symbols; all four were wrong and all four were IN RANGE.", "props": { "name": { "status": "live", @@ -164,7 +164,7 @@ "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/rest/src/meta-item-read-gate.ts#filterDashboardForUser (ADR-0057 D10 — the widget is stripped from the payload when the named kernel service is not registered; fail-open when the kernel cannot be probed); packages/rest/src/rest.test.ts#filterDashboardForUser (the pin: drops widgets whose gate reports the service absent, keeps them when it is present)", - "note": "server-side capability gate: the widget is stripped from the payload when the named kernel service is not registered (fail-open when the kernel cannot be probed). The authoritative half of the pair — read it as the counter-example to judging a widget key from the renderer repo alone. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, both legs — `rest-server.ts:1921-1931` had rotted onto the auth-gate normalization comment in `computeExecCtx`, ~380 lines above the widget gate, and `rest.test.ts:3271-3305` onto a NAV-level case (\"ITEM level: nav entries the caller cannot satisfy are stripped\") ~600 lines above the widget suite — a different gate's tests, still in range and still plausible. Re-closed by hand against 8cb96ec41." + "note": "server-side capability gate: the widget is stripped from the payload when the named kernel service is not registered (fail-open when the kernel cannot be probed). The authoritative half of the pair — read it as the counter-example to judging a widget key from the renderer repo alone. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED, both legs — `rest-server.ts:1921-1931` had rotted onto the auth-gate normalization comment in `computeExecCtx`, ~380 lines above the widget gate, and `rest.test.ts:3271-3305` onto a NAV-level case (\"ITEM level: nav entries the caller cannot satisfy are stripped\") ~600 lines above the widget suite — a different gate's tests, still in range and still plausible. Re-closed by hand against 8cb96ec41." }, "actionUrl": { "status": "dead", @@ -227,13 +227,13 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "objectui @91757a7: packages/core/src/utils/dashboard-filters.ts:343 (resolveBoundField), :380-386 (explicit binding honoured, unbound reported); framework: packages/lint/src/validate-widget-bindings.ts#effectiveFilterField", - "note": "per-widget binding of a dashboard-level filter to one of this widget's fields, or `false` to opt out (framework#2501). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED on the framework leg — `:280` had rotted onto the closing brace of `dashboardFilterDefs`; the reader is ten lines below it in `effectiveFilterField`, where a string re-targets, `false` opts out, and both win over the legacy `targetWidgets` allow-list. Re-closed by hand against 8cb96ec41." + "note": "per-widget binding of a dashboard-level filter to one of this widget's fields, or `false` to opt out (framework#2501). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED on the framework leg — `:280` had rotted onto the closing brace of `dashboardFilterDefs`; the reader is ten lines below it in `effectiveFilterField`, where a string re-targets, `false` opts out, and both win over the legacy `targetWidgets` allow-list. Re-closed by hand against 8cb96ec41." }, "suppressWarnings": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/lint/src/validate-widget-bindings.ts#validateWidgetBindings (the per-widget `suppressed(rule)` closure — `Array.isArray(w.suppressWarnings) && w.suppressWarnings.includes(rule)` — consulted by every WARNING-severity diagnostic in this rule set; errors are deliberately not suppressible)", - "note": "build-time only, and that IS its contract — the key exists to silence a named build diagnostic, so the lint reading it is the whole feature (contrast `actionUrl`, where a lint reads the key but the promised runtime affordance does not exist). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:367` had rotted onto a closing brace in the measure-aggregate branch, ~18 lines past the read. Recorded because it is a class this batch met four times: the four rule-id positions the old citation listed (`:346, :417, :610, :640`) were BARE line suffixes with no path in front of them, so `PATH_RE` never matched them and they were prose no check has ever resolved, bounded or key-checked. Re-closed by hand against 8cb96ec41." + "note": "build-time only, and that IS its contract — the key exists to silence a named build diagnostic, so the lint reading it is the whole feature (contrast `actionUrl`, where a lint reads the key but the promised runtime affordance does not exist). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:367` had rotted onto a closing brace in the measure-aggregate branch, ~18 lines past the read. Recorded because it is a class this batch met four times: the four rule-id positions the old citation listed (`:346, :417, :610, :640`) were BARE line suffixes with no path in front of them, so `PATH_RE` never matched them and they were prose no check has ever resolved, bounded or key-checked. Re-closed by hand against 8cb96ec41." }, "responsive": { "status": "dead", diff --git a/packages/spec/liveness/datasource.json b/packages/spec/liveness/datasource.json index c566a485377..360ed5a1011 100644 --- a/packages/spec/liveness/datasource.json +++ b/packages/spec/liveness/datasource.json @@ -68,7 +68,7 @@ "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)", + "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, commit d70428ae7)", "note": "2026-08-28: RE-ANCHORED (commit 9ee2dcfbd) and REPOINTED — see the block note." }, "ca": { diff --git a/packages/spec/liveness/doc.json b/packages/spec/liveness/doc.json index a1f973f437f..039268409e3 100644 --- a/packages/spec/liveness/doc.json +++ b/packages/spec/liveness/doc.json @@ -1,54 +1,54 @@ { "type": "doc", - "_note": "DocSchema (ADR-0046 flat Markdown package docs). Fully live. The schema header calls docs 'inert data' — true of the KERNEL (it stores `content` unparsed), but every property has a real runtime consumer in the delivery layer: the REST read layer localizes, audience-gates and serves docs (`packages/rest/src/rest-server.ts`, inside `registerMetadataEndpointsInner` — the `doc` list branch, the `/meta/book/:name/tree` branch and the single-item branch), and the tree endpoint resolves book membership from doc headers via the spec's own `resolveBookTree` (packages/spec/src/system/book.zod.ts). objectui's console docs portal is a faithful port of the same resolver (apps/console/src/pages/book-nav.ts @940ba24) rendering the reader UI — a delivery surface for readers, NOT an authoring preview. Seeded 2026-08-01 (#4488). NOTE: `tags` was DECLARED in 17.0.0 (#4509, ADR-0049) — the enforce half of enforce-or-remove: the book-side `include: { tag }` rule and the REST corpus (`tags: d.tags`) both already expected the key, so declaring it made the previously-inert `{ tag }` include variant live; see the `tags` entry below for the fix's full history. 2026-08-28 (#13003): every citation in this file was re-anchored to its consuming symbol, and this file needed it most in the batch — ALL FIFTEEN of its line citations had rotted, because both cited files were reorganized under it. In `book.zod.ts` the pointers had come to rest inside the `ResolverDoc` / `ResolvedEntry` INTERFACES, i.e. on type declarations of the very fields whose consumers they claimed to cite; in `rest-server.ts` the entire doc-serving block moved ~1,600 lines down (the old 2,9xx-3,3xx pointers now land in the batch-endpoints and server-registration regions). Neither move is visible to the existence check, the line bound or the key-mention check.", + "_note": "DocSchema (ADR-0046 flat Markdown package docs). Fully live. The schema header calls docs 'inert data' — true of the KERNEL (it stores `content` unparsed), but every property has a real runtime consumer in the delivery layer: the REST read layer localizes, audience-gates and serves docs (`packages/rest/src/rest-server.ts`, inside `registerMetadataEndpointsInner` — the `doc` list branch, the `/meta/book/:name/tree` branch and the single-item branch), and the tree endpoint resolves book membership from doc headers via the spec's own `resolveBookTree` (packages/spec/src/system/book.zod.ts). objectui's console docs portal is a faithful port of the same resolver (apps/console/src/pages/book-nav.ts @940ba24) rendering the reader UI — a delivery surface for readers, NOT an authoring preview. Seeded 2026-08-01 (#4488). NOTE: `tags` was DECLARED in 17.0.0 (#4509, ADR-0049) — the enforce half of enforce-or-remove: the book-side `include: { tag }` rule and the REST corpus (`tags: d.tags`) both already expected the key, so declaring it made the previously-inert `{ tag }` include variant live; see the `tags` entry below for the fix's full history. 2026-08-28 (commit 8cb96ec41): every citation in this file was re-anchored to its consuming symbol, and this file needed it most in the batch — ALL FIFTEEN of its line citations had rotted, because both cited files were reorganized under it. In `book.zod.ts` the pointers had come to rest inside the `ResolverDoc` / `ResolvedEntry` INTERFACES, i.e. on type declarations of the very fields whose consumers they claimed to cite; in `rest-server.ts` the entire doc-serving block moved ~1,600 lines down (the old 2,9xx-3,3xx pointers now land in the batch-endpoints and server-registration regions). Neither move is visible to the existence check, the line bound or the key-mention check.", "props": { "name": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/spec/src/system/book.zod.ts#matchesInclude (`globToRegExp(include).test(doc.name)` — the glob `include` rule matches over names); packages/spec/src/system/book.zod.ts#entryFromDoc (`{ doc: doc.name, … }` — the name is what a rendered tree entry points at); packages/rest/src/meta-item-read-gate.ts#resolveDocAudiences (`docReader`: `audiences.get(docName as string)` over `resolveDocAudiences(books, corpus)` — the effective-audience map is keyed by name, asked with the doc's name on the list branch, the tree and the item read)", - "note": "identity: the single-doc route key, the resolver's membership key (glob `include` matches over names), and the audience map key. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — BOTH legs were wrong. `book.zod.ts:225` is a BLANK LINE inside the `ResolvedBook` interface; `rest-server.ts:3129` lands on `upsertMany: batch.operations?.upsertMany ?? true` in the batch-endpoints config, ~1,480 lines from any doc read. Re-closed by hand against 93ea19bca." + "note": "identity: the single-doc route key, the resolver's membership key (glob `include` matches over names), and the audience map key. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — BOTH legs were wrong. `book.zod.ts:225` is a BLANK LINE inside the `ResolvedBook` interface; `rest-server.ts:3129` lands on `upsertMany: batch.operations?.upsertMany ?? true` in the batch-endpoints config, ~1,480 lines from any doc read. Re-closed by hand against 93ea19bca." }, "label": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/book.zod.ts#byOrderThenLabel (`(a.label ?? a.name).localeCompare(b.label ?? b.name)` — the order tiebreak); packages/spec/src/system/book.zod.ts#entryFromDoc (`label: doc.label` onto the rendered entry); packages/spec/src/system/doc.zod.ts#resolveDocLocale (`label: variant.label ?? base.label` — the per-locale swap, with per-field fallback to the base doc)", - "note": "tree entry title + the order tiebreak sort key (byOrderThenLabel). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — both legs were wrong and both were wrong in the same instructive way: `book.zod.ts:198` is `description?: string;` and `:202` is the `tags` docblock, i.e. two INTERFACE FIELD DECLARATIONS, one of them another key's. A field declaration is the most convincing wrong place a citation can land — the key is right there, in the right file, spelled correctly — and it proves nothing at all, because a declaration is what a `dead` key has too. Re-closed by hand against 93ea19bca." + "note": "tree entry title + the order tiebreak sort key (byOrderThenLabel). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — both legs were wrong and both were wrong in the same instructive way: `book.zod.ts:198` is `description?: string;` and `:202` is the `tags` docblock, i.e. two INTERFACE FIELD DECLARATIONS, one of them another key's. A field declaration is the most convincing wrong place a citation can land — the key is right there, in the right file, spelled correctly — and it proves nothing at all, because a declaration is what a `dead` key has too. Re-closed by hand against 93ea19bca." }, "description": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/spec/src/system/book.zod.ts#entryFromDoc (`description: doc.description` — carried onto the rendered tree entry); packages/spec/src/system/doc.zod.ts#resolveDocLocale (`description: variant.description ?? base.description`); packages/rest/src/meta-item-read-gate.ts#createMetaBookTreeAnswer (the tree route's corpus projection `{ name, label, description, order, group, tags, packageId }` — the transport half that reads the key — handed to `audience.readableTree(book, docs)`, whose `resolveBookTree` carries it onto the rendered entry)", - "note": "carried into tree entries and kept on the list response (which strips `content`) so portals can show summaries without fetching bodies. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:202` is the `tags` docblock (a DIFFERENT key's documentation) and `rest-server.ts:3131` is `defaultAtomic: batch.defaultAtomic ?? true` in the batch-endpoints config. Re-closed by hand against 93ea19bca." + "note": "carried into tree entries and kept on the list response (which strips `content`) so portals can show summaries without fetching bodies. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:202` is the `tags` docblock (a DIFFERENT key's documentation) and `rest-server.ts:3131` is `defaultAtomic: batch.defaultAtomic ?? true` in the batch-endpoints config. Re-closed by hand against 93ea19bca." }, "content": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/doc.zod.ts#resolveDocLocale (`content: variant.content` — the locale swap, and the ONLY field with no per-field fallback: a variant that omits it yields undefined by design); packages/rest/src/rest-server.ts#registerMetadataEndpointsInner (the `doc` list branch destructures `const { content: _content, ...rest } = it` unless `?include=content`; the single-item branch returns the collapsed doc whole)", - "note": "the document body: served whole on single-doc GET, deliberately stripped from list responses unless `?include=content`, locale-swapped by resolveDocLocale. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `rest-server.ts:3007` had rotted onto a docblock about `enableProjectScoping`, and `doc.zod.ts:120` onto the middle of the `tags` docblock — the sentence \"the consumer already existed, so this is the enforce half of enforce-or-remove\", which is about `tags`, cited as evidence for `content`. HONEST RESIDUAL on the rest-server leg: the `?include=content` strip is inline in a ~2,800-line route registrar with no enclosing named helper, so the anchor is the registrar itself. That is coarse, and it is stated rather than dressed up — the narrow half of this entry is the `doc.zod.ts` anchor, which is exact. Re-closed by hand against 93ea19bca." + "note": "the document body: served whole on single-doc GET, deliberately stripped from list responses unless `?include=content`, locale-swapped by resolveDocLocale. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `rest-server.ts:3007` had rotted onto a docblock about `enableProjectScoping`, and `doc.zod.ts:120` onto the middle of the `tags` docblock — the sentence \"the consumer already existed, so this is the enforce half of enforce-or-remove\", which is about `tags`, cited as evidence for `content`. HONEST RESIDUAL on the rest-server leg: the `?include=content` strip is inline in a ~2,800-line route registrar with no enclosing named helper, so the anchor is the registrar itself. That is coarse, and it is stated rather than dressed up — the narrow half of this entry is the `doc.zod.ts` anchor, which is exact. Re-closed by hand against 93ea19bca." }, "order": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/spec/src/system/book.zod.ts#byOrderThenLabel (`(a.order ?? 0) - (b.order ?? 0)` — the primary sort, absent reads as 0); packages/rest/src/meta-item-read-gate.ts#docCorpusOf (`order: d.order` in the list-branch corpus projection the audience resolver reads)", - "note": "sort key within a book group (0 when absent, then label). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:198` is `description?: string;` in the `ResolverDoc` interface: a citation for `order` landing on the DECLARATION of a different key. `order`'s own declaration is the next line but one, which is how close a wrong pointer can sit to a right one and still say nothing. Re-closed by hand against 93ea19bca." + "note": "sort key within a book group (0 when absent, then label). 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:198` is `description?: string;` in the `ResolverDoc` interface: a citation for `order` landing on the DECLARATION of a different key. `order`'s own declaration is the next line but one, which is how close a wrong pointer can sit to a right one and still say nothing. Re-closed by hand against 93ea19bca." }, "group": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/spec/src/system/book.zod.ts#resolveBookTree (a doc joins the first group whose `include` matches it OR whose `key` equals `doc.group`); packages/rest/src/meta-item-read-gate.ts#docCorpusOf (`group: d.group` in the list-branch corpus projection the audience resolver reads)", - "note": "explicit book-group membership; a doc joins the group whose `key` equals it when no `include` rule claims it first. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:238` had rotted onto the docblock of `ResolvedEntrySchema` (a sentence about #12038's describe-only transcription), ~63 lines above `resolveBookTree`. Re-closed by hand against 93ea19bca." + "note": "explicit book-group membership; a doc joins the group whose `key` equals it when no `include` rule claims it first. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:238` had rotted onto the docblock of `ResolvedEntrySchema` (a sentence about #12038's describe-only transcription), ~63 lines above `resolveBookTree`. Re-closed by hand against 93ea19bca." }, "translations": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/doc.zod.ts#resolveDocLocale (`const { translations, ...base } = doc` then `translations[want] ?? translations[want.split('-')[0]]` — exact locale, then primary subtag, then the base doc; the map itself never survives into the return value); packages/rest/src/meta-item-read-gate.ts#resolveDocLocale (called on every doc read path — the list branch maps it over the items, the tree branch over the corpus, the single-item branch over the one doc)", - "note": "per-locale {label,description,content} variants collapsed by resolveDocLocale on every read path (list, tree corpus, single item); the map itself is stripped from responses. This is the doc's OWN i18n mechanism — the generic bundle translator does not cover `doc`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — all three legs were wrong: `doc.zod.ts:110` is the `/**` that OPENS the `tags` docblock, and `rest-server.ts:2996` / `:3388` are comments about `os serve` config and `api.version` respectively. The rest-server leg is anchored to the imported symbol it calls rather than to the registrar, because that is what actually has to survive for this claim to hold: drop the import and the three call sites go with it, and the anchor reds. Re-closed by hand against 93ea19bca." + "note": "per-locale {label,description,content} variants collapsed by resolveDocLocale on every read path (list, tree corpus, single item); the map itself is stripped from responses. This is the doc's OWN i18n mechanism — the generic bundle translator does not cover `doc`. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — all three legs were wrong: `doc.zod.ts:110` is the `/**` that OPENS the `tags` docblock, and `rest-server.ts:2996` / `:3388` are comments about `os serve` config and `api.version` respectively. The rest-server leg is anchored to the imported symbol it calls rather than to the registrar, because that is what actually has to survive for this claim to hold: drop the import and the three call sites go with it, and the anchor reds. Re-closed by hand against 93ea19bca." }, "tags": { "status": "live", "verifiedAt": "2026-09-27", "evidence": "packages/spec/src/system/book.zod.ts#matchesInclude (`Array.isArray(doc.tags) && doc.tags.includes(include.tag)` — the tag branch of `include`); packages/spec/src/system/book.zod.ts#resolveBookTree (the caller that decides membership from it); packages/rest/src/meta-item-read-gate.ts#createMetaBookTreeAnswer (`tags: d.tags` in the tree corpus the route hands `audience.readableTree(book, docs)` — the transport half); packages/rest/src/meta-item-read-gate.ts#resolveBookTree (`resolveBookTree(book, docs, book._packageId)` — where that corpus reaches the resolver)", - "note": "DECLARED 2026-08-02 (#4509, ADR-0049) — the enforce half of enforce-or-remove. The consumer, the transport and the resolver-side interface all predated this key: matchesInclude compared `doc.tags` against a group's `include: { tag }`, the book route already forwarded `tags: d.tags`, and ResolverDoc declared `tags?: string[]` marked '(P3d; absent today)'. What was missing was one line HERE — DocSchema is strict, so authoring `tags:` was a parse error, every doc reached the resolver with tags undefined, and the `{ tag }` include variant could never match. Removing the variant was the alternative and was rejected: a union member has no clean tombstone (retiredKey covers object keys), so authors would have gotten a bare union error, and it would have discarded working matcher code. Live on arrival — the branch it feeds is reachable the moment a doc carries a tag. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `book.zod.ts:213` is `description?: string;` in the `ResolvedEntry` interface, and the parenthetical claiming it was \"matchesInclude tag branch\" named the right function ~61 lines away; the \"reached from :255 and :281\" hops were prose the ledger never bounded. `rest-server.ts:3218` had rotted onto `this.registerSecurityEndpoints(bp)` — a route-registration call, ~1,700 lines from the corpus that forwards the tags. Re-closed by hand against 93ea19bca." + "note": "DECLARED 2026-08-02 (#4509, ADR-0049) — the enforce half of enforce-or-remove. The consumer, the transport and the resolver-side interface all predated this key: matchesInclude compared `doc.tags` against a group's `include: { tag }`, the book route already forwarded `tags: d.tags`, and ResolverDoc declared `tags?: string[]` marked '(P3d; absent today)'. What was missing was one line HERE — DocSchema is strict, so authoring `tags:` was a parse error, every doc reached the resolver with tags undefined, and the `{ tag }` include variant could never match. Removing the variant was the alternative and was rejected: a union member has no clean tombstone (retiredKey covers object keys), so authors would have gotten a bare union error, and it would have discarded working matcher code. Live on arrival — the branch it feeds is reachable the moment a doc carries a tag. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `book.zod.ts:213` is `description?: string;` in the `ResolvedEntry` interface, and the parenthetical claiming it was \"matchesInclude tag branch\" named the right function ~61 lines away; the \"reached from :255 and :281\" hops were prose the ledger never bounded. `rest-server.ts:3218` had rotted onto `this.registerSecurityEndpoints(bp)` — a route-registration call, ~1,700 lines from the corpus that forwards the tags. Re-closed by hand against 93ea19bca." } } } diff --git a/packages/spec/liveness/flow.json b/packages/spec/liveness/flow.json index 5d9d77ae13c..e76994f62f6 100644 --- a/packages/spec/liveness/flow.json +++ b/packages/spec/liveness/flow.json @@ -1,6 +1,6 @@ { "type": "flow", - "_note": "FlowSchema. Seeded from docs/audits/2026-06-flowschema-property-liveness.md. Consumers: service-automation engine + node executors. runAs is marker-driven experimental (spec). CORRECTED 2026-07: the previous note claimed 'status/active gate nothing' — that was true when the audit ran but became FALSE with commit 497bda853 (2026-07-05, 'honor flow status for enable/disable'). `status` now gates binding AND execution; `active` remains a deprecated no-op. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent. 2026-08-28 (#13003): the four `path:NNN` citations in this file were re-anchored to their consuming symbols. All four were wrong, all four IN RANGE, and `status` also carried three bare line suffixes with no path that no check has ever resolved.", + "_note": "FlowSchema. Seeded from docs/audits/2026-06-flowschema-property-liveness.md. Consumers: service-automation engine + node executors. runAs is marker-driven experimental (spec). CORRECTED 2026-07: the previous note claimed 'status/active gate nothing' — that was true when the audit ran but became FALSE with commit 497bda853 (2026-07-05, 'honor flow status for enable/disable'). `status` now gates binding AND execution; `active` remains a deprecated no-op. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent. 2026-08-28 (commit 8f10a79f7): the four `path:NNN` citations in this file were re-anchored to their consuming symbols. All four were wrong, all four IN RANGE, and `status` also carried three bare line suffixes with no path that no check has ever resolved.", "props": { "name": { "status": "live", @@ -10,13 +10,13 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-automation/src/engine.ts#executeWithoutRetry (`successMessage: flow.successMessage` on the terminal success envelope — deliberately not a per-attempt value); packages/services/service-automation/src/engine.ts#resumeInternal (the resumed run returns the same authored string)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1292` had rotted onto a docblock sentence about `'runtime'` package provenance, ~2,450 lines above the readers. The wrapping `execute` path returns the same field and is named here as prose rather than anchored, because `execute` is too common a word in this file for an anchor to falsify anything. Re-closed by hand against 8cb96ec41." + "note": "2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:1292` had rotted onto a docblock sentence about `'runtime'` package provenance, ~2,450 lines above the readers. The wrapping `execute` path returns the same field and is named here as prose rather than anchored, because `execute` is too common a word in this file for an anchor to falsify anything. Re-closed by hand against 8cb96ec41." }, "errorMessage": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-automation/src/engine.ts#retryExecution (carries the authored text onto the last attempt's failure); packages/services/service-automation/src/engine.ts#resumeInternal (`errorMessage: flow.errorMessage` on the resumed run's failure envelope)", - "note": "2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1348` had rotted onto a docblock heading (\"⛔ This is NOT the retired `flowEnabled` map under a new name\"), ~2,480 lines above the readers. As with `successMessage`, the top-level `execute` path reads the same field and is left as prose for the same reason. Re-closed by hand against 8cb96ec41." + "note": "2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:1348` had rotted onto a docblock heading (\"⛔ This is NOT the retired `flowEnabled` map under a new name\"), ~2,480 lines above the readers. As with `successMessage`, the top-level `execute` path reads the same field and is left as prose for the same reason. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", @@ -38,7 +38,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-automation/src/engine.ts#registerFlow (`this.flowStatusDisabled.set(name, flowStatus === 'obsolete' || flowStatus === 'invalid')` — the authoring state is read ONCE, at registration); packages/services/service-automation/src/engine.ts#isFlowEnabled (composes that bit with the durable ledger bit; every trigger and execute path asks this one predicate)", - "note": "RE-VERIFIED 2026-07 (#3686 preview-claim sweep): the prior `live` verdict cited only a metadata-admin PREVIEW panel, which echoes what the author typed. Verdict stands, evidence replaced: it is genuinely engine-consumed since 497bda853. The previously cited FlowPreview line number did not even contain a status read. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the sharpest in-range rot in this batch. The parsable half of the citation, `:1382`, had come to rest on `private flowLedgerDisabled = new Set();` — the ADJACENT map, not this key's — while #10243 had meanwhile SPLIT the single `flowEnabled` map into `flowStatusDisabled` (this key) and `flowLedgerDisabled` (the durable toggle), composed by `isFlowEnabled`. A pointer landing on a plausible line in the right class is the failure mode a line bound cannot reach. The other three positions (`:1387`, `:1704`, `:3097`) were bare line suffixes with no path and resolved to nothing at all. Re-closed by hand against 8cb96ec41." + "note": "RE-VERIFIED 2026-07 (#3686 preview-claim sweep): the prior `live` verdict cited only a metadata-admin PREVIEW panel, which echoes what the author typed. Verdict stands, evidence replaced: it is genuinely engine-consumed since 497bda853. The previously cited FlowPreview line number did not even contain a status read. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — the sharpest in-range rot in this batch. The parsable half of the citation, `:1382`, had come to rest on `private flowLedgerDisabled = new Set();` — the ADJACENT map, not this key's — while commit 15249270f had meanwhile SPLIT the single `flowEnabled` map into `flowStatusDisabled` (this key) and `flowLedgerDisabled` (the durable toggle), composed by `isFlowEnabled`. A pointer landing on a plausible line in the right class is the failure mode a line bound cannot reach. The other three positions (`:1387`, `:1704`, `:3097`) were bare line suffixes with no path and resolved to nothing at all. Re-closed by hand against 8cb96ec41." }, "runAs": { "status": "live", @@ -50,7 +50,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/services/service-automation/src/engine.ts#resolveTriggerBinding (`flow.type === 'schedule'` and `flow.type === 'api'` decide which trigger the flow is bound to)", - "note": "flow.type==='schedule'/'api' drives trigger dispatch. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:637` had rotted onto a docblock about the unknown-node-type audit being structured rather than logged, ~1,430 lines above the dispatch. Re-closed by hand against 8cb96ec41." + "note": "flow.type==='schedule'/'api' drives trigger dispatch. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:637` had rotted onto a docblock about the unknown-node-type audit being structured rather than logged, ~1,430 lines above the dispatch. Re-closed by hand against 8cb96ec41." }, "variables": { "status": "live", diff --git a/packages/spec/liveness/hook.json b/packages/spec/liveness/hook.json index 767b5128801..e1367e014ca 100644 --- a/packages/spec/liveness/hook.json +++ b/packages/spec/liveness/hook.json @@ -1,24 +1,24 @@ { "type": "hook", - "_note": "HookSchema. Seeded from docs/audits/2026-06-hookschema-property-liveness.md — a model-healthy schema (near-total liveness). Consumers: objectql hook-binder/engine + runtime sandbox. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): the two `dead` rows here (`label`, `description`) are the docs-shaped precedent the rest of this ledger cites, so the maintainer's 2026-08-10 previews ruling (#7131 — see README, 'Designer previews count as consumers') was applied to them mechanically, and the answer is an ABSENCE. There is NO registered metadata-admin preview for `hook` at objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts registers 22 types and `hook` is not among them. Recorded rather than skipped, because 'the type has no registered preview' is the sentence the README asks for and the one `translation.label`'s superseded note was missing. Both verdicts stand unchanged; the ruling gives them nothing to re-litigate. 2026-08-28 (#13003): all eight `path:NNN` citations in this file were re-anchored. The five `evidence` pointers into `hook-binder.ts` were all wrong and all IN RANGE — two keys even shared one wrong line — while the three `producer` pointers at `:221` were accurate and are a straight grammar migration. The declarative trio's `evidence` was a bare path with no line, which no line bound can falsify; those are anchors now. 2026-09-29 (#20299): `label` and `description` are RE-GRADED dead → live. The 2026-08-10 lookup above still reads true, since `hook` still has no registered preview at the `.objectui-sha` pin dd3f7e1be, but it looked at previews only. The Studio metadata list and the metadata quick-find draw both keys for every hook, and a booted read of `GET /api/v1/meta/hook` serves them; the rows carry the reader, the producer and the measurement. They are still KEPT, docs-shaped and not authorWarn'd.", + "_note": "HookSchema. Seeded from docs/audits/2026-06-hookschema-property-liveness.md — a model-healthy schema (near-total liveness). Consumers: objectql hook-binder/engine + runtime sandbox. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): the two `dead` rows here (`label`, `description`) are the docs-shaped precedent the rest of this ledger cites, so the maintainer's 2026-08-10 previews ruling (#7131 — see README, 'Designer previews count as consumers') was applied to them mechanically, and the answer is an ABSENCE. There is NO registered metadata-admin preview for `hook` at objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts registers 22 types and `hook` is not among them. Recorded rather than skipped, because 'the type has no registered preview' is the sentence the README asks for and the one `translation.label`'s superseded note was missing. Both verdicts stand unchanged; the ruling gives them nothing to re-litigate. 2026-08-28 (commit 8f10a79f7): all eight `path:NNN` citations in this file were re-anchored. The five `evidence` pointers into `hook-binder.ts` were all wrong and all IN RANGE — two keys even shared one wrong line — while the three `producer` pointers at `:221` were accurate and are a straight grammar migration. The declarative trio's `evidence` was a bare path with no line, which no line bound can falsify; those are anchors now. 2026-09-29 (#20299): `label` and `description` are RE-GRADED dead → live. The 2026-08-10 lookup above still reads true, since `hook` still has no registered preview at the `.objectui-sha` pin dd3f7e1be, but it looked at previews only. The Studio metadata list and the metadata quick-find draw both keys for every hook, and a booted read of `GET /api/v1/meta/hook` serves them; the rows carry the reader, the producer and the measurement. They are still KEPT, docs-shaped and not authorWarn'd.", "props": { "name": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (`hook: hook.name` on every skip and error record, `hookName: hook.name` on the registration itself — the binding key and the log identity)", - "note": "registration key + log identity. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:182` landed on the string literal `'no handler';`. That is INSIDE the right function, but it names a fragment of a diagnostic rather than any read of this key, so the pointer was true about the file and false about the code. Re-closed by hand against 8cb96ec41." + "note": "registration key + log identity. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:182` landed on the string literal `'no handler';`. That is INSIDE the right function, but it names a fragment of a diagnostic rather than any read of this key, so the pointer was true about the file and false about the code. Re-closed by hand against 8cb96ec41." }, "object": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (`const objects = normalizeObjects(hook.object)`, then one engine registration per object × event); packages/objectql/src/hook-binder.ts#normalizeObjects (single name, array, or the `*` wildcard)", - "note": "single/array/'*' wildcard honored. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:171` had rotted onto a bare closing brace above the binder's main loop, and it was the SAME line `events` cited, so two keys shared one wrong pointer. Re-closed by hand against 8cb96ec41." + "note": "single/array/'*' wildcard honored. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:171` had rotted onto a bare closing brace above the binder's main loop, and it was the SAME line `events` cited, so two keys shared one wrong pointer. Re-closed by hand against 8cb96ec41." }, "events": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (`const events = Array.isArray(hook.events) ? hook.events : []`, then one engine registration per event)", - "note": "per-event registration. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:171` had rotted onto a bare closing brace; see `object`, which cited the same line. Re-closed by hand against 8cb96ec41." + "note": "per-event registration. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:171` had rotted onto a bare closing brace; see `object`, which cited the same line. Re-closed by hand against 8cb96ec41." }, "body": { "status": "live", @@ -29,13 +29,13 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/hook-binder.ts#resolveHandler (`const h = hook.handler` — resolved against the engine's registered functions); packages/objectql/src/hook-binder.ts#bindHooksToEngine (the legacy-handler warning, emitted when a hook declares a handler string and no `body`)", - "note": "deprecated but fully wired; body takes precedence. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:237` had rotted onto a closing brace at the end of the per-event registration loop, ~76 lines above `resolveHandler`. Re-closed by hand against 8cb96ec41." + "note": "deprecated but fully wired; body takes precedence. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:237` had rotted onto a closing brace at the end of the per-event registration loop, ~76 lines above `resolveHandler`. Re-closed by hand against 8cb96ec41." }, "priority": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (`priority: typeof hook.priority === 'number' ? hook.priority : 100` — handed to the engine registration, lower first)", - "note": "genuinely orders hooks (lower first). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:177` had rotted onto `result.skipped += 1;`, the unresolved-handler counter. Re-closed by hand against 8cb96ec41." + "note": "genuinely orders hooks (lower first). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:177` had rotted onto `result.skipped += 1;`, the unresolved-handler counter. Re-closed by hand against 8cb96ec41." }, "async": { "status": "live", @@ -53,7 +53,7 @@ "evidenceScope": "in-repo", "evidence": "packages/objectql/src/hook-wrappers.ts#wrapDeclarativeHook (`meta.retryPolicy?.maxRetries` / `?.backoffMs`, with `retryPolicyDefaults()` supplying the schema's own numbers when the block is present but partial — the absence of the block is not the same as an empty one)", "producer": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (`wrapDeclarativeHook(hook, resolved, { logger, metrics })` — the binder hands the AUTHORED hook to the wrapper, so the wrapper reads the author's block rather than a caller-built options object)", - "note": "{maxRetries,backoffMs} linear backoff. Producer-side re-verified 2026-08-09 (#4837 slice): the whole declarative group (condition / async / retryPolicy / timeout / onError) is fed from one call site, so one producer pointer covers it — this is the shape `Seed.env` failed, checked and passing here. 2026-08-28: RE-ANCHORED (#13003) — the PRODUCER citation was ACCURATE (`:221` is still the `wrapDeclarativeHook` call), so that half is the grammar migration. What this pass repairs is the EVIDENCE half, which was a bare path with no line at all and therefore unfalsifiable by the line bound BY CONSTRUCTION — the same silent class `hook.timeout` and `hook.onError` carried. Re-closed by hand against 8cb96ec41." + "note": "{maxRetries,backoffMs} linear backoff. Producer-side re-verified 2026-08-09 (#4837 slice): the whole declarative group (condition / async / retryPolicy / timeout / onError) is fed from one call site, so one producer pointer covers it — this is the shape `Seed.env` failed, checked and passing here. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the PRODUCER citation was ACCURATE (`:221` is still the `wrapDeclarativeHook` call), so that half is the grammar migration. What this pass repairs is the EVIDENCE half, which was a bare path with no line at all and therefore unfalsifiable by the line bound BY CONSTRUCTION — the same silent class `hook.timeout` and `hook.onError` carried. Re-closed by hand against 8cb96ec41." }, "timeoutMs": { "status": "live", @@ -61,7 +61,7 @@ "evidenceScope": "in-repo", "evidence": "packages/objectql/src/hook-wrappers.ts#wrapDeclarativeHook (step 4 of the wrapper's ladder — a wall-clock abort around the handler, read from `meta.timeoutMs`; independent of `body.timeoutMs`)", "producer": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (the same `wrapDeclarativeHook` call as retryPolicy)", - "note": "wall-clock abort, independent of body.timeoutMs. RENAMED 2026-09-04 (#14478) from `timeout`: the unit lived only in the describe while the body-level `timeoutMs` and `retryPolicy.backoffMs` spelled theirs; the reader moved from `meta.timeout` to `meta.timeoutMs` in the same PR at the same magnitude. Anchors carried over from the `timeout` row (re-anchored #13003)." + "note": "wall-clock abort, independent of body.timeoutMs. RENAMED 2026-09-04 (#14478) from `timeout`: the unit lived only in the describe while the body-level `timeoutMs` and `retryPolicy.backoffMs` spelled theirs; the reader moved from `meta.timeout` to `meta.timeoutMs` in the same PR at the same magnitude. Anchors carried over from the `timeout` row (re-anchored in commit 8f10a79f7)." }, "timeout": { "status": "dead", @@ -74,7 +74,7 @@ "evidenceScope": "in-repo", "evidence": "packages/objectql/src/hook-wrappers.ts#wrapDeclarativeHook (step 5 of the wrapper's ladder — `log` swallows and continues, `abort` rethrows; a `HookConditionError` deliberately does NOT reach it, since the condition decides whether there is a handler run at all)", "producer": "packages/objectql/src/hook-binder.ts#bindHooksToEngine (the same `wrapDeclarativeHook` call as retryPolicy)", - "note": "'log' suppresses+continues; 'abort' rethrows. 2026-08-28: RE-ANCHORED (#13003) — accurate producer line migrated; the path-only evidence pointer is now an anchor (see `retryPolicy`). Re-closed by hand against 8cb96ec41." + "note": "'log' suppresses+continues; 'abort' rethrows. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — accurate producer line migrated; the path-only evidence pointer is now an anchor (see `retryPolicy`). Re-closed by hand against 8cb96ec41." }, "runAs": { "status": "live", diff --git a/packages/spec/liveness/job.json b/packages/spec/liveness/job.json index 0d3988e7080..d27480e1e81 100644 --- a/packages/spec/liveness/job.json +++ b/packages/spec/liveness/job.json @@ -1,12 +1,12 @@ { "type": "job", - "_note": "JobSchema. The file-authored path is healthy: `defineStack({ jobs })` → `AppPlugin.start`'s `kernel:ready` hook → IJobService.schedule → the service-job adapters honor every schedule shape (`CronJobAdapter.schedule` / `DbJobAdapter.schedule`) and `runWithPolicy` enforces retryPolicy/timeout (#3494 — these used to be parsed-but-ignored). `retryPolicy` here is the ENFORCED spelling ({maxRetries, backoffMs, backoffMultiplier}); do not confuse it with the datasource `retryPolicy`, which is dead and spells its delay differently. TYPE-LEVEL GAP CLOSED 2026-08-02 (#4509) by CLOSING THE DOOR, not building a bridge: `job` was registered `allowRuntimeCreate: true` while only the compiled bundle's `jobs` ever reached the scheduler, so a Studio-created job saved cleanly and never ran. Unlike the webhook (#3461) and email_template (#4509 item 1) disconnects, this one could not be bridged: `handler` names a function in the compiled bundle's function table (`collectBundleFunctions`), which a runtime writer does not have and cannot name — the missing piece is a handler-binding design, not an ingestion path. So `allowRuntimeCreate` AND `allowOrgOverride` are now both false (metadata-plugin.zod.ts, with the rationale block), leaving `*.job.ts` / `defineStack({ jobs })` as the supported doors. The kind stays registered: its file loader is genuinely consumed, so it still passes the ADR-0088 admission test. Seeded 2026-08-01. 2026-08-28 (#13003): every LOCAL citation re-anchored to its consuming symbol — and this file is its own best argument for doing so. The 2026-08-02 note recorded that the SEEDED lines had already drifted ~25 lines and were restamped with fresh numbers; twenty-six days later every one of those fresh numbers had drifted again, this time by ~70-100 lines, onto `} else {`, `try {`, a bare `}` and a line about registering ACTIONS. Restamping is not a fix for line rot, it is the same claim with a newer date — which is the case this whole worklist rests on. The two objectui-cited entries (`label`, `description`) are left BYTE-FOR-BYTE UNTOUCHED: their evidence and producer are pinned at `objectui @aeb8424b`, a commit this container cannot reproduce, and foreign anchors are never collected by the scanner.", + "_note": "JobSchema. The file-authored path is healthy: `defineStack({ jobs })` → `AppPlugin.start`'s `kernel:ready` hook → IJobService.schedule → the service-job adapters honor every schedule shape (`CronJobAdapter.schedule` / `DbJobAdapter.schedule`) and `runWithPolicy` enforces retryPolicy/timeout (#3494 — these used to be parsed-but-ignored). `retryPolicy` here is the ENFORCED spelling ({maxRetries, backoffMs, backoffMultiplier}); do not confuse it with the datasource `retryPolicy`, which is dead and spells its delay differently. TYPE-LEVEL GAP CLOSED 2026-08-02 (#4509) by CLOSING THE DOOR, not building a bridge: `job` was registered `allowRuntimeCreate: true` while only the compiled bundle's `jobs` ever reached the scheduler, so a Studio-created job saved cleanly and never ran. Unlike the webhook (#3461) and email_template (#4509 item 1) disconnects, this one could not be bridged: `handler` names a function in the compiled bundle's function table (`collectBundleFunctions`), which a runtime writer does not have and cannot name — the missing piece is a handler-binding design, not an ingestion path. So `allowRuntimeCreate` AND `allowOrgOverride` are now both false (metadata-plugin.zod.ts, with the rationale block), leaving `*.job.ts` / `defineStack({ jobs })` as the supported doors. The kind stays registered: its file loader is genuinely consumed, so it still passes the ADR-0088 admission test. Seeded 2026-08-01. 2026-08-28 (commit 8cb96ec41): every LOCAL citation re-anchored to its consuming symbol — and this file is its own best argument for doing so. The 2026-08-02 note recorded that the SEEDED lines had already drifted ~25 lines and were restamped with fresh numbers; twenty-six days later every one of those fresh numbers had drifted again, this time by ~70-100 lines, onto `} else {`, `try {`, a bare `}` and a line about registering ACTIONS. Restamping is not a fix for line rot, it is the same claim with a newer date — which is the case this whole worklist rests on. The two objectui-cited entries (`label`, `description`) are left BYTE-FOR-BYTE UNTOUCHED: their evidence and producer are pinned at `objectui @aeb8424b`, a commit this container cannot reproduce, and foreign anchors are never collected by the scanner.", "props": { "name": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/app-plugin.ts#start (`const jobName: string = job?.name` — a job without one is skipped with a warn before anything else is read, and the name is the scheduling key passed to `svc.schedule` plus the subject of every diagnostic in the loop)", - "note": "scheduling identity; a job without one is skipped loudly. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `app-plugin.ts:815` had rotted onto a bare `} else {` and `:833` onto a bare `try {`, ~98 and ~80 lines above the reads. HONEST RESIDUAL: the scheduling loop is inline in `AppPlugin.start`'s `kernel:ready` hook with no enclosing named helper, so `start` is the anchor — a weak one at text level, since almost any file contains the word. It is the true enclosing symbol and it is named as such rather than dressed up; the entries below that have a distinctive downstream consumer cite it beside `start` for exactly this reason. Re-closed by hand against 93ea19bca." + "note": "scheduling identity; a job without one is skipped loudly. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `app-plugin.ts:815` had rotted onto a bare `} else {` and `:833` onto a bare `try {`, ~98 and ~80 lines above the reads. HONEST RESIDUAL: the scheduling loop is inline in `AppPlugin.start`'s `kernel:ready` hook with no enclosing named helper, so `start` is the anchor — a weak one at text level, since almost any file contains the word. It is the true enclosing symbol and it is named as such rather than dressed up; the entries below that have a distinctive downstream consumer cite it beside `start` for exactly this reason. Re-closed by hand against 93ea19bca." }, "label": { "status": "live", @@ -28,19 +28,19 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/job-schedule.ts#toBoundaryJobSchedule (#4567 authoring tier → boundary tier: the parsed `Schedule`'s cron `expression` is the ADR expression envelope `{dialect,source}` and is lowered to the bare string the adapters take, refusing by name rather than scheduling a wrong shape); packages/runtime/src/app-plugin.ts#start (`toBoundaryJobSchedule(job.schedule, jobName)` — the call site, and `job.schedule` again in the FAILED-TO-SCHEDULE error); packages/services/service-job/src/cron-job-adapter.ts#CronJobAdapter (`schedule()` branches all three variants: `schedule.expression` + the per-job timezone, `schedule.type === 'interval' && schedule.intervalMs`, `schedule.type === 'once' && schedule.at`); packages/services/service-job/src/db-job-adapter.ts#DbJobAdapter (`schedule()` routes the cron variant to the cron adapter and persists the shape onto sys_job via `upsertJobRow`)", - "note": "all three variants enforced: cron `expression` + per-job `timezone`, interval `intervalMs`, once `at`; the db adapter persists the shape onto sys_job. WALK BOUNDARY: a discriminated union — the gate classifies it as one property; the per-variant keys are covered by the adapter evidence above, not by ledger rows. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — all three legs were wrong. `app-plugin.ts:834` had rotted onto `const actions = collectBundleActions(this.bundle)` — a DIFFERENT metadata kind's registration, which is the most misleading landing in this file because it still reads as bundle-wiring code; `cron-job-adapter.ts:71-88` and `db-job-adapter.ts:83` had both rotted onto docblocks. The entry also gains `toBoundaryJobSchedule`, the #4567 lowering seam that did not exist when this was written and is now the first thing that reads the key. Re-closed by hand against 93ea19bca." + "note": "all three variants enforced: cron `expression` + per-job `timezone`, interval `intervalMs`, once `at`; the db adapter persists the shape onto sys_job. WALK BOUNDARY: a discriminated union — the gate classifies it as one property; the per-variant keys are covered by the adapter evidence above, not by ledger rows. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — all three legs were wrong. `app-plugin.ts:834` had rotted onto `const actions = collectBundleActions(this.bundle)` — a DIFFERENT metadata kind's registration, which is the most misleading landing in this file because it still reads as bundle-wiring code; `cron-job-adapter.ts:71-88` and `db-job-adapter.ts:83` had both rotted onto docblocks. The entry also gains `toBoundaryJobSchedule`, the #4567 lowering seam that did not exist when this was written and is now the first thing that reads the key. Re-closed by hand against 93ea19bca." }, "handler": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/app-plugin.ts#start (`const handler = fnMap[job.handler]` — a miss warns and SKIPS the job rather than scheduling a no-op); packages/runtime/src/app-plugin.ts#collectBundleFunctions (the bundle function table the string resolves against — the same registry hooks and actions use)", - "note": "resolved against the bundle's function map; a missing handler skips the job with a warning rather than scheduling a no-op. This resolution is ALSO why the type is closed to runtime creation (#4509): the function table is a bundle artifact, so a handler string authored at runtime has nothing to resolve against. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `app-plugin.ts:824-830` had rotted onto a comment about registering actions on `POST /api/v1/actions/...`, ~95 lines above the lookup. The `collectBundleFunctions` half was named in the note with a line (`:812`) that the ledger never bounded because notes are prose; it is now in `evidence` under its own symbol, which is the leg that carries the runtime-creation argument. Re-closed by hand against 93ea19bca." + "note": "resolved against the bundle's function map; a missing handler skips the job with a warning rather than scheduling a no-op. This resolution is ALSO why the type is closed to runtime creation (#4509): the function table is a bundle artifact, so a handler string authored at runtime has nothing to resolve against. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `app-plugin.ts:824-830` had rotted onto a comment about registering actions on `POST /api/v1/actions/...`, ~95 lines above the lookup. The `collectBundleFunctions` half was named in the note with a line (`:812`) that the ledger never bounded because notes are prose; it is now in `evidence` under its own symbol, which is the leg that carries the runtime-creation argument. Re-closed by hand against 93ea19bca." }, "retryPolicy": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/app-plugin.ts#start (`(job.retryPolicy || job.timeout) ? { retryPolicy: job.retryPolicy, timeout: job.timeout } : undefined` — threaded into `svc.schedule` only when the author set one); packages/services/service-job/src/run-with-policy.ts#runWithPolicy (`const policy = options?.retryPolicy` → maxRetries / backoffMs / backoffMultiplier / maxRetryDelayMs / jitter drive the retry loop); packages/services/service-job/src/run-with-policy.ts#RETRY_DEFAULTS (what an OMITTED member means since 17.0.0 — maxRetries 0, i.e. no retry unless asked for, #4661)", - "note": "maxRetries/backoffMs/backoffMultiplier all drive the exponential-backoff retry loop (delay = min(backoffMs * multiplier^(retry-1), maxRetryDelayMs), jittered when asked). Enforced since #3494. This is the `retryPolicy` the datasource ledger warns about confusing with its dead namesake. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `app-plugin.ts:838-841` had rotted onto `if (actions.length > 0 && typeof ql.registerAction === 'function')`, another ACTIONS line, and `run-with-policy.ts:58-65` onto the `JobAttemptRecorder` interface — a neighbouring type rather than the policy loop. The `RETRY_DEFAULTS` leg is new and is the one that decides what an author's silence means, which is the half a reader of this entry most needs. Re-closed by hand against 93ea19bca." + "note": "maxRetries/backoffMs/backoffMultiplier all drive the exponential-backoff retry loop (delay = min(backoffMs * multiplier^(retry-1), maxRetryDelayMs), jittered when asked). Enforced since #3494. This is the `retryPolicy` the datasource ledger warns about confusing with its dead namesake. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `app-plugin.ts:838-841` had rotted onto `if (actions.length > 0 && typeof ql.registerAction === 'function')`, another ACTIONS line, and `run-with-policy.ts:58-65` onto the `JobAttemptRecorder` interface — a neighbouring type rather than the policy loop. The `RETRY_DEFAULTS` leg is new and is the one that decides what an author's silence means, which is the half a reader of this entry most needs. Re-closed by hand against 93ea19bca." }, "timeoutMs": { "status": "live", @@ -48,7 +48,7 @@ "evidenceScope": "in-repo", "evidence": "packages/services/service-job/src/run-with-policy.ts#runWithPolicy (`const timeoutMs = options?.timeoutMs` — applied PER ATTEMPT, and a timed-out attempt retries like any other failure); packages/services/service-job/src/run-with-policy.ts#withTimeout (the race itself); packages/services/service-job/src/run-with-policy.ts#JobTimeoutError (what an over-limit attempt rejects with — the in-flight handler is abandoned, not cancelled)", "producer": "packages/runtime/src/app-plugin.ts#start — the scheduler threads `{ retryPolicy: job.retryPolicy, timeoutMs: job.timeoutMs }` into `svc.schedule`, and only when the author set one of them", - "note": "per-attempt limit; an over-limit run records execution status 'timeout' (JobTimeoutError). The in-flight handler is abandoned, not cancelled — as documented. RENAMED 2026-09-04 (#14478) from `timeout`: the unit lived only in the describe while the sibling `retryPolicy.backoffMs` spelled its own; the contract key `JobScheduleOptions.timeoutMs`, the producer threading in app-plugin and the consumer read in runWithPolicy all moved in the same PR at the same magnitude. Producer side re-verified 2026-08-09 (#4837 slice); both legs re-anchored 2026-08-28 (#13003) — those anchors carried over unchanged." + "note": "per-attempt limit; an over-limit run records execution status 'timeout' (JobTimeoutError). The in-flight handler is abandoned, not cancelled — as documented. RENAMED 2026-09-04 (#14478) from `timeout`: the unit lived only in the describe while the sibling `retryPolicy.backoffMs` spelled its own; the contract key `JobScheduleOptions.timeoutMs`, the producer threading in app-plugin and the consumer read in runWithPolicy all moved in the same PR at the same magnitude. Producer side re-verified 2026-08-09 (#4837 slice); both legs re-anchored 2026-08-28 (commit 8cb96ec41) — those anchors carried over unchanged." }, "timeout": { "status": "dead", @@ -59,7 +59,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/runtime/src/app-plugin.ts#start (`if (job.enabled === false) { … continue; }` — the job is skipped at registration, before its handler is even resolved, so nothing is scheduled to no-op later)", - "note": "`enabled: false` skips scheduling entirely at registration — genuinely enforced, unlike the retired flow.active/tool.active. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `app-plugin.ts:820` had rotted onto a bare `}`. Same `start` residual as `name`: the read is inline in the `kernel:ready` hook and there is no narrower named symbol to anchor to. Re-closed by hand against 93ea19bca." + "note": "`enabled: false` skips scheduling entirely at registration — genuinely enforced, unlike the retired flow.active/tool.active. 2026-08-28: RE-ANCHORED (commit 8cb96ec41) and REPOINTED — `app-plugin.ts:820` had rotted onto a bare `}`. Same `start` residual as `name`: the read is inline in the `kernel:ready` hook and there is no narrower named symbol to anchor to. Re-closed by hand against 93ea19bca." } } } diff --git a/packages/spec/liveness/metadata_endpoints.json b/packages/spec/liveness/metadata_endpoints.json index aeb2859b534..b338d614818 100644 --- a/packages/spec/liveness/metadata_endpoints.json +++ b/packages/spec/liveness/metadata_endpoints.json @@ -1,6 +1,6 @@ { "type": "metadata_endpoints", - "_note": "MetadataEndpointsConfigSchema — packages/spec/src/api/rest-server.zod.ts#MetadataEndpointsConfigSchema, the `metadata` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census filed with #14369, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Mixed: `prefix`, `enableCache`, `maskObjectFields` and four of the five `endpoints.*` switches are read; `cacheTtl` and `endpoints.schema` are not. (`endpoints.maintenance` was added 2026-09-06 by #15542 and is read from its first commit \u2014 see its row.) This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY (#15543, measured 2026-09-08 on origin/main 8ccf7a1df): every `live` row in this file now carries a REACHABILITY sentence, and they all say the same thing because it is one measurement. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and there is exactly ONE door, programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site reaching `new RestServer(...)`. CORRECTED 2026-09-08: an earlier draft of this sentence named a second door, createHonoServerPlugin({ restConfig }) \u2014 NO SUCH FUNCTION EXISTS. A definition probe returns zero across the tree against a positive control that finds createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. The real shape: HonoServerPlugin is a CLASS (packages/plugins/plugin-hono-server/src/hono-plugin.ts:222) that declares a restConfig?: RestServerConfig option whose single reader (hono-plugin.ts:595) takes api.basePath for the SPA fallback; it never constructs a REST server, so it is not a door onto any key in this file. No shipped boot path opens the one real door with a config of its own: packages/cli/src/commands/serve.ts reads the stack config's own top-level `api:` block and forwards exactly two keys out of it (api.enableProjectScoping and api.projectResolution, serve.ts:3966-3968) into a fixed argument, through an `as any` cast; packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all. So the CLI does read a config file, it just forwards those two keys and nothing else, and every key in this file is EMBEDDER-ONLY. A CLI-started deployment therefore gets the schema default for every key here, with ONE carve-out that is also the security-relevant one: RestServer.normalizeConfig folds the environment into the EFFECTIVE value of maskObjectFields (packages/rest/src/rest-server.ts:4156 -> isObjectSchemaMaskingEnabled, packages/metadata-core/src/object-schema-fls.ts:198), so OS_ALLOW_UNMASKED_OBJECT_METADATA turns the ADR-0106 D8 mask off for a deployment that cannot reach the key. That env var is the only thing outside an embedder's argument that moves any value in this file. That is the RECORDED POSTURE, not a gap awaiting a fix: ruled 2026-09-07 (director seat, summon #17, decision batch #2) that the keys stay and keep their runtime reads, that threading a config through is not taken (a new authorable surface for no measured demand) and that retiring them is refused (they have an embedder consumer); the docblocks in packages/spec/src/api/rest-server.zod.ts state the reachability plainly, and this ledger is where the ADR-0049 question gets its written per-key answer. WHY REACHABILITY IS A SEPARATE AXIS FROM `status`: `live` means the runtime READS the key, which is the only thing this ledger's statuses classify, and it is correct here. Reachability answers who can WRITE it, which `live` never answered and which this card, #15542 and the three api-backend.rest-*-config-contract checklist items each had to re-derive from the boot paths because no file recorded it. The two never substitute for each other, and adding these sentences re-verified no call graph, so `verifiedAt` is deliberately NOT bumped by this edit.", + "_note": "MetadataEndpointsConfigSchema — packages/spec/src/api/rest-server.zod.ts#MetadataEndpointsConfigSchema, the `metadata` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census recorded in commit a3d5724c8, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Mixed: `prefix`, `enableCache`, `maskObjectFields` and four of the five `endpoints.*` switches are read; `cacheTtl` and `endpoints.schema` are not. (`endpoints.maintenance` was added 2026-09-06 by #15542 and is read from its first commit \u2014 see its row.) This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY (#15543, measured 2026-09-08 on origin/main 8ccf7a1df): every `live` row in this file now carries a REACHABILITY sentence, and they all say the same thing because it is one measurement. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and there is exactly ONE door, programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts), whose start() is the only non-test site reaching `new RestServer(...)`. CORRECTED 2026-09-08: an earlier draft of this sentence named a second door, createHonoServerPlugin({ restConfig }) \u2014 NO SUCH FUNCTION EXISTS. A definition probe returns zero across the tree against a positive control that finds createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. The real shape: HonoServerPlugin is a CLASS (packages/plugins/plugin-hono-server/src/hono-plugin.ts:222) that declares a restConfig?: RestServerConfig option whose single reader (hono-plugin.ts:595) takes api.basePath for the SPA fallback; it never constructs a REST server, so it is not a door onto any key in this file. No shipped boot path opens the one real door with a config of its own: packages/cli/src/commands/serve.ts reads the stack config's own top-level `api:` block and forwards exactly two keys out of it (api.enableProjectScoping and api.projectResolution, serve.ts:3966-3968) into a fixed argument, through an `as any` cast; packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all. So the CLI does read a config file, it just forwards those two keys and nothing else, and every key in this file is EMBEDDER-ONLY. A CLI-started deployment therefore gets the schema default for every key here, with ONE carve-out that is also the security-relevant one: RestServer.normalizeConfig folds the environment into the EFFECTIVE value of maskObjectFields (packages/rest/src/rest-server.ts:4156 -> isObjectSchemaMaskingEnabled, packages/metadata-core/src/object-schema-fls.ts:198), so OS_ALLOW_UNMASKED_OBJECT_METADATA turns the ADR-0106 D8 mask off for a deployment that cannot reach the key. That env var is the only thing outside an embedder's argument that moves any value in this file. That is the RECORDED POSTURE, not a gap awaiting a fix: ruled 2026-09-07 (director seat, summon #17, decision batch #2) that the keys stay and keep their runtime reads, that threading a config through is not taken (a new authorable surface for no measured demand) and that retiring them is refused (they have an embedder consumer); the docblocks in packages/spec/src/api/rest-server.zod.ts state the reachability plainly, and this ledger is where the ADR-0049 question gets its written per-key answer. WHY REACHABILITY IS A SEPARATE AXIS FROM `status`: `live` means the runtime READS the key, which is the only thing this ledger's statuses classify, and it is correct here. Reachability answers who can WRITE it, which `live` never answered and which this card, #15542 and the three api-backend.rest-*-config-contract checklist items each had to re-derive from the boot paths because no file recorded it. The two never substitute for each other, and adding these sentences re-verified no call graph, so `verifiedAt` is deliberately NOT bumped by this edit.", "props": { "prefix": { "status": "live", @@ -22,7 +22,7 @@ "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); `metadata.enableCache` is the live switch — it selects the protocol's `getMetaItemCached` read path, which takes no TTL — and no Cache-Control / ETag / Last-Modified header was ever built from this value; a declarative `api` endpoint's `cacheTtl` is the key that does reach the wire. The negative-bound observation this row carried (no lower bound, `-1` accepted, #11984 pinned it as accepted) dies with the key; the #11984 pin is reversed to a refusal pin. Pre-retirement census note: 0 read sites at 2514d49f3. `enableCache` is live and `cacheTtl` is not: the cached branch delegates to the protocol's own `getMetaItemCached`, which does not take a TTL from this config, and no ETag / Cache-Control / Last-Modified header is built from this value anywhere. `metadata.cacheTtl: 60` changes no header and no cache lifetime. Travelling with this row rather than as a separate defect (triage ruling, 2026-09-02): the schema declares `z.number().int()` with no lower bound, so a NEGATIVE TTL is accepted — #11984 pins `-1` as accepted precisely because that is what the contract says. If this key survives its enforce-or-remove call, that bound is part of enforcing it; if it goes, the pin goes with it. Either way it is one key's story, not two. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); `metadata.enableCache` is the live switch — it selects the protocol's `getMetaItemCached` read path, which takes no TTL — and no Cache-Control / ETag / Last-Modified header was ever built from this value; a declarative `api` endpoint's `cacheTtl` is the key that does reach the wire. The negative-bound observation this row carried (no lower bound, `-1` accepted, #11984 pinned it as accepted) dies with the key; the #11984 pin is reversed to a refusal pin. Pre-retirement census note: 0 read sites at 2514d49f3. `enableCache` is live and `cacheTtl` is not: the cached branch delegates to the protocol's own `getMetaItemCached`, which does not take a TTL from this config, and no ETag / Cache-Control / Last-Modified header is built from this value anywhere. `metadata.cacheTtl: 60` changes no header and no cache lifetime. Travelling with this row rather than as a separate defect (triage ruling, 2026-09-02): the schema declares `z.number().int()` with no lower bound, so a NEGATIVE TTL is accepted — #11984 pins `-1` as accepted precisely because that is what the contract says. If this key survives its enforce-or-remove call, that bound is part of enforcing it; if it goes, the pin goes with it. Either way it is one key's story, not two. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." }, "maskObjectFields": { "status": "live", @@ -70,7 +70,7 @@ "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the route it gated — `GET /meta/:type/:name/schema` — does not exist (packages/rest/src mounts no path ending in `/schema`), so there is nothing to enforce; `endpoints.types` / `items` / `item` are the switches that gate real mounts and stay live. Pre-retirement census note: 0 read sites at 2514d49f3. The odd one out of the four: its three siblings each gate a route mount and this one gates nothing, because the route its describe() names — `GET /meta/:type/:name/schema` — does not exist. packages/rest/src mounts no path ending in `/schema` at all, so `endpoints.schema: false` removes nothing and `true` adds nothing; the switch was declared for a route that was never built, the same shape as `batch.operations.upsertMany`. This is also the sharpest case in the family for why this container may not carry one blanket verdict: three live members and one dead one under a single `endpoints` key would have hidden exactly this. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the route it gated — `GET /meta/:type/:name/schema` — does not exist (packages/rest/src mounts no path ending in `/schema`), so there is nothing to enforce; `endpoints.types` / `items` / `item` are the switches that gate real mounts and stay live. Pre-retirement census note: 0 read sites at 2514d49f3. The odd one out of the four: its three siblings each gate a route mount and this one gates nothing, because the route its describe() names — `GET /meta/:type/:name/schema` — does not exist. packages/rest/src mounts no path ending in `/schema` at all, so `endpoints.schema: false` removes nothing and `true` adds nothing; the switch was declared for a route that was never built, the same shape as `batch.operations.upsertMany`. This is also the sharpest case in the family for why this container may not carry one blanket verdict: three live members and one dead one under a single `endpoints` key would have hidden exactly this. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." } } } diff --git a/packages/spec/liveness/qa.json b/packages/spec/liveness/qa.json index f79c93a3cad..321b96b4b2f 100644 --- a/packages/spec/liveness/qa.json +++ b/packages/spec/liveness/qa.json @@ -1,6 +1,6 @@ { "type": "qa", - "_note": "TestSuiteSchema (packages/spec/src/qa/testing.zod.ts) — the Quality Protocol file surface: an author writes `qa/*.test.json`, `os test` loads it, and core's TestRunner executes it. Seeded 2026-08-10 (#6247), the ENFORCE leg of an enforce-or-remove call that was ruled the other way first and then withdrawn, which is the lesson worth keeping. #6247 filed this domain as declared-but-inert on a grep that scanned only `*Schema` identifiers; every consumer here reads the TYPE names (`QA.TestSuite`, `QA.TestScenario`, `QA.TestStep`, `QA.TestAction`, `QA.TestAssertion`), so the search matched nothing and a complete execution chain read as zero consumers. The 2026-08-07 retire ruling rested on that reading and was WITHDRAWN on 2026-08-08 (issue comment 5225532429) once the sweep's pre-flight gate falsified it. A schema with no `parse` site is not the same finding as a schema with no consumer, and only the first one was true. The measured chain, by layer: the RUNNER (packages/core/src/qa/runner.ts) reads suite.scenarios, scenario.id/setup/steps/teardown and step.name/action/capture/assertions; the ADAPTER (packages/core/src/qa/http-adapter.ts) switches on action.type — its case labels ARE the TestActionTypeSchema values — and reads target/payload/user; both are published through packages/core/src/index.ts:25 (`export * as QA`); the driving entry point is the shipped oclif command `os test` (packages/cli/src/commands/test.ts), documented at content/docs/deployment/cli.mdx:987,1012-1020 and packages/cli/README.md:104. WALK BOUNDARY, recorded rather than silently skipped: the gate classifies one level and this file drills `scenarios` one more, so the verdicts here cover the suite and scenario levels only. Step / action / assertion keys sit BELOW the walk; they were measured in the same pass and their verdicts are recorded in the `setup`/`steps`/`teardown` notes instead of being fanned out into rows the gate would not check. AUTHOR-WARN CHANNEL: none exists for this type, and no entry is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a QA suite is not part of a stack at all; it is a loose JSON file `os test` globs off disk. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the load-site parse (below) is what actually reaches the author. LOAD-SITE ENFORCEMENT: the same change that seeded this file replaced the CLI's `JSON.parse(content) as QA.TestSuite` cast — the schema author's own `// Should validate with Zod` TODO — with a real `TestSuiteSchema.safeParse`, so a malformed suite is named at load time instead of reaching the runner as a lie about its own shape. That is what makes the `live` rows below enforced rather than merely read. No `qa` property is a bound HIGH_RISK class in proof-registry.mts, so no entry carries a `proof`; none is invented to look thorough. 2026-08-28 (#13003): the four `path:NNN` citations in this file were re-anchored to `runScenario`, the method that reads all four keys. All four were wrong and all four IN RANGE — they moved as one block when a diagnostic helper was added at the head of the file. The many bare `:NNN` suffixes in the sub-key notes below are prose, not citations: no check has ever resolved them, and they are left as the hand-measured record they are.", + "_note": "TestSuiteSchema (packages/spec/src/qa/testing.zod.ts) — the Quality Protocol file surface: an author writes `qa/*.test.json`, `os test` loads it, and core's TestRunner executes it. Seeded 2026-08-10 (#6247), the ENFORCE leg of an enforce-or-remove call that was ruled the other way first and then withdrawn, which is the lesson worth keeping. #6247 filed this domain as declared-but-inert on a grep that scanned only `*Schema` identifiers; every consumer here reads the TYPE names (`QA.TestSuite`, `QA.TestScenario`, `QA.TestStep`, `QA.TestAction`, `QA.TestAssertion`), so the search matched nothing and a complete execution chain read as zero consumers. The 2026-08-07 retire ruling rested on that reading and was WITHDRAWN on 2026-08-08 (issue comment 5225532429) once the sweep's pre-flight gate falsified it. A schema with no `parse` site is not the same finding as a schema with no consumer, and only the first one was true. The measured chain, by layer: the RUNNER (packages/core/src/qa/runner.ts) reads suite.scenarios, scenario.id/setup/steps/teardown and step.name/action/capture/assertions; the ADAPTER (packages/core/src/qa/http-adapter.ts) switches on action.type — its case labels ARE the TestActionTypeSchema values — and reads target/payload/user; both are published through packages/core/src/index.ts:25 (`export * as QA`); the driving entry point is the shipped oclif command `os test` (packages/cli/src/commands/test.ts), documented at content/docs/deployment/cli.mdx:987,1012-1020 and packages/cli/README.md:104. WALK BOUNDARY, recorded rather than silently skipped: the gate classifies one level and this file drills `scenarios` one more, so the verdicts here cover the suite and scenario levels only. Step / action / assertion keys sit BELOW the walk; they were measured in the same pass and their verdicts are recorded in the `setup`/`steps`/`teardown` notes instead of being fanned out into rows the gate would not check. AUTHOR-WARN CHANNEL: none exists for this type, and no entry is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a QA suite is not part of a stack at all; it is a loose JSON file `os test` globs off disk. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the load-site parse (below) is what actually reaches the author. LOAD-SITE ENFORCEMENT: the same change that seeded this file replaced the CLI's `JSON.parse(content) as QA.TestSuite` cast — the schema author's own `// Should validate with Zod` TODO — with a real `TestSuiteSchema.safeParse`, so a malformed suite is named at load time instead of reaching the runner as a lie about its own shape. That is what makes the `live` rows below enforced rather than merely read. No `qa` property is a bound HIGH_RISK class in proof-registry.mts, so no entry carries a `proof`; none is invented to look thorough. 2026-08-28 (commit 8f10a79f7): the four `path:NNN` citations in this file were re-anchored to `runScenario`, the method that reads all four keys. All four were wrong and all four IN RANGE — they moved as one block when a diagnostic helper was added at the head of the file. The many bare `:NNN` suffixes in the sub-key notes below are prose, not citations: no check has ever resolved them, and they are left as the hand-measured record they are.", "props": { "name": { "status": "live", @@ -16,7 +16,7 @@ "evidence": "packages/core/src/qa/runner.ts#runScenario (`scenarioId: scenario.id` on BOTH result envelopes — the setup-failure path and the completed run)", "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "The scenario identity in every result the CLI prints, in brackets after the scenario's `name` (packages/cli/src/commands/test.ts#scenarioLabel). Until #20289 it was the ONLY handle a report gave — `name` was printed nowhere. Not deduplicated: two scenarios may declare the same id and both run, so an id collision shows up as two indistinguishable result lines rather than an error. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:101` had rotted onto `stepName: step.name` in the per-step result, and the sibling `:47` was a bare line suffix with no path in front of it, which the scanner cannot resolve at all. Both id reads live in `runScenario`. Re-closed by hand against 8cb96ec41." + "note": "The scenario identity in every result the CLI prints, in brackets after the scenario's `name` (packages/cli/src/commands/test.ts#scenarioLabel). Until #20289 it was the ONLY handle a report gave — `name` was printed nowhere. Not deduplicated: two scenarios may declare the same id and both run, so an id collision shows up as two indistinguishable result lines rather than an error. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:101` had rotted onto `stepName: step.name` in the per-step result, and the sibling `:47` was a bare line suffix with no path in front of it, which the scanner cannot resolve at all. Both id reads live in `runScenario`. Re-closed by hand against 8cb96ec41." }, "name": { "status": "live", @@ -45,21 +45,21 @@ "evidence": "packages/core/src/qa/runner.ts#runScenario (`if (scenario.setup) { for (const step of scenario.setup) … }` — run before the main steps; a throw here aborts the scenario with `Setup failed` and no step results)", "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Array of TestStep — one level below the walk, measured in the same pass and recorded here rather than fanned into rows the gate would not check. Step keys: `name` LIVE (runner.ts:67, :76 — the label on every step result), `action` LIVE (:112, the only thing actually executed), `capture` LIVE (:118-121, writes result paths into the scenario variable context that `{{var}}` interpolation reads at :136), `assertions` LIVE (:125-128), `description` DEAD (docs-shaped, unread, kept). Action keys: `type` LIVE (http-adapter.ts:21 switch), `target` LIVE (:23-33), `payload` LIVE (:23-35), `user` LIVE (:17-18 — emitted as the `X-Run-As` header, so impersonation is only as real as the server's handling of that header). Assertion keys: `field` LIVE (runner.ts:160), `operator` LIVE (:164), `expectedValue` LIVE (:161). Two VALUE-level gaps, both loud rather than silent, and neither of them a key verdict (the api.json `type` precedent): `run_script` is in TestActionTypeSchema with no adapter branch and throws 'Unsupported action type', and the `not_contains`/`gt`/`gte`/`lt`/`lte`/`error` operators throw 'Unknown assertion operator'. The one genuinely silent path is `contains` against an actual that is neither array nor string (runner.ts:171-177), which falls through and PASSES — filed separately. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:41-54` had rotted onto the tail of the `contains`-misuse diagnostic helper and the `export class TestRunner` line below it. The whole file had shifted by the same amount when that helper was added at its head, which is why all four of this ledger's pointers were wrong together and none of them out of range. Re-closed by hand against 8cb96ec41." + "note": "Array of TestStep — one level below the walk, measured in the same pass and recorded here rather than fanned into rows the gate would not check. Step keys: `name` LIVE (runner.ts:67, :76 — the label on every step result), `action` LIVE (:112, the only thing actually executed), `capture` LIVE (:118-121, writes result paths into the scenario variable context that `{{var}}` interpolation reads at :136), `assertions` LIVE (:125-128), `description` DEAD (docs-shaped, unread, kept). Action keys: `type` LIVE (http-adapter.ts:21 switch), `target` LIVE (:23-33), `payload` LIVE (:23-35), `user` LIVE (:17-18 — emitted as the `X-Run-As` header, so impersonation is only as real as the server's handling of that header). Assertion keys: `field` LIVE (runner.ts:160), `operator` LIVE (:164), `expectedValue` LIVE (:161). Two VALUE-level gaps, both loud rather than silent, and neither of them a key verdict (the api.json `type` precedent): `run_script` is in TestActionTypeSchema with no adapter branch and throws 'Unsupported action type', and the `not_contains`/`gt`/`gte`/`lt`/`lte`/`error` operators throw 'Unknown assertion operator'. The one genuinely silent path is `contains` against an actual that is neither array nor string (runner.ts:171-177), which falls through and PASSES — filed separately. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:41-54` had rotted onto the tail of the `contains`-misuse diagnostic helper and the `export class TestRunner` line below it. The whole file had shifted by the same amount when that helper was added at its head, which is why all four of this ledger's pointers were wrong together and none of them out of range. Re-closed by hand against 8cb96ec41." }, "steps": { "status": "live", "evidence": "packages/core/src/qa/runner.ts#runScenario (`for (const step of scenario.steps)` — the main sequence; stops at the first failing step)", "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "The required member — a scenario with an empty `steps` array parses, runs nothing, and reports passed. Step/action/assertion sub-keys are recorded on the `setup` entry above; all three arrays are the same TestStep surface executed by the same `runStep`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:62-83`'s endpoint had rotted onto `steps: []` inside the SETUP-failure envelope, i.e. onto the branch that by construction runs no steps. Re-closed by hand against 8cb96ec41." + "note": "The required member — a scenario with an empty `steps` array parses, runs nothing, and reports passed. Step/action/assertion sub-keys are recorded on the `setup` entry above; all three arrays are the same TestStep surface executed by the same `runStep`. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:62-83`'s endpoint had rotted onto `steps: []` inside the SETUP-failure envelope, i.e. onto the branch that by construction runs no steps. Re-closed by hand against 8cb96ec41." }, "teardown": { "status": "live", "evidence": "packages/core/src/qa/runner.ts#runScenario (`if (scenario.teardown) { for (const step of scenario.teardown) … }` — runs even after a failed step, and only turns a scenario red when it had otherwise PASSED)", "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "note": "Same TestStep surface as `setup`/`steps`. Worth knowing: a teardown throw only turns the scenario red when the scenario had otherwise PASSED (:92-95), so cleanup failures behind a real failure are swallowed on purpose. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:86-98`'s endpoint had rotted onto the `try {` that opens the MAIN step loop. Re-closed by hand against 8cb96ec41." + "note": "Same TestStep surface as `setup`/`steps`. Worth knowing: a teardown throw only turns the scenario red when the scenario had otherwise PASSED (:92-95), so cleanup failures behind a real failure are swallowed on purpose. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:86-98`'s endpoint had rotted onto the `try {` that opens the MAIN step loop. Re-closed by hand against 8cb96ec41." }, "requires": { "status": "live", diff --git a/packages/spec/liveness/rest_api.json b/packages/spec/liveness/rest_api.json index 43e9c32cb7d..0dd439637c0 100644 --- a/packages/spec/liveness/rest_api.json +++ b/packages/spec/liveness/rest_api.json @@ -1,6 +1,6 @@ { "type": "rest_api", - "_note": "RestApiConfigSchema — packages/spec/src/api/rest-server.zod.ts#RestApiConfigSchema, the `api` sub-object of RestServerConfig and the FIFTH of its five. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the sub-objects are rooted separately instead of the whole RestServerConfigSchema. ⛔ WHY THIS FILE IS `rest_api.json` AND NOT `api.json`, which is the first mistake a reader makes here: packages/spec/liveness/api.json ALREADY EXISTS and is a DIFFERENT `api` — its own header names ApiEndpointSchema (packages/spec/src/api/endpoint.zod.ts), the registered `api` metadata type, with real consumers in the matcher, executor, policy chain and mapping layer. It has nothing to do with RestApiConfigSchema. Filing these verdicts there would publish one file's measurement under another file's name. One spelling, two unrelated meanings, inside packages/spec — the same shape as the `userMessage` collision. The gate's own SPEC_ONLY_SCHEMAS paragraph repeats this fence so the next enrolment does not have to rediscover it. SEEDED 2026-09-21, a round later than its four siblings, and the delay is the point. #14369 enrolled the four and deliberately left this one out: at that moment the `api` block's consumption seam was still VALIDATE-ONLY (#11637 ran the declared contract and discarded its output), so a census taken then would have recorded a half that was about to move, and both the gate source and the crud_endpoints / route_generation README rows said so in as many words. THE FENCE HAS EXPIRED, measured before anything here was written: RestServer.normalizeConfig now BUILDS the `api` block from parseDeclaredApiConfig's output instead of discarding it — 'the asymmetry is gone and all five now build from their parsed output' (packages/rest/src/rest-server.ts#parseDeclaredApiConfig) — and that change is RELEASED, not in flight: it is in packages/rest/CHANGELOG.md, whose entry re-states the same zero this file records ('nothing in the platform reads either key today ... the repo has no other read site for either key'). So the census below is taken on a settled seam, which is the whole condition the exclusion was waiting on. No other seam was found to constrain these keys, and the sweep for one is recorded per row. ⚠️ THE UPSTREAM REFERENCES IN THIS FAMILY'S PAPER TRAIL DO NOT RESOLVE. #14366 (the card that landed the consumption seam), #14369 (the card that enrolled the four siblings), #14691 (the retirement that removed their dead keys), #14365 and #14690 all return HTTP 404, measured 2026-09-21 against neighbours that resolve at 200 (#14368, #14370, #14692). Those numbers are cited throughout rest-server.ts, rest-server.zod.ts and the four sibling ledgers, and the work they name is all VISIBLY LANDED in the tree — so read the tree, not the tracker, and ⛔ do not guess replacement numbers. The class is carded at #17512, which measured five instances; these are not all of the same five. MIXED: twelve keys gate or shape the mounted surface (`version` / `basePath` / `apiPath` become the prefix of every route; the eight `enable*` switches decide mounts and the discovery document's capability block; `enableProjectScoping` / `projectResolution` decide the scoped mount and are the only two keys a shipped boot path can author). Eleven do not: the `requireAuth` tombstone, and the two declared containers `documentation` (seven members) and `responseFormat` (three), which normalizeConfig copies through and nothing reads back. THIS FILE RECORDS STATUS; IT DECIDES NOTHING. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. ⛔ The two dead containers do NOT get one shared verdict by default: `documentation`'s members are OpenAPI `info` fields whose enforce route collides with a recorded ownership decision (#11646 — see the per-row notes), while `responseFormat`'s enforce route means making the response envelope configurable, a larger claim. Each row states its own. REMOVAL SHAPE, if that is the call the human floor makes, stated here because the precedent is split and the wrong half is the obvious one: a RestServerConfig is plugin TS configuration, never a stack collection member and never a sys_metadata row (the RestServerConfig.openApi31 precedent, #4579), so a retirement here takes the `crud.patterns` route — a `retiredKey()` tombstone at the schema plus a D3 registry entry carrying the prescription — and NOT an ADR-0087 conversion. The counter-example is a trap: `stack.api.requireAuth` DOES have a conversion (`stack-api-require-auth-removed`), but its surface is the STACK's own top-level `api:` block, a separate and deliberately narrow schema in stack.zod.ts carrying four keys (`requireAuth` tombstone, `enableProjectScoping`, `projectResolution`, `enforceProjectMembership`). `documentation` and `responseFormat` are NOT in it, so they are not authorable from objectstack.config.ts at all and there is no stored source for a conversion to strip. CENSUS METHOD AND SCOPE, run 2026-09-21 on origin/main 31184e5daf3bc48cf51bd9af7d6b05fe9c53ab6f: `git grep` with NO pathspec over the whole tracked tree, filtered afterwards (the pathspec form has a measured trap in this checkout); read sites outside NormalizedRestServerConfig's type declaration and normalizeConfig itself, comments and tests excluded. Every zero carries a LIT CONTROL on the same instrument and the same object, and every named hole in the radius carries a second instrument with its own control — the per-row notes record both, plus the three shapes a spelling sweep is blind to (spread, destructuring, computed access / casts), each swept and each empty. The structural backstop is what a grep cannot give: NormalizedRestServerConfig is a module-local type with no `export` and RestServer.config is `private`, so the normalized block is unreachable from outside that one class. HOLES IN THE RADIUS, named rather than papered over: (1) the sibling repo objectui — swept separately and clean at the pinned sha 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 and at head 98178b20, 0 hits for RestApiConfig / RestServerConfig / responseFormat / includeMetadata / includePagination / termsOfService against a lit control of 181 for `basePath` at the pin (182 at head); (2) the closed cloud runtime, NOT reachable from the measuring container — #14796's structural reading (cloud never authors a RestServerConfig) is cited as a standing reading, not re-measured here, which is why every `evidenceScope` below says `in-repo` and not `cross-repo`; (3) untracked build output, which `git grep` does not see — packages/console/dist is objectui's build and returns 0 for the keys and 0 for the control, so it contributes no reading either way. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS, and a RestServerConfig is not part of a stack at all — it is the argument a host passes when it constructs the server, so a warn flag here would emit nothing, which is the same silent no-op this ledger exists to catch. The dead entries carry their correction in `note`, and the construction-time parse is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY IS A SEPARATE AXIS FROM `status`, and every row below carries the sentence: `live` means the runtime READS the key, which is the only thing these statuses classify; reachability answers who can WRITE it. Both facts for this block are #15543's standing reading — embedder-only, one programmatic door, `os serve` forwarding exactly two keys — and re-stating it here re-verified no call graph. RETIRED 2026-09-27 (#20295, ADR-0049 enforce-or-remove): the call was made for four of the dead keys — `responseFormat` (the whole block, one `retiredKey()` tombstone; its three child rows collapse into one row) and `documentation.enabled` — by exactly the REMOVAL SHAPE above: tombstones plus a D3 entry, no conversion. Both rows stay `dead` with a REMOVED note. `documentation`'s other members are a separate decision and keep their rows unchanged. ENFORCED / RETIRED 2026-09-28 (#20294, ruling B on #20359): that separate decision split `documentation` by who owns each field. Its identity members — `title`, `description`, `termsOfService`, and the `contact` / `license` containers with their members — are LIVE: `RestServer.overlayDocumentationInfo` lays the authored ones over the served OpenAPI `info` on both doors (the containers replace the bundled objects whole), and nothing authored serves the artifact's `info` unchanged. `version` is RETIRED by the same REMOVAL SHAPE as `enabled` — a tombstone plus a D3 entry, no conversion — because the served `info.version` is the protocol version (#11646). Its row stays `dead` with a REMOVED note.", + "_note": "RestApiConfigSchema — packages/spec/src/api/rest-server.zod.ts#RestApiConfigSchema, the `api` sub-object of RestServerConfig and the FIFTH of its five. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the sub-objects are rooted separately instead of the whole RestServerConfigSchema. ⛔ WHY THIS FILE IS `rest_api.json` AND NOT `api.json`, which is the first mistake a reader makes here: packages/spec/liveness/api.json ALREADY EXISTS and is a DIFFERENT `api` — its own header names ApiEndpointSchema (packages/spec/src/api/endpoint.zod.ts), the registered `api` metadata type, with real consumers in the matcher, executor, policy chain and mapping layer. It has nothing to do with RestApiConfigSchema. Filing these verdicts there would publish one file's measurement under another file's name. One spelling, two unrelated meanings, inside packages/spec — the same shape as the `userMessage` collision. The gate's own SPEC_ONLY_SCHEMAS paragraph repeats this fence so the next enrolment does not have to rediscover it. SEEDED 2026-09-21, a round later than its four siblings, and the delay is the point. Commit a3d5724c8 enrolled the four and deliberately left this one out: at that moment the `api` block's consumption seam was still VALIDATE-ONLY (#11637 ran the declared contract and discarded its output), so a census taken then would have recorded a half that was about to move, and both the gate source and the crud_endpoints / route_generation README rows said so in as many words. THE FENCE HAS EXPIRED, measured before anything here was written: RestServer.normalizeConfig now BUILDS the `api` block from parseDeclaredApiConfig's output instead of discarding it — 'the asymmetry is gone and all five now build from their parsed output' (packages/rest/src/rest-server.ts#parseDeclaredApiConfig) — and that change is RELEASED, not in flight: it is in packages/rest/CHANGELOG.md, whose entry re-states the same zero this file records ('nothing in the platform reads either key today ... the repo has no other read site for either key'). So the census below is taken on a settled seam, which is the whole condition the exclusion was waiting on. No other seam was found to constrain these keys, and the sweep for one is recorded per row. ⚠️ THE UPSTREAM REFERENCES IN THIS FAMILY'S PAPER TRAIL DO NOT RESOLVE. The tracker numbers of the card that landed the consumption seam, the card that enrolled the four siblings and the retirement that removed their dead keys (commits 53cbad9f7, a3d5724c8 and b3a63d32c, in that order), and two more, all return HTTP 404, measured 2026-09-21 against neighbours that resolve at 200 (#14368, #14370, #14692). Those numbers were cited throughout rest-server.ts, rest-server.zod.ts and the four sibling ledgers, and the work they name is all VISIBLY LANDED in the tree — so read the tree, not the tracker, and ⛔ do not guess replacement numbers. The class is carded at #17512, which measured five instances; these are not all of the same five. MIXED: twelve keys gate or shape the mounted surface (`version` / `basePath` / `apiPath` become the prefix of every route; the eight `enable*` switches decide mounts and the discovery document's capability block; `enableProjectScoping` / `projectResolution` decide the scoped mount and are the only two keys a shipped boot path can author). Eleven do not: the `requireAuth` tombstone, and the two declared containers `documentation` (seven members) and `responseFormat` (three), which normalizeConfig copies through and nothing reads back. THIS FILE RECORDS STATUS; IT DECIDES NOTHING. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. ⛔ The two dead containers do NOT get one shared verdict by default: `documentation`'s members are OpenAPI `info` fields whose enforce route collides with a recorded ownership decision (#11646 — see the per-row notes), while `responseFormat`'s enforce route means making the response envelope configurable, a larger claim. Each row states its own. REMOVAL SHAPE, if that is the call the human floor makes, stated here because the precedent is split and the wrong half is the obvious one: a RestServerConfig is plugin TS configuration, never a stack collection member and never a sys_metadata row (the RestServerConfig.openApi31 precedent, #4579), so a retirement here takes the `crud.patterns` route — a `retiredKey()` tombstone at the schema plus a D3 registry entry carrying the prescription — and NOT an ADR-0087 conversion. The counter-example is a trap: `stack.api.requireAuth` DOES have a conversion (`stack-api-require-auth-removed`), but its surface is the STACK's own top-level `api:` block, a separate and deliberately narrow schema in stack.zod.ts carrying four keys (`requireAuth` tombstone, `enableProjectScoping`, `projectResolution`, `enforceProjectMembership`). `documentation` and `responseFormat` are NOT in it, so they are not authorable from objectstack.config.ts at all and there is no stored source for a conversion to strip. CENSUS METHOD AND SCOPE, run 2026-09-21 on origin/main 31184e5daf3bc48cf51bd9af7d6b05fe9c53ab6f: `git grep` with NO pathspec over the whole tracked tree, filtered afterwards (the pathspec form has a measured trap in this checkout); read sites outside NormalizedRestServerConfig's type declaration and normalizeConfig itself, comments and tests excluded. Every zero carries a LIT CONTROL on the same instrument and the same object, and every named hole in the radius carries a second instrument with its own control — the per-row notes record both, plus the three shapes a spelling sweep is blind to (spread, destructuring, computed access / casts), each swept and each empty. The structural backstop is what a grep cannot give: NormalizedRestServerConfig is a module-local type with no `export` and RestServer.config is `private`, so the normalized block is unreachable from outside that one class. HOLES IN THE RADIUS, named rather than papered over: (1) the sibling repo objectui — swept separately and clean at the pinned sha 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 and at head 98178b20, 0 hits for RestApiConfig / RestServerConfig / responseFormat / includeMetadata / includePagination / termsOfService against a lit control of 181 for `basePath` at the pin (182 at head); (2) the closed cloud runtime, NOT reachable from the measuring container — #14796's structural reading (cloud never authors a RestServerConfig) is cited as a standing reading, not re-measured here, which is why every `evidenceScope` below says `in-repo` and not `cross-repo`; (3) untracked build output, which `git grep` does not see — packages/console/dist is objectui's build and returns 0 for the keys and 0 for the control, so it contributes no reading either way. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS, and a RestServerConfig is not part of a stack at all — it is the argument a host passes when it constructs the server, so a warn flag here would emit nothing, which is the same silent no-op this ledger exists to catch. The dead entries carry their correction in `note`, and the construction-time parse is what actually reaches the author — for accept/reject, which is a different question from liveness. REACHABILITY IS A SEPARATE AXIS FROM `status`, and every row below carries the sentence: `live` means the runtime READS the key, which is the only thing these statuses classify; reachability answers who can WRITE it. Both facts for this block are #15543's standing reading — embedder-only, one programmatic door, `os serve` forwarding exactly two keys — and re-stating it here re-verified no call graph. RETIRED 2026-09-27 (#20295, ADR-0049 enforce-or-remove): the call was made for four of the dead keys — `responseFormat` (the whole block, one `retiredKey()` tombstone; its three child rows collapse into one row) and `documentation.enabled` — by exactly the REMOVAL SHAPE above: tombstones plus a D3 entry, no conversion. Both rows stay `dead` with a REMOVED note. `documentation`'s other members are a separate decision and keep their rows unchanged. ENFORCED / RETIRED 2026-09-28 (#20294, ruling B on #20359): that separate decision split `documentation` by who owns each field. Its identity members — `title`, `description`, `termsOfService`, and the `contact` / `license` containers with their members — are LIVE: `RestServer.overlayDocumentationInfo` lays the authored ones over the served OpenAPI `info` on both doors (the containers replace the bundled objects whole), and nothing authored serves the artifact's `info` unchanged. `version` is RETIRED by the same REMOVAL SHAPE as `enabled` — a tombstone plus a D3 entry, no conversion — because the served `info.version` is the protocol version (#11646). Its row stays `dead` with a REMOVED note.", "props": { "version": { "status": "live", @@ -16,7 +16,7 @@ "evidenceScope": "in-repo", "evidence": "packages/rest/src/rest-server.ts#getApiBasePath (`${api.basePath}/${api.version}` — the fallback prefix when `apiPath` is unset)", "producer": "packages/rest/src/rest-server.ts#normalizeConfig (parses `config.api` through `parseDeclaredApiConfig` and lists the key into `this.config.api`, the object every consumer below reads; the parsed schema's own `.default()`s supply the value when the host omits the key)", - "note": "Same destructured read as `version`; `getApiBasePath` is deliberately the single source of the prefix (#6306), so there is one consumer and copying its expression to a second site is the failure that rule exists to stop. REACHABILITY (#15543, standing reading): embedder-only. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and the one door is programmatic — createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts). No shipped boot path opens it with a config of its own: packages/cli/src/commands/serve.ts forwards exactly two keys out of the stack's own top-level `api:` block (enableProjectScoping, projectResolution) and plugin-dev calls createRestApiPlugin() with no config at all, so a CLI-started deployment always gets the schema default for every key here. `live` answers who READS the key; reachability answers who can SET it, and the two never substitute for each other." + "note": "Same destructured read as `version`; `getApiBasePath` is deliberately the single source of the prefix (commit fec784863), so there is one consumer and copying its expression to a second site is the failure that rule exists to stop. REACHABILITY (#15543, standing reading): embedder-only. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and the one door is programmatic — createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts). No shipped boot path opens it with a config of its own: packages/cli/src/commands/serve.ts forwards exactly two keys out of the stack's own top-level `api:` block (enableProjectScoping, projectResolution) and plugin-dev calls createRestApiPlugin() with no config at all, so a CLI-started deployment always gets the schema default for every key here. `live` answers who READS the key; reachability answers who can SET it, and the two never substitute for each other." }, "apiPath": { "status": "live", @@ -24,7 +24,7 @@ "evidenceScope": "in-repo", "evidence": "packages/rest/src/rest-server.ts#getApiBasePath (`api.apiPath ?? …` — when set it REPLACES the basePath/version prefix outright)", "producer": "packages/rest/src/rest-server.ts#normalizeConfig (parses `config.api` through `parseDeclaredApiConfig` and lists the key into `this.config.api`, the object every consumer below reads; the parsed schema's own `.default()`s supply the value when the host omits the key)", - "note": "The one key on this block whose value changes where every other route lands; #6306 made getApiBasePath public precisely so the direct-mount registrars honour it instead of recomputing the prefix and dropping this key. REACHABILITY (#15543, standing reading): embedder-only. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and the one door is programmatic — createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts). No shipped boot path opens it with a config of its own: packages/cli/src/commands/serve.ts forwards exactly two keys out of the stack's own top-level `api:` block (enableProjectScoping, projectResolution) and plugin-dev calls createRestApiPlugin() with no config at all, so a CLI-started deployment always gets the schema default for every key here. `live` answers who READS the key; reachability answers who can SET it, and the two never substitute for each other." + "note": "The one key on this block whose value changes where every other route lands; Commit fec784863 made getApiBasePath public precisely so the direct-mount registrars honour it instead of recomputing the prefix and dropping this key. REACHABILITY (#15543, standing reading): embedder-only. A RestServerConfig is the ARGUMENT a host passes when it constructs the server, and the one door is programmatic — createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts). No shipped boot path opens it with a config of its own: packages/cli/src/commands/serve.ts forwards exactly two keys out of the stack's own top-level `api:` block (enableProjectScoping, projectResolution) and plugin-dev calls createRestApiPlugin() with no config at all, so a CLI-started deployment always gets the schema default for every key here. `live` answers who READS the key; reachability answers who can SET it, and the two never substitute for each other." }, "enableCrud": { "status": "live", @@ -208,7 +208,7 @@ "status": "dead", "verifiedAt": "2026-09-27", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-27 (#20295) — tombstoned at the schema as ONE key (retiredKey carries the prescription; authoring it is a tsc error and a parse error, and RestServer construction refuses it with that prescription instead of copying it into this.config.api). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-api-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent). What to do instead: nothing to configure — response shapes are fixed: each route answers in the response schema packages/spec declares for it, which the client SDK parses and the served /openapi.json describes. The three child rows this container carried (`envelope` / `includeMetadata` / `includePagination`) collapse into this one row: the tombstone is a leaf, and rows for keys that left the walked shape would report ORPHAN (the `crud.patterns` precedent, #14691). Their pre-retirement verdicts, kept as the record — all three `dead`, 0 read sites at 31184e5d, the three blind shapes swept against their own controls: `envelope: false` unwrapped no response (the envelope is produced unconditionally); `includeMetadata` gated no response metadata (timestamp, requestId); `includePagination` gated nothing (list responses carry their pagination block regardless). Re-measured 2026-09-27 on origin/main 4e0f72e8 before the tombstone landed: `packages/**` non-test code carried 0 reads (the only code sites were NormalizedRestServerConfig's type declaration and normalizeConfig's own write), against a lit control on the same instrument (`enableOpenApi` finds its read at rest-server.ts#registerRoutes); objectui at its pin f8a9d0fb returned 0 for RestApiConfig / RestServerConfig / responseFormat / includePagination / enableOpenApi against a lit control (`basePath` = 184); cloud at 96eb092 returned 0 authoring sites for RestApiConfig / RestServerConfig / responseFormat / includePagination / `documentation.enabled`, against a lit control (`createRestApiPlugin` = 11 — every call site forwards the stack's own top-level `api:` block, whose schema carries neither key). So `evidenceScope` is `cross-repo`: the closed runtime was reachable this time and was swept, not cited. REACHABILITY (#15543, standing reading): embedder-only — see this file's _note." + "note": "REMOVED 2026-09-27 (#20295) — tombstoned at the schema as ONE key (retiredKey carries the prescription; authoring it is a tsc error and a parse error, and RestServer construction refuses it with that prescription instead of copying it into this.config.api). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-api-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent). What to do instead: nothing to configure — response shapes are fixed: each route answers in the response schema packages/spec declares for it, which the client SDK parses and the served /openapi.json describes. The three child rows this container carried (`envelope` / `includeMetadata` / `includePagination`) collapse into this one row: the tombstone is a leaf, and rows for keys that left the walked shape would report ORPHAN (the `crud.patterns` precedent, commit b3a63d32c). Their pre-retirement verdicts, kept as the record — all three `dead`, 0 read sites at 31184e5d, the three blind shapes swept against their own controls: `envelope: false` unwrapped no response (the envelope is produced unconditionally); `includeMetadata` gated no response metadata (timestamp, requestId); `includePagination` gated nothing (list responses carry their pagination block regardless). Re-measured 2026-09-27 on origin/main 4e0f72e8 before the tombstone landed: `packages/**` non-test code carried 0 reads (the only code sites were NormalizedRestServerConfig's type declaration and normalizeConfig's own write), against a lit control on the same instrument (`enableOpenApi` finds its read at rest-server.ts#registerRoutes); objectui at its pin f8a9d0fb returned 0 for RestApiConfig / RestServerConfig / responseFormat / includePagination / enableOpenApi against a lit control (`basePath` = 184); cloud at 96eb092 returned 0 authoring sites for RestApiConfig / RestServerConfig / responseFormat / includePagination / `documentation.enabled`, against a lit control (`createRestApiPlugin` = 11 — every call site forwards the stack's own top-level `api:` block, whose schema carries neither key). So `evidenceScope` is `cross-repo`: the closed runtime was reachable this time and was swept, not cited. REACHABILITY (#15543, standing reading): embedder-only — see this file's _note." } } } diff --git a/packages/spec/liveness/route_generation.json b/packages/spec/liveness/route_generation.json index 52512de5cc0..0b6cc036bf0 100644 --- a/packages/spec/liveness/route_generation.json +++ b/packages/spec/liveness/route_generation.json @@ -1,30 +1,30 @@ { "type": "route_generation", - "_note": "RouteGenerationConfigSchema — packages/spec/src/api/rest-server.zod.ts#RouteGenerationConfigSchema, the `routes` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census filed with #14369, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Every key in this file is `dead`, and that is the finding: the whole sub-object is parsed, defaulted and normalized into `this.config.routes`, and nothing ever reads it back. `routes.excludeObjects: ['sys_log']` excludes nothing; `routes.nameTransform: 'plural'` still mounts every route under the object's raw name. This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. `routes.*` in particular reads as designed-but-never-wired: enforcing it is real work in route generation and changes the mounted surface. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness.", + "_note": "RouteGenerationConfigSchema — packages/spec/src/api/rest-server.zod.ts#RouteGenerationConfigSchema, the `routes` sub-object of RestServerConfig. It is not a metadata type, not a request body and not a manifest: it is part of the REST server's CONSTRUCTION ARGUMENT, so no registry has ever held it and no ratchet rooted in one could ask who reads it. The ledger governs it through the gate's SPEC_ONLY_SCHEMAS override, the same route `query` / `qa` / `manifest` take; check-liveness.mts carries the rationale, including why the four sub-objects are rooted separately instead of the whole RestServerConfigSchema (the walk drills one level, and rooting on the whole config would leave `metadata.endpoints.schema` and `batch.operations.upsertMany` with no row of their own). Seeded 2026-09-02 from the census recorded in commit a3d5724c8, which is the second half of #11984's measurement: that PR made RestServer.normalizeConfig PARSE and CONSUME this sub-object instead of casting it. That settles accept/reject — an out-of-enum or out-of-range value is now refused at construction instead of sitting in the normalized config as if it were declared — and that is ALL it settles. Executing a declared contract does not give a key a consumer, which is exactly the distinction this file records. Every key in this file is `dead`, and that is the finding: the whole sub-object is parsed, defaulted and normalized into `this.config.routes`, and nothing ever reads it back. `routes.excludeObjects: ['sys_log']` excludes nothing; `routes.nameTransform: 'plural'` still mounts every route under the object's raw name. This file RECORDS status; it decides nothing. The enforce-or-remove call per dead key (ADR-0049) is a follow-up on the human floor — the enforce route is a feature per key, and for a key that is published in an `@example` or in the generated reference docs the remove route is a capability retirement, not a tidy-up. `routes.*` in particular reads as designed-but-never-wired: enforcing it is real work in route generation and changes the mounted surface. Census method and scope, re-run at 2514d49f3 (2026-09-02): read sites in packages/rest/src non-test sources, excluding NormalizedRestServerConfig's type declaration and normalizeConfig itself (a key the normalizer writes into its own output is not thereby read); comments excluded; plus a repo-wide grep outside packages/spec and rest-server.ts, which finds only changesets, the generated reference docs and the #11984 refusal tests. objectui @d4c6a86 is clean (0 hits for every key here). The closed cloud runtime was not reachable from the measuring container, so the declared scope stays in-repo rather than claiming a sweep that was not run. AUTHOR-WARN CHANNEL: none exists for this type, and no entry here is marked `authorWarn` for that reason (`_authorWarnSkipped`). The CLI lint (packages/lint/src/lint-liveness-properties.ts) walks stack COLLECTIONS — `stack.flows`, `stack.views`, … — and a RestServerConfig is not part of a stack at all: it is the argument a host passes when it constructs the server. Marking an entry `authorWarn` here would produce a warning nothing can emit, which is the same silent no-op this ledger exists to catch, so the dead entries below carry their correction in `note` and the construction-time parse (#11984) is what actually reaches the author — for accept/reject, which is a different question from liveness.", "props": { "includeObjects": { "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); per-object API exposure is declared ON the object and enforced by the REST data surface (rest-server.ts#enforceApiAccess: `enable.apiEnabled: false` → 404, an `enable.apiMethods` whitelist → 405 for unlisted operations), so the capability this key promised already exists at its proper seat and the key was a second, unread dialect of it (Prime Directive #12: one strict contract, not N). Pre-retirement census note: 0 read sites at 2514d49f3. Normalized into `this.config.routes.includeObjects` and never consulted: route registration iterates the registered objects with no include filter, so an author who names three objects still gets routes for all of them. Dead at the CONSUMER — the authored value arrives intact and is simply never read. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); per-object API exposure is declared ON the object and enforced by the REST data surface (rest-server.ts#enforceApiAccess: `enable.apiEnabled: false` → 404, an `enable.apiMethods` whitelist → 405 for unlisted operations), so the capability this key promised already exists at its proper seat and the key was a second, unread dialect of it (Prime Directive #12: one strict contract, not N). Pre-retirement census note: 0 read sites at 2514d49f3. Normalized into `this.config.routes.includeObjects` and never consulted: route registration iterates the registered objects with no include filter, so an author who names three objects still gets routes for all of them. Dead at the CONSUMER — the authored value arrives intact and is simply never read. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." }, "excludeObjects": { "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); per-object API exposure is declared ON the object and enforced by the REST data surface (rest-server.ts#enforceApiAccess: `enable.apiEnabled: false` → 404, an `enable.apiMethods` whitelist → 405 for unlisted operations), so the capability this key promised already exists at its proper seat and the key was a second, unread dialect of it (Prime Directive #12: one strict contract, not N). The `@example` on RestServerConfigSchema that advertised `routes: { excludeObjects: ['system_log'] }` is corrected in the same change. Pre-retirement census note: 0 read sites at 2514d49f3. Same shape as `includeObjects`: normalized, never consulted, so an excluded object is still mounted. This key is the one customer-visible member of the ten: RestServerConfigSchema's own `@example` advertises `routes: { excludeObjects: ['system_log'] }`, and the generated reference page (content/docs/references/api/rest-server.mdx) repeats the declaration — published prose promising a capability the runtime does not deliver. Fixing that example belongs to whichever limb this key lands on, not here. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); per-object API exposure is declared ON the object and enforced by the REST data surface (rest-server.ts#enforceApiAccess: `enable.apiEnabled: false` → 404, an `enable.apiMethods` whitelist → 405 for unlisted operations), so the capability this key promised already exists at its proper seat and the key was a second, unread dialect of it (Prime Directive #12: one strict contract, not N). The `@example` on RestServerConfigSchema that advertised `routes: { excludeObjects: ['system_log'] }` is corrected in the same change. Pre-retirement census note: 0 read sites at 2514d49f3. Same shape as `includeObjects`: normalized, never consulted, so an excluded object is still mounted. This key is the one customer-visible member of the ten: RestServerConfigSchema's own `@example` advertises `routes: { excludeObjects: ['system_log'] }`, and the generated reference page (content/docs/references/api/rest-server.mdx) repeats the declaration — published prose promising a capability the runtime does not deliver. Fixing that example belongs to whichever limb this key lands on, not here. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." }, "nameTransform": { "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the object `name` is the canonical id on every surface, the REST path segment included (Prime Directive #6), so a URL transform contradicts the one-name rule and was never a candidate for enforcement; the enum was validated (since #11984) and every value mounted what `'none'` mounts. Pre-retirement census note: 0 read sites at 2514d49f3. Repo-wide there are no reads outside packages/spec and rest-server.ts either. The enum is enforced at the door since #11984 — `'snake_case'` is refused at construction — but no route builder transforms a name, so `'plural'`, `'kebab-case'` and `'camelCase'` all mount exactly what `'none'` mounts. An enum that is validated and then ignored is the clearest case in this file of accept/reject and liveness being different questions. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the object `name` is the canonical id on every surface, the REST path segment included (Prime Directive #6), so a URL transform contradicts the one-name rule and was never a candidate for enforcement; the enum was validated (since #11984) and every value mounted what `'none'` mounts. Pre-retirement census note: 0 read sites at 2514d49f3. Repo-wide there are no reads outside packages/spec and rest-server.ts either. The enum is enforced at the door since #11984 — `'snake_case'` is refused at construction — but no route builder transforms a name, so `'plural'`, `'kebab-case'` and `'camelCase'` all mount exactly what `'none'` mounts. An enum that is validated and then ignored is the clearest case in this file of accept/reject and liveness being different questions. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." }, "overrides": { "status": "dead", "verifiedAt": "2026-09-03", "evidenceScope": "cross-repo", - "note": "REMOVED 2026-09-03 (#14691) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); `enabled` and `operations` duplicated the object's own enforced exposure keys (`enable.apiEnabled` / `enable.apiMethods`, rest-server.ts#enforceApiAccess) and `basePath` per object would contradict the one deployment-wide data base (`crud.dataPrefix`) the discovery document advertises as `routes.data` — so the family resolved to REMOVE, not to the ENFORCE split triage held open for it. The three child rows this container carried (`enabled` / `basePath` / `operations`) collapse into this one row: the tombstone is a leaf, and rows for keys that left the walked shape would report ORPHAN. Closes #14365's question about `overrides.*.operations` — no record left. Pre-retirement census note: 0 read sites at 2514d49f3. The per-object `overrides` record is normalized and never read, so no object's routes can be turned off through it. ⚠️ Do not read the `overrides` hits in packages/rest/src as consumers of this key: rest-server.ts#registerDataActionEndpoints reads `body.overrides` off a REQUEST BODY, and http-request-test-builder.ts takes an `overrides` argument — different keys with the same name. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." + "note": "REMOVED 2026-09-03 (commit b3a63d32c) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error). No source is stripped by a conversion: a RestServerConfig is plugin TS configuration, never a stack collection member or a sys_metadata row (the `RestServerConfig.openApi31` precedent, #4579), so the D3 entry `rest-server-config-dead-keys-retired` carries the prescription. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); `enabled` and `operations` duplicated the object's own enforced exposure keys (`enable.apiEnabled` / `enable.apiMethods`, rest-server.ts#enforceApiAccess) and `basePath` per object would contradict the one deployment-wide data base (`crud.dataPrefix`) the discovery document advertises as `routes.data` — so the family resolved to REMOVE, not to the ENFORCE split triage held open for it. The three child rows this container carried (`enabled` / `basePath` / `operations`) collapse into this one row: the tombstone is a leaf, and rows for keys that left the walked shape would report ORPHAN. Closes the `z.partialRecord` question (commit f60ab90ae) on `overrides.*.operations` — no record left. Pre-retirement census note: 0 read sites at 2514d49f3. The per-object `overrides` record is normalized and never read, so no object's routes can be turned off through it. ⚠️ Do not read the `overrides` hits in packages/rest/src as consumers of this key: rest-server.ts#registerDataActionEndpoints reads `body.overrides` off a REQUEST BODY, and http-request-test-builder.ts takes an `overrides` argument — different keys with the same name. Scope widened 2026-09-03: in-repo census at 2514d49f3 (0 read sites) + objectui @d4c6a86 clean + cloud @9b6abe0f2fd5 clean STRUCTURALLY (#14796: cloud never authors a `RestServerConfig`), so `evidenceScope` is `cross-repo`." } } } diff --git a/packages/spec/liveness/seed.json b/packages/spec/liveness/seed.json index 3fcca86f4c7..b3dfac08582 100644 --- a/packages/spec/liveness/seed.json +++ b/packages/spec/liveness/seed.json @@ -1,24 +1,24 @@ { "type": "seed", - "_note": "SeedSchema. Live throughout. `locale` was the one `experimental` row and 2026-09-09 (#16595) closed it, on the producer side — see that row. Consumer: SeedLoaderService (packages/metadata-protocol/src/seed-loader.ts), reached on BOTH authoring paths: (1) boot/replay — the stack's `data:` collection lands in `manifest.data`, app-plugin.ts normalizes it and calls seedLoader.load() (packages/runtime/src/app-plugin.ts:832, :971), plus the per-org replayer registered for tenant provisioning; (2) runtime drafts — publishMetaItem applies a published `seed` draft through the same loader (packages/metadata-protocol/src/protocol.ts:6764, `skipSeedApply` opt-out for package batches). Seeded 2026-08-01 (#4488). 2026-08-28 (#13003): all six `path:NNN` citations in this file — five `evidence`, one `producer` — were re-anchored to their consuming symbols. Every one was wrong and every one was IN RANGE, in a 2,680-line file.", + "_note": "SeedSchema. Live throughout. `locale` was the one `experimental` row and 2026-09-09 (#16595) closed it, on the producer side — see that row. Consumer: SeedLoaderService (packages/metadata-protocol/src/seed-loader.ts), reached on BOTH authoring paths: (1) boot/replay — the stack's `data:` collection lands in `manifest.data`, app-plugin.ts normalizes it and calls seedLoader.load() (packages/runtime/src/app-plugin.ts:832, :971), plus the per-org replayer registered for tenant provisioning; (2) runtime drafts — publishMetaItem applies a published `seed` draft through the same loader (packages/metadata-protocol/src/protocol.ts:6764, `skipSeedApply` opt-out for package batches). Seeded 2026-08-01 (#4488). 2026-08-28 (commit 8f10a79f7): all six `path:NNN` citations in this file — five `evidence`, one `producer` — were re-anchored to their consuming symbols. Every one was wrong and every one was IN RANGE, in a 2,680-line file.", "props": { "object": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/metadata-protocol/src/seed-loader.ts#loadDataset (`const objectName = dataset.object` — the write target; the same key is the dependency-graph node and the per-object definition-scan key one level up in `load`)", - "note": "target object; also the dependency-graph node key (topological insert order). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:98` had rotted onto `function quotableSeedFailureDetail`, a client-refusal diagnostic helper ~484 lines above `loadDataset`. Re-closed by hand against 8cb96ec41." + "note": "target object; also the dependency-graph node key (topological insert order). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:98` had rotted onto `function quotableSeedFailureDetail`, a client-refusal diagnostic helper ~484 lines above `loadDataset`. Re-closed by hand against 8cb96ec41." }, "externalId": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/metadata-protocol/src/seed-loader.ts#loadDataset (`const externalId = dataset.externalId || 'name'` — the natural key threaded into every existence read and write decision); packages/metadata-protocol/src/seed-loader.ts#decideWriteAction (insert vs update turns on the row found under that key)", - "note": "upsert/uniqueness key, single or composite (framework#3434 join tables); also what OTHER datasets' reference values resolve against (buildReferenceMap threads it into the DB probe). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:119` had rotted onto a docblock sentence about the two failure-detail passes sharing one vocabulary, ~465 lines above the read. `buildReferenceMap` in the note is unchanged and still real; the anchors name where the key itself is read. Re-closed by hand against 8cb96ec41." + "note": "upsert/uniqueness key, single or composite (framework#3434 join tables); also what OTHER datasets' reference values resolve against (buildReferenceMap threads it into the DB probe). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:119` had rotted onto a docblock sentence about the two failure-detail passes sharing one vocabulary, ~465 lines above the read. `buildReferenceMap` in the note is unchanged and still real; the anchors name where the key itself is read. Re-closed by hand against 8cb96ec41." }, "mode": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/metadata-protocol/src/seed-loader.ts#loadDataset (`const mode = dataset.mode || config.defaultMode`); packages/metadata-protocol/src/seed-loader.ts#decideWriteAction (insert/update/upsert/replace/ignore, decided against the existing row)", - "note": "insert/update/upsert/replace/ignore — drives decideWriteAction/writeRecord. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:245` had rotted onto `export class SeedLoaderService`, the class declaration rather than any read of the key. Re-closed by hand against 8cb96ec41." + "note": "insert/update/upsert/replace/ignore — drives decideWriteAction/writeRecord. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:245` had rotted onto `export class SeedLoaderService`, the class declaration rather than any read of the key. Re-closed by hand against 8cb96ec41." }, "env": { "status": "live", @@ -26,7 +26,7 @@ "evidenceScope": "in-repo", "evidence": "packages/metadata-protocol/src/seed-loader.ts#datasetAllowsEnv (`const declared = dataset.env` — the one read of the key; a dataset carrying no `env` is unrestricted, which is exactly what `SeedSchema.env`'s default parses to); packages/metadata-protocol/src/seed-loader.ts#filterByEnv (drops the datasets it excludes, and always NAMES what it dropped)", "producer": "packages/metadata-protocol/src/seed-loader.ts#resolveEnvConfig (`load()` resolves the comparison environment ITSELF — `resolveSeedEnvFromNodeEnv` off NODE_ENV — before anything reads config, rather than trusting a caller to pass it, and warns by name when it cannot and env-scoped datasets exist)", - "note": "THE SPECIMEN THIS FIELD EXISTS FOR (#4837). Until #4704 this row was `live` on the consumer pointer alone and the verdict was FALSE: the cited line really did call filterByEnv, but none of the SIX call sites that build a SeedLoaderRequest (app boot, per-org replay, hot reload, package apply, draft publish, marketplace install) passed `env` — so `config.env` was permanently undefined, filterByEnv returned its input on its first line, and `dataset.env` was never read at all. `seed-loader.test.ts` passed throughout, because it supplies `config.env` itself: it exercised a mechanism nothing fed. #4704 fixed the wiring INSIDE `load()`, the one funnel every seeding path goes through, so call site seven cannot reopen the hole. Re-verified 2026-08-09 with both sides cited. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, BOTH halves. The evidence line `:191` had rotted onto `datasetAllowsEnv`'s own DOCBLOCK — the read was extracted out of `filterByEnv` into that helper — and the producer line `:174` onto `resolveSeedEnvFromNodeEnv`'s docblock while its parenthetical named `resolveEnvConfig, :1809`: a real symbol beside a line ~2,140 lines away from it. The specimen this row exists for (#4837) is unchanged; what had rotted is only where its two halves point, which is the failure the two-half shape was built to make visible. Re-closed by hand against 8cb96ec41." + "note": "THE SPECIMEN THIS FIELD EXISTS FOR (#4837). Until #4704 this row was `live` on the consumer pointer alone and the verdict was FALSE: the cited line really did call filterByEnv, but none of the SIX call sites that build a SeedLoaderRequest (app boot, per-org replay, hot reload, package apply, draft publish, marketplace install) passed `env` — so `config.env` was permanently undefined, filterByEnv returned its input on its first line, and `dataset.env` was never read at all. `seed-loader.test.ts` passed throughout, because it supplies `config.env` itself: it exercised a mechanism nothing fed. #4704 fixed the wiring INSIDE `load()`, the one funnel every seeding path goes through, so call site seven cannot reopen the hole. Re-verified 2026-08-09 with both sides cited. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED, BOTH halves. The evidence line `:191` had rotted onto `datasetAllowsEnv`'s own DOCBLOCK — the read was extracted out of `filterByEnv` into that helper — and the producer line `:174` onto `resolveSeedEnvFromNodeEnv`'s docblock while its parenthetical named `resolveEnvConfig, :1809`: a real symbol beside a line ~2,140 lines away from it. The specimen this row exists for (#4837) is unchanged; what had rotted is only where its two halves point, which is the failure the two-half shape was built to make visible. Re-closed by hand against 8cb96ec41." }, "locale": { "status": "live", @@ -40,7 +40,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/metadata-protocol/src/seed-loader.ts#loadDataset (`for (let i = 0; i < dataset.records.length; i++)` — the payload rows, written one at a time so a failure can name the row that caused it)", - "note": "the payload rows. WALK BOUNDARY: each record is a z.record — the keys an author actually writes are the TARGET OBJECT's field names, governed by that object's own field definitions (and the defineSeed factory's compile-time key check), not by this ledger. Recorded here rather than left implicit, per the datasource `config` precedent. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:434` had rotted onto the `referenceVia` pointer-pair scan inside `load`, ~384 lines above the row loop. Re-closed by hand against 8cb96ec41." + "note": "the payload rows. WALK BOUNDARY: each record is a z.record — the keys an author actually writes are the TARGET OBJECT's field names, governed by that object's own field definitions (and the defineSeed factory's compile-time key check), not by this ledger. Recorded here rather than left implicit, per the datasource `config` precedent. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:434` had rotted onto the `referenceVia` pointer-pair scan inside `load`, ~384 lines above the row loop. Re-closed by hand against 8cb96ec41." } } } diff --git a/packages/spec/liveness/skill.json b/packages/spec/liveness/skill.json index 50e2209de6f..ce712f179bd 100644 --- a/packages/spec/liveness/skill.json +++ b/packages/spec/liveness/skill.json @@ -1,6 +1,6 @@ { "type": "skill", - "_note": "SkillSchema. Seeded from docs/audits/2026-06-skillschema-property-liveness.md. skill-registry.ts + agent-runtime.ts are the runtime consumers. ⚠ EVIDENCE LIVES IN CLOUD/EE: the `cloud @: packages/service-ai/...` paths cited below are the closed `@objectstack/service-ai` runtime in the CLOUD repo, NOT git-tracked framework code. 2026-08-30 (#13272): the previous wording of this sentence was false in BOTH halves. (a) It called the framework's own service-ai tree “a stale build artifact with no src/” — there is no tree at all: `packages/services/` holds every sibling service EXCEPT service-ai, and `git ls-files | grep -ic service-ai` returns 0. Absent, not stale. (b) The citations spelled `packages/services/service-ai/...`, a path present in NEITHER repo; cloud's real layout, measured at cloud@15f55df (#13042), is `packages/service-ai/...`. Every citation below now carries the explicit `cloud` realm marker, so it is attributed by the marker `scanEvidence` reads rather than riding the `FOREIGN_PATH_PREFIXES` special case that silently exempted the stale spelling from resolution — which is why 22 dead pointers sat green. ✅ RE-VERIFIED 2026-09-15 (#13272): the re-verification half this entry parked is discharged. All eight cloud consumers cited below were re-read in a cloud checkout at cloud @cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8 and all eight are CONFIRMED; every row now carries `verifiedAt`, `evidenceScope: cross-repo`, and a `#symbol` anchor instead of a line number. The `verifiedAt: 2026-08-06` these eight used to carry was worse than no stamp at all — it was taken while the citation still named `packages/services/service-ai/…`, a path present in NEITHER repo, so it was false confidence that the 180-day staleness clock in verification.mts is structurally unable to see: 40 days old, never flagged, resting on nothing. The anchors are the load-bearing half, and they are why the DATE matters: `scanEvidence` never collects an anchor while a foreign realm marker is in force, so CI cannot re-derive a single one of the cloud anchors — each rests on that dated reading alone. ⛔ Never re-stamp `verifiedAt` here without re-reading cloud; a bare re-stamp restores exactly the unfalsifiable pointer this entry's history is made of. The FRAMEWORK half is the one CI can falsify, and it is now written so it does: `packages/mcp/src/skill-prompts.ts#projectSkillPrompt` sits after a `;`, which ends the cloud realm's scope, so the anchor is LOCAL and `checkEvidenceAnchors` resolves it against the file on every run — where the old parenthesised `(projectSkillPrompt)` was prose the gate never read. ⚠ `instructions` used to cite `(skillPromptResult)`, one hop too far downstream: that function consumes the PROJECTED value, while the record's own `instructions` key is read in `projectSkillPrompt`, which is what the anchor now names. 2026-08-06 (#3905): that used to be the WHOLE story — the open framework consumed nothing here. It now consumes the INSTRUCTIONS half: `packages/mcp/src/skill-prompts.ts` projects every active skill carrying `instructions` onto the MCP `prompts` primitive (name/label/description/instructions/active are read there, in-repo and testable). The TOOL-BINDING half (`tools`, `surface`, `triggerConditions`) stays cloud-runtime-only and now says so in the schema's own JSDoc. See content/docs/ai for the open/cloud boundary. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent.", + "_note": "SkillSchema. Seeded from docs/audits/2026-06-skillschema-property-liveness.md. skill-registry.ts + agent-runtime.ts are the runtime consumers. ⚠ EVIDENCE LIVES IN CLOUD/EE: the `cloud @: packages/service-ai/...` paths cited below are the closed `@objectstack/service-ai` runtime in the CLOUD repo, NOT git-tracked framework code. 2026-08-30 (#13272): the previous wording of this sentence was false in BOTH halves. (a) It called the framework's own service-ai tree “a stale build artifact with no src/” — there is no tree at all: `packages/services/` holds every sibling service EXCEPT service-ai, and `git ls-files | grep -ic service-ai` returns 0. Absent, not stale. (b) The citations spelled `packages/services/service-ai/...`, a path present in NEITHER repo; cloud's real layout, measured at cloud@15f55df (commit c19035e97), is `packages/service-ai/...`. Every citation below now carries the explicit `cloud` realm marker, so it is attributed by the marker `scanEvidence` reads rather than riding the `FOREIGN_PATH_PREFIXES` special case that silently exempted the stale spelling from resolution — which is why 22 dead pointers sat green. ✅ RE-VERIFIED 2026-09-15 (#13272): the re-verification half this entry parked is discharged. All eight cloud consumers cited below were re-read in a cloud checkout at cloud @cb8ee7ff60c097cc21a584fe9caf8ef4391cc0e8 and all eight are CONFIRMED; every row now carries `verifiedAt`, `evidenceScope: cross-repo`, and a `#symbol` anchor instead of a line number. The `verifiedAt: 2026-08-06` these eight used to carry was worse than no stamp at all — it was taken while the citation still named `packages/services/service-ai/…`, a path present in NEITHER repo, so it was false confidence that the 180-day staleness clock in verification.mts is structurally unable to see: 40 days old, never flagged, resting on nothing. The anchors are the load-bearing half, and they are why the DATE matters: `scanEvidence` never collects an anchor while a foreign realm marker is in force, so CI cannot re-derive a single one of the cloud anchors — each rests on that dated reading alone. ⛔ Never re-stamp `verifiedAt` here without re-reading cloud; a bare re-stamp restores exactly the unfalsifiable pointer this entry's history is made of. The FRAMEWORK half is the one CI can falsify, and it is now written so it does: `packages/mcp/src/skill-prompts.ts#projectSkillPrompt` sits after a `;`, which ends the cloud realm's scope, so the anchor is LOCAL and `checkEvidenceAnchors` resolves it against the file on every run — where the old parenthesised `(projectSkillPrompt)` was prose the gate never read. ⚠ `instructions` used to cite `(skillPromptResult)`, one hop too far downstream: that function consumes the PROJECTED value, while the record's own `instructions` key is read in `projectSkillPrompt`, which is what the anchor now names. 2026-08-06 (#3905): that used to be the WHOLE story — the open framework consumed nothing here. It now consumes the INSTRUCTIONS half: `packages/mcp/src/skill-prompts.ts` projects every active skill carrying `instructions` onto the MCP `prompts` primitive (name/label/description/instructions/active are read there, in-repo and testable). The TOOL-BINDING half (`tools`, `surface`, `triggerConditions`) stays cloud-runtime-only and now says so in the schema's own JSDoc. See content/docs/ai for the open/cloud boundary. 2026-07-30 (#3896 close-out sweep): the dead authoring keys were REMOVED — tombstoned at the schema with prescriptions (retiredKey) and stripped by the protocol-17 close-out conversions; entries deleted per the #3715 precedent.", "props": { "name": { "status": "live", diff --git a/packages/spec/liveness/tool.json b/packages/spec/liveness/tool.json index dd51a04cf80..3db6face3b0 100644 --- a/packages/spec/liveness/tool.json +++ b/packages/spec/liveness/tool.json @@ -1,6 +1,6 @@ { "type": "tool", - "_note": "ToolSchema. Seeded from docs/audits/2026-06-toolschema-property-liveness.md. Tool metadata is not an execution entry point — the RUNTIME uses a separate AIToolDefinition, and most props here are live via that same-named surface. ⚠ 2026-08-29: the framing this note carried for years, 'WRITE-ONLY … not metadata read-back', is FALSIFIED and was actively harmful — it is why nobody looked at the renderer. objectui's metadata-admin preview IS a registered consumer of persisted `tool` records (objectui @26896c6, `registerMetadataPreview('tool', ToolPreview)`), and ToolPreview reads ALL SIX props off the stored record: name, label, description, objectName, parameters, outputSchema. So there are TWO live bases on this type, and each row says which one it rests on: the AIToolDefinition surface (behavioural) and the metadata read-back (display, the #7131 rule). `objectName` is the row that turns on the difference — it has NO behavioural reader anywhere and is live on the read-back alone. ⚠ EVIDENCE LIVES MOSTLY IN CLOUD: every `cloud`-attributed path below is the closed `@objectstack/service-ai` / `@objectstack/service-ai-studio` runtime in the CLOUD repo, not git-tracked here — see content/docs/ai for the open/cloud boundary. 2026-08-29 RE-CLOSED against cloud `origin/main@15f55df`, the first pass run from a container that actually had a cloud checkout in reach; three things the old note asserted turned out to be false, and each failed in its own way. (a) It said the framework's own service-ai tree 'is a stale build artifact with no src/'. That was stale twice over and is now simply FALSE — the tree is ABSENT: `git ls-files` matches 0 paths containing service-ai, no such directory exists on disk, and `packages/services/` holds 16 members, none of them service-ai. There is no artifact left to be stale. (b) The cited path prefix was wrong for the CLOUD repo too. Cloud's real layout is `packages/service-ai/…` and `packages/service-ai-studio/…`, never `packages/services/service-ai/…`, so the five citations this file used to carry named a path that exists in NEITHER repo — unfalsifiable pointers, which is exactly what the README says a `live` verdict must never rest on. ⚠ MECHANICAL TRAP for anyone repointing a sibling ledger: `scripts/liveness/evidence.mts` hardcodes only `packages/services/service-ai/` in FOREIGN_PATH_PREFIXES, which is why the WRONG spelling sat green for so long; the REAL cloud path is repo-rooted in shape and is NOT in that list, so an unmarked repoint resolves as LOCAL and fails CI. Every citation of it MUST carry the `cloud` realm marker. The stale spelling and this note's sentence (a) both survive in action.json (3 entries), agent.json (11) and skill.json (8) — filed as #13272. (c) 'the OPEN framework edition does not consume them' is FALSE for `name` and `description`: `packages/mcp` bridges each AIToolDefinition onto an MCP server and reads both. Those two entries now carry framework-local ANCHORED evidence, so CI can falsify them from this checkout — the condition #13042 was filed for. (d) the write-only claim above, corrected in the same pass once an objectui checkout came into reach. Note the pattern across all four: every one of them was a NEGATIVE claim ('no src', 'not framework code', 'the open edition does not', 'not read back') that no gate could falsify, and three of the four were wrong. The realms named in each `evidenceScope` are the realms actually walked — cloud @15f55df and objectui @26896c6 — and never 'everywhere'. 2026-07-30 (#3896 close-out): the four inert authoring keys (category/permissions/active/builtIn) were REMOVED from ToolSchema — permissions promised an invocation gate nothing enforced, active:false withdrew nothing; strict-rejected with prescriptions (TOOL_RETIRED_KEY_GUIDANCE) and stripped by the tool-inert-authoring-keys-removed conversion. Entries deleted per the requiresConfirmation precedent (#3715).", + "_note": "ToolSchema. Seeded from docs/audits/2026-06-toolschema-property-liveness.md. Tool metadata is not an execution entry point — the RUNTIME uses a separate AIToolDefinition, and most props here are live via that same-named surface. ⚠ 2026-08-29: the framing this note carried for years, 'WRITE-ONLY … not metadata read-back', is FALSIFIED and was actively harmful — it is why nobody looked at the renderer. objectui's metadata-admin preview IS a registered consumer of persisted `tool` records (objectui @26896c6, `registerMetadataPreview('tool', ToolPreview)`), and ToolPreview reads ALL SIX props off the stored record: name, label, description, objectName, parameters, outputSchema. So there are TWO live bases on this type, and each row says which one it rests on: the AIToolDefinition surface (behavioural) and the metadata read-back (display, the #7131 rule). `objectName` is the row that turns on the difference — it has NO behavioural reader anywhere and is live on the read-back alone. ⚠ EVIDENCE LIVES MOSTLY IN CLOUD: every `cloud`-attributed path below is the closed `@objectstack/service-ai` / `@objectstack/service-ai-studio` runtime in the CLOUD repo, not git-tracked here — see content/docs/ai for the open/cloud boundary. 2026-08-29 RE-CLOSED against cloud `origin/main@15f55df`, the first pass run from a container that actually had a cloud checkout in reach; three things the old note asserted turned out to be false, and each failed in its own way. (a) It said the framework's own service-ai tree 'is a stale build artifact with no src/'. That was stale twice over and is now simply FALSE — the tree is ABSENT: `git ls-files` matches 0 paths containing service-ai, no such directory exists on disk, and `packages/services/` holds 16 members, none of them service-ai. There is no artifact left to be stale. (b) The cited path prefix was wrong for the CLOUD repo too. Cloud's real layout is `packages/service-ai/…` and `packages/service-ai-studio/…`, never `packages/services/service-ai/…`, so the five citations this file used to carry named a path that exists in NEITHER repo — unfalsifiable pointers, which is exactly what the README says a `live` verdict must never rest on. ⚠ MECHANICAL TRAP for anyone repointing a sibling ledger: `scripts/liveness/evidence.mts` hardcodes only `packages/services/service-ai/` in FOREIGN_PATH_PREFIXES, which is why the WRONG spelling sat green for so long; the REAL cloud path is repo-rooted in shape and is NOT in that list, so an unmarked repoint resolves as LOCAL and fails CI. Every citation of it MUST carry the `cloud` realm marker. The stale spelling and this note's sentence (a) both survive in action.json (3 entries), agent.json (11) and skill.json (8) — filed as #13272. (c) 'the OPEN framework edition does not consume them' is FALSE for `name` and `description`: `packages/mcp` bridges each AIToolDefinition onto an MCP server and reads both. Those two entries now carry framework-local ANCHORED evidence, so CI can falsify them from this checkout — the condition this re-close (commit c19035e97) was run to meet. (d) the write-only claim above, corrected in the same pass once an objectui checkout came into reach. Note the pattern across all four: every one of them was a NEGATIVE claim ('no src', 'not framework code', 'the open edition does not', 'not read back') that no gate could falsify, and three of the four were wrong. The realms named in each `evidenceScope` are the realms actually walked — cloud @15f55df and objectui @26896c6 — and never 'everywhere'. 2026-07-30 (#3896 close-out): the four inert authoring keys (category/permissions/active/builtIn) were REMOVED from ToolSchema — permissions promised an invocation gate nothing enforced, active:false withdrew nothing; strict-rejected with prescriptions (TOOL_RETIRED_KEY_GUIDANCE) and stripped by the tool-inert-authoring-keys-removed conversion. Entries deleted per the requiresConfirmation precedent (#3715).", "props": { "name": { "status": "live", @@ -16,7 +16,7 @@ "evidenceScope": "cross-repo", "evidence": "cloud @15f55df: packages/service-ai-studio/src/plugin.ts#toToolLabel — the boot-time Studio ingest inside AIStudioPlugin.init reads the label off each AIToolDefinition, falls back to a name-derived one when unset, and persists the result as the tool metadata record; that call is the ONLY site in the whole cloud repo that registers tool metadata. objectui @26896c6: packages/app-shell/src/views/metadata-admin/previews/ToolPreview.tsx#ToolPreview then reads that persisted label back and renders it as the preview header, with the tool's machine name as fallback", "producer": "cloud @15f55df: packages/service-ai/src/tools/data-tools.ts and packages/service-ai-studio/src/tools/apply-edit.tool.ts are representative of the shipped definition literals that set it. Cited for REACHABILITY rather than for a missing supplier — the read point runs unconditionally at plugin boot, and its toToolLabel fallback means even an unset value still changes what is persisted", - "note": "display, and it IS displayed — the #7131 display-key rule, met at the projection rather than at a preview. RE-CLOSED 2026-08-29 (cloud @15f55df) and given its FIRST evidence pointer: this entry had carried a bare `live` with no `evidence` field at all since seeding, which is why it was the one row #13042's census could not even call stale — a claim with nothing to rot. Both sides of the round trip are now cited: cloud reads the label off the AIToolDefinition to compose the persisted record, and objectui's registered metadata-admin preview reads that record back and renders it. (An earlier pass of this re-closure recorded the read-back as un-walked for want of an objectui checkout; the walk has since been taken, and this row no longer carries that hedge.)" + "note": "display, and it IS displayed — the #7131 display-key rule, met at the projection rather than at a preview. RE-CLOSED 2026-08-29 (cloud @15f55df) and given its FIRST evidence pointer: this entry had carried a bare `live` with no `evidence` field at all since seeding, which is why it was the one row the census that prompted this re-close (commit c19035e97) could not even call stale — a claim with nothing to rot. Both sides of the round trip are now cited: cloud reads the label off the AIToolDefinition to compose the persisted record, and objectui's registered metadata-admin preview reads that record back and renders it. (An earlier pass of this re-closure recorded the read-back as un-walked for want of an objectui checkout; the walk has since been taken, and this row no longer carries that hedge.)" }, "description": { "status": "live", @@ -31,7 +31,7 @@ "verifiedAt": "2026-08-29", "evidenceScope": "cross-repo", "evidence": "cloud @15f55df: packages/service-ai/src/adapters/vercel-adapter.ts#buildVercelOptions wraps it with the SDK jsonSchema helper and passes it as the tool's inputSchema, which is what constrains the arguments the model may emit", - "note": "LLM function schema. Re-closed 2026-08-29 against cloud @15f55df. The verdict itself never moved — this row was `live` throughout on the cloud LLM path alone (vercel-adapter.ts#buildVercelOptions), which is what the asymmetry below was measured against. ⚠ 2026-08-30 (#13345): the asymmetry this note used to record — registerToolFromDefinition registered each bridged tool with NO inputSchema, so this key reached the cloud LLM but not an MCP client, contradicting its own docblock — is CLOSED, by #13317 (`e29fc212`, merged 2026-08-30T04:42:09Z, filed as #13271). `packages/mcp/src/mcp-server-runtime.ts#toolInputSchema` (L223-242) now converts `AIToolDefinition.parameters` through zod@4's `fromJSONSchema`, and `registerToolFromDefinition` (L998) forwards the result as the SDK `inputSchema` (L1005), which the SDK converts straight back to JSON Schema for `tools/list` — the key reaches MCP clients too, now on both consumers. The pre-fix behaviour was NOT silence, and the replaced wording overstated it as such: a schema-less registration made the SDK synthesise `EMPTY_OBJECT_JSON_SCHEMA` (`{\"type\":\"object\",\"properties\":{}}`) — a positive claim that the tool takes NO arguments, sharper than an absent key would have been. #13317 closes the asymmetry, not the grade." + "note": "LLM function schema. Re-closed 2026-08-29 against cloud @15f55df. The verdict itself never moved — this row was `live` throughout on the cloud LLM path alone (vercel-adapter.ts#buildVercelOptions), which is what the asymmetry below was measured against. ⚠ 2026-08-30 (commit ececf7a21): the asymmetry this note used to record — registerToolFromDefinition registered each bridged tool with NO inputSchema, so this key reached the cloud LLM but not an MCP client, contradicting its own docblock — is CLOSED, by commit e29fc212c (merged 2026-08-30T04:42:09Z, filed as #13271). `packages/mcp/src/mcp-server-runtime.ts#toolInputSchema` (L223-242) now converts `AIToolDefinition.parameters` through zod@4's `fromJSONSchema`, and `registerToolFromDefinition` (L998) forwards the result as the SDK `inputSchema` (L1005), which the SDK converts straight back to JSON Schema for `tools/list` — the key reaches MCP clients too, now on both consumers. The pre-fix behaviour was NOT silence, and the replaced wording overstated it as such: a schema-less registration made the SDK synthesise `EMPTY_OBJECT_JSON_SCHEMA` (`{\"type\":\"object\",\"properties\":{}}`) — a positive claim that the tool takes NO arguments, sharper than an absent key would have been. Commit e29fc212c closes the asymmetry, not the grade." }, "objectName": { "status": "live", diff --git a/packages/spec/liveness/translation.json b/packages/spec/liveness/translation.json index 9b4401c242c..ee8f4eb4721 100644 --- a/packages/spec/liveness/translation.json +++ b/packages/spec/liveness/translation.json @@ -1,6 +1,6 @@ { "type": "translation", - "_note": "TranslationItemSchema (#3778 — one locale's translations, the SAME groups the file-authored bundles use). NO LONGER A PIPE: the schema was a z.preprocess wrapping the retired object-first-dialect guard, which the gate's walker could not see through until #4488 fixed unwrap() to take the OUT side of a transform-input pipe — `translation` was literally unwalkable before this ledger. #4001 closed the shape with `.strict()` and folded the guard's ten prescriptions into the unknown-key `guidance`, so the preprocess is gone and the registered schema is a plain strict object. Consumer chain: runtime-authored items sync into the i18n adapter's authored layer (packages/core/src/fallbacks/authored-translation-sync.ts — at kernel:ready, on metadata:reloaded, and on translation mutations; #2591 closed the publish dead-end), file bundles load via service-i18n; both merge into ONE tree read by the spec resolvers (packages/spec/src/system/i18n-resolver.ts), the REST localization layer (translateMetaItem/translateMetaTypes), objectui's client resolvers (useObjectLabel/useSettingsLabel), and plugin-audit's summary localizer. WALK BOUNDARY: every group but `settingsCommon` is a z.record keyed by target names — the drill sees each record's VALUE shape one level; the deeper per-key conventions (objects..fields..label, apps..navigation..label, …) are governed by the resolvers cited per row, not by ledger rows. `settingsCommon` is a fixed strictObject with no target names, so the drill's one level lands on its own named member `sourceLabels` — itself a fixed strictObject whose keys are the ADR-0010 resolution layers (`env`, `global`, `tenant`, `user`, `default`), a closed set the schema holds closed: the retired spellings (`org`/`workspace`, `system`, `fallback`, `environment`) are rejected with a pointer to the layer each meant, never accepted as a layer. Those layer keys sit below the boundary: they are read as one unit by `resolveSettingsSourceLabel` (packages/spec/src/system/i18n-resolver.ts) and objectui's `useSettingsLabel().sourceLabel`, and the blanket verdict `settingsCommon` carries over them is the DECLARED kind — `translation/settingsCommon` is a row of scripts/liveness/undrilled-containers.baseline.json, the same standing as the record groups whose value shapes are not drilled. Note also the sync merges the RAW stored payload (authored-translation-sync.ts:155, not a schema re-parse), so the declared groups below are the CONTRACT while undeclared keys technically flow through on rows already stored — the resolvers read only the declared conventions. Since #4001 no NEW row can acquire one: the metadata door rejects an undeclared key instead of stripping it, so that residue is a finite set that only shrinks. `settings` is NOT a group of this ledger any more: its row was DELETED 2026-09-24 (#19620, ruling batch #210 item 2 letter B) by the strict-delete route — TranslationItemSchema no longer declares it and refuses it by name with the platform-only prescription, so the key left the walked shape and a surviving row would be an ORPHAN. That row was `live` on evidence reading the SERVED tree (objectui useSettingsLabel), which the PLATFORM bundle feeds; the deletion retires the key from this item ledger ONLY and says nothing about the platform capability, which stays declared on PlatformTranslationDataSchema (not this ledger’s subject) and read by the resolveSettings* family. Stored rows written before the door closed are converted, not read raw: authored-translation-sync replays the ADR-0087 chain (translation-per-app-settings-removed) over each row before merging it, which retires the `settings` clause of the RAW-payload note above for that key. Every group but `flows` is live — `flows` is the one that is `planned`, and `datasets` was seeded LIVE and DRILLED by #14253 with its reader (`translateDataset`) in the same change. A BOUNDARY and not a total, deliberately: the per-prop rows below carry the verdicts, the generated `state-counts/translation.md` carries this type's totals (#7377), and `props` holds the groups PLUS `locale` and the item-identity keys `name`/`label` — so no total taken over `props` is a total of groups, which is how both totals this sentence has carried came to be wrong. ⚠️ It first read \"10 of 11 groups live; the one dead group (`validationMessages`) is pointed at by #3778's own legacy-key migration table, making it a shipped false signpost\" — describing a group REMOVED in 17.0.0 (#4667), i.e. prose outliving its subject in the header of the very file whose rows warn about that; corrected 2026-09-02 (#14253) to \"11 of 12 groups live; the twelfth, `datasets`, …\", which matched no reading of `props` at all — `datasets` is one of the groups, never a twelfth. Corrected again 2026-09-06 (#15775) by deleting the integers rather than re-deriving them, on #7377's precedent for this ledger family's other hand-maintained counts. Seeded 2026-08-01 (#4488). 2026-08-28 (#13003): all nine `path:NNN` citations in this file were re-anchored to their consuming symbols; EIGHT of the nine were wrong and every one of those was IN RANGE (the exception is `locale`, whose range still lands inside its reader). This ledger carried the batch's heaviest load of the OTHER silent class as well — nine further positions written as bare `:NNN` suffixes with no path in front of them, which `PATH_RE` never matches, so they degraded to prose that no check has ever resolved, bounded or key-checked.", + "_note": "TranslationItemSchema (#3778 — one locale's translations, the SAME groups the file-authored bundles use). NO LONGER A PIPE: the schema was a z.preprocess wrapping the retired object-first-dialect guard, which the gate's walker could not see through until #4488 fixed unwrap() to take the OUT side of a transform-input pipe — `translation` was literally unwalkable before this ledger. #4001 closed the shape with `.strict()` and folded the guard's ten prescriptions into the unknown-key `guidance`, so the preprocess is gone and the registered schema is a plain strict object. Consumer chain: runtime-authored items sync into the i18n adapter's authored layer (packages/core/src/fallbacks/authored-translation-sync.ts — at kernel:ready, on metadata:reloaded, and on translation mutations; #2591 closed the publish dead-end), file bundles load via service-i18n; both merge into ONE tree read by the spec resolvers (packages/spec/src/system/i18n-resolver.ts), the REST localization layer (translateMetaItem/translateMetaTypes), objectui's client resolvers (useObjectLabel/useSettingsLabel), and plugin-audit's summary localizer. WALK BOUNDARY: every group but `settingsCommon` is a z.record keyed by target names — the drill sees each record's VALUE shape one level; the deeper per-key conventions (objects..fields..label, apps..navigation..label, …) are governed by the resolvers cited per row, not by ledger rows. `settingsCommon` is a fixed strictObject with no target names, so the drill's one level lands on its own named member `sourceLabels` — itself a fixed strictObject whose keys are the ADR-0010 resolution layers (`env`, `global`, `tenant`, `user`, `default`), a closed set the schema holds closed: the retired spellings (`org`/`workspace`, `system`, `fallback`, `environment`) are rejected with a pointer to the layer each meant, never accepted as a layer. Those layer keys sit below the boundary: they are read as one unit by `resolveSettingsSourceLabel` (packages/spec/src/system/i18n-resolver.ts) and objectui's `useSettingsLabel().sourceLabel`, and the blanket verdict `settingsCommon` carries over them is the DECLARED kind — `translation/settingsCommon` is a row of scripts/liveness/undrilled-containers.baseline.json, the same standing as the record groups whose value shapes are not drilled. Note also the sync merges the RAW stored payload (authored-translation-sync.ts:155, not a schema re-parse), so the declared groups below are the CONTRACT while undeclared keys technically flow through on rows already stored — the resolvers read only the declared conventions. Since #4001 no NEW row can acquire one: the metadata door rejects an undeclared key instead of stripping it, so that residue is a finite set that only shrinks. `settings` is NOT a group of this ledger any more: its row was DELETED 2026-09-24 (#19620, ruling batch #210 item 2 letter B) by the strict-delete route — TranslationItemSchema no longer declares it and refuses it by name with the platform-only prescription, so the key left the walked shape and a surviving row would be an ORPHAN. That row was `live` on evidence reading the SERVED tree (objectui useSettingsLabel), which the PLATFORM bundle feeds; the deletion retires the key from this item ledger ONLY and says nothing about the platform capability, which stays declared on PlatformTranslationDataSchema (not this ledger’s subject) and read by the resolveSettings* family. Stored rows written before the door closed are converted, not read raw: authored-translation-sync replays the ADR-0087 chain (translation-per-app-settings-removed) over each row before merging it, which retires the `settings` clause of the RAW-payload note above for that key. Every group but `flows` is live — `flows` is the one that is `planned`, and `datasets` was seeded LIVE and DRILLED by #14253 with its reader (`translateDataset`) in the same change. A BOUNDARY and not a total, deliberately: the per-prop rows below carry the verdicts, the generated `state-counts/translation.md` carries this type's totals (#7377), and `props` holds the groups PLUS `locale` and the item-identity keys `name`/`label` — so no total taken over `props` is a total of groups, which is how both totals this sentence has carried came to be wrong. ⚠️ It first read \"10 of 11 groups live; the one dead group (`validationMessages`) is pointed at by #3778's own legacy-key migration table, making it a shipped false signpost\" — describing a group REMOVED in 17.0.0 (#4667), i.e. prose outliving its subject in the header of the very file whose rows warn about that; corrected 2026-09-02 (#14253) to \"11 of 12 groups live; the twelfth, `datasets`, …\", which matched no reading of `props` at all — `datasets` is one of the groups, never a twelfth. Corrected again 2026-09-06 (#15775) by deleting the integers rather than re-deriving them, on #7377's precedent for this ledger family's other hand-maintained counts. Seeded 2026-08-01 (#4488). 2026-08-28 (commit 8f10a79f7): all nine `path:NNN` citations in this file were re-anchored to their consuming symbols; EIGHT of the nine were wrong and every one of those was IN RANGE (the exception is `locale`, whose range still lands inside its reader). This ledger carried the batch's heaviest load of the OTHER silent class as well — nine further positions written as bare `:NNN` suffixes with no path in front of them, which `PATH_RE` never matches, so they degraded to prose that no check has ever resolved, bounded or key-checked.", "props": { "name": { "status": "live", @@ -22,37 +22,37 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/core/src/fallbacks/authored-translation-sync.ts#readAuthoredTranslationLayer (`const locale = (typeof data?.locale === 'string' && data.locale) || …`, then `byLocale[locale] = deepMerge(...)`; an item whose locale cannot be resolved is SKIPPED, loudly)", - "note": "which bundle entry the item fills. Required for a reason the schema states: the sync SKIPS an item whose locale it cannot resolve — loudly (warn log), with a name-derived fallback for pre-#3778 rows. 2026-08-28: RE-ANCHORED (#13003) — the cited range lands inside `readAuthoredTranslationLayer` (its endpoint `:148` is the closing `);` of the warn call), so this leg is the grammar migration rather than a repair. Re-closed by hand against 8cb96ec41." + "note": "which bundle entry the item fills. Required for a reason the schema states: the sync SKIPS an item whose locale it cannot resolve — loudly (warn log), with a name-derived fallback for pre-#3778 rows. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) — the cited range lands inside `readAuthoredTranslationLayer` (its endpoint `:148` is the closing `);` of the warn call), so this leg is the grammar migration rather than a repair. Re-closed by hand against 8cb96ec41." }, "objects": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupObjectField (`objects..label` / `pluralLabel` / `description`); packages/spec/src/system/i18n-resolver.ts#lookupObjectFieldAttr (`objects..fields..`); packages/spec/src/system/i18n-resolver.ts#resolveViewLabel (`objects.._views..label`); packages/spec/src/system/i18n-resolver.ts#lookupActionField (`objects.._actions`, checked BEFORE globalActions); packages/spec/src/system/i18n-resolver.ts#lookupTabLabel (`objects.._tabs`); objectui @940ba24: packages/i18n/src/useObjectLabel.ts:397-400", - "note": "the largest group, fully live: label/pluralLabel/description (translateObject), fields.{label,help,placeholder,options}, _views (resolveViewLabel + empty-state copy), _actions (label/confirmText/successMessage/params/resultDialog — object-scoped first, then globalActions fallback), _sections (objectui record:details section labels). Served through REST translateMetaItem(s) and the /api/v1/i18n endpoints; objectui re-resolves client-side via the spec-translations transform. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — this entry named SIX positions in i18n-resolver.ts and only the first, `:735`, was parsable as a citation at all; it had rotted onto `widgets?: WidgetLike[]` in the `DashboardLike` INTERFACE. The other five (`:751`, `:159`, `:197`, `:873`, `:900`) were bare line suffixes with no path in front of them, so they were never resolved, bounded or key-checked by anything — the largest instance in this batch of the class `flow.status` and `dashboard.widgets.suppressWarnings` also carried. The five anchors above are the five distinct group readers, named. Re-closed by hand against 8cb96ec41." + "note": "the largest group, fully live: label/pluralLabel/description (translateObject), fields.{label,help,placeholder,options}, _views (resolveViewLabel + empty-state copy), _actions (label/confirmText/successMessage/params/resultDialog — object-scoped first, then globalActions fallback), _sections (objectui record:details section labels). Served through REST translateMetaItem(s) and the /api/v1/i18n endpoints; objectui re-resolves client-side via the spec-translations transform. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — this entry named SIX positions in i18n-resolver.ts and only the first, `:735`, was parsable as a citation at all; it had rotted onto `widgets?: WidgetLike[]` in the `DashboardLike` INTERFACE. The other five (`:751`, `:159`, `:197`, `:873`, `:900`) were bare line suffixes with no path in front of them, so they were never resolved, bounded or key-checked by anything — the largest instance in this batch of the class `flow.status` and `dashboard.widgets.suppressWarnings` also carried. The five anchors above are the five distinct group readers, named. Re-closed by hand against 8cb96ec41." }, "apps": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (`apps..label` / `.description`); packages/spec/src/system/i18n-resolver.ts#lookupNavLabel (`apps..navigation..label`); packages/rest/src/rest-server.ts#translateMetaItem (the /meta read path that applies it — dispatches through `translateMetadataDocument` to `translateApp`)", - "note": "translateApp swaps app label/description and walks the navigation tree replacing node labels by id — applied on every /meta app read. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, both legs — `i18n-resolver.ts:442` had rotted onto a PARAMETER in `lookupActionResultDialogNode`'s signature, and `rest-server.ts:2001` onto a blank line in the Server-Timing disclosure gate ~570 lines from the translate seam. The rest leg is also named more honestly than the note was: this repo has NO `translateApp` call site — the REST layer calls `translateMetaItem`, which reaches `translateApp` through the per-type translator map in `translateMetadataDocument`. Re-closed by hand against 8cb96ec41." + "note": "translateApp swaps app label/description and walks the navigation tree replacing node labels by id — applied on every /meta app read. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED, both legs — `i18n-resolver.ts:442` had rotted onto a PARAMETER in `lookupActionResultDialogNode`'s signature, and `rest-server.ts:2001` onto a blank line in the Server-Timing disclosure gate ~570 lines from the translate seam. The rest leg is also named more honestly than the note was: this repo has NO `translateApp` call site — the REST layer calls `translateMetaItem`, which reaches `translateApp` through the per-type translator map in `translateMetadataDocument`. Re-closed by hand against 8cb96ec41." }, "messages": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/plugins/plugin-audit/src/audit-writers.ts#writeAudit (`translate('messages.activityCreated' | 'messages.activityUpdated' | 'messages.activityDeleted', …)`); packages/plugins/plugin-audit/src/audit-writers.ts#writeCommentMentions (`messages.mentionedYou` / `messages.mentionedYouAnonymous`); packages/plugins/plugin-audit/src/audit-writers.ts#translateWith (`i18n.t(key, locale, params)` — the one seam every composed key goes through)", - "note": "consumed via II18nService.t: plugin-audit localizes activity-feed summaries (messages.activityCreated/Updated/Deleted, framework#3039) and collaboration notifications (messages.mentionedYou). Easy to mis-verify — no resolver in i18n-resolver.ts reads it; the consumer is a t() caller with composed keys, which a literal grep for the group name never finds. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:562-579`'s endpoint had rotted onto the activity-milestone guard, and `:744-745` was a bare line suffix with no path. The note's own warning now holds of the citation as well as of the code: the consumer is a `t()` caller with COMPOSED keys, so the anchors name the three functions that compose them instead of lines a grep for the group name would never have produced in the first place. Re-closed by hand against 8cb96ec41." + "note": "consumed via II18nService.t: plugin-audit localizes activity-feed summaries (messages.activityCreated/Updated/Deleted, framework#3039) and collaboration notifications (messages.mentionedYou). Easy to mis-verify — no resolver in i18n-resolver.ts reads it; the consumer is a t() caller with composed keys, which a literal grep for the group name never finds. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:562-579`'s endpoint had rotted onto the activity-milestone guard, and `:744-745` was a bare line suffix with no path. The note's own warning now holds of the citation as well as of the code: the consumer is a `t()` caller with COMPOSED keys, so the anchors name the three functions that compose them instead of lines a grep for the group name would never have produced in the first place. Re-closed by hand against 8cb96ec41." }, "globalActions": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupActionField (`data.globalActions?.[action.name]?.[field]` — consulted after `objects.._actions`); packages/spec/src/system/i18n-resolver.ts#lookupActionResultDialogNode (the same object-first, global-fallback order for the result-dialog copy)", - "note": "the object-less fallback for action label/confirmText/successMessage/params/resultDialog — resolveAction* checks objects.._actions first, then here. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:200` had rotted onto the return-type line of `pickData`, the bundle/locale picker ~186 lines above the first `globalActions` read; `:249` was a bare line suffix with no path. Re-closed by hand against 8cb96ec41." + "note": "the object-less fallback for action label/confirmText/successMessage/params/resultDialog — resolveAction* checks objects.._actions first, then here. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:200` had rotted onto the return-type line of `pickData`, the bundle/locale picker ~186 lines above the first `globalActions` read; `:249` was a bare line suffix with no path. Re-closed by hand against 8cb96ec41." }, "dashboards": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupDashboardAttr (`dashboards..label` / `.description`); packages/spec/src/system/i18n-resolver.ts#lookupWidgetAttr (`dashboards..widgets..`, `subCaption` included); packages/spec/src/system/i18n-resolver.ts#translateGlobalFilter (`dashboards..globalFilters..label` / `.options.`, keyed by `globalFilterKey` — `name`, else `field`; #16772); packages/spec/src/system/i18n-resolver.ts#translateDashboard", - "note": "translateDashboard: label/description plus per-widget title/description/subCaption by widget id; header action labels. `subCaption` (#7862, #5428 item 4) overlays the metric widget's `options.description` — a different authored field from `widget.description`, each on its own key — live through the same translateDashboard REST path; objectui's client-side renderer half is the downstream follow-up. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:538` had rotted onto the opening line of `translateAction`'s docblock, an ACTION resolver ~244 lines above the dashboard ones; the second position `:554` was a bare line suffix with no path and resolved to nothing. Re-closed by hand against 8cb96ec41." + "note": "translateDashboard: label/description plus per-widget title/description/subCaption by widget id; header action labels. `subCaption` (#7862, #5428 item 4) overlays the metric widget's `options.description` — a different authored field from `widget.description`, each on its own key — live through the same translateDashboard REST path; objectui's client-side renderer half is the downstream follow-up. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:538` had rotted onto the opening line of `translateAction`'s docblock, an ACTION resolver ~244 lines above the dashboard ones; the second position `:554` was a bare line suffix with no path and resolved to nothing. Re-closed by hand against 8cb96ec41." }, "datasets": { "status": "live", @@ -90,7 +90,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupPageAttr (`pages..` — label/description/title/subtitle, title falling back to label); packages/spec/src/system/i18n-resolver.ts#lookupPageComponentCopy (`pages..components.` — header copy keyed by page name because page:header instances carry no stable id)", - "note": "translatePage: label/description/title/subtitle (title falls back to label; header copy keyed by page name because page:header instances carry no stable id). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:636` had rotted onto `function lookupAppAttr`, the APP group's reader, ~226 lines above the page ones. Re-closed by hand against 8cb96ec41." + "note": "translatePage: label/description/title/subtitle (title falls back to label; header copy keyed by page name because page:header instances carry no stable id). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:636` had rotted onto `function lookupAppAttr`, the APP group's reader, ~226 lines above the page ones. Re-closed by hand against 8cb96ec41." }, "flows": { "status": "planned", @@ -118,7 +118,7 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/rest/src/rest-server.ts#translateMetaTypesResponse (`resolveMetadataTypeLabel(...)` and `resolveMetadataFormLabels(...)` applied to every GET /meta types entry); packages/spec/src/system/i18n-resolver.ts#lookupMetadataForm (`metadataForms.`); packages/spec/src/system/i18n-resolver.ts#lookupMetadataFormField (`metadataForms..fields.`)", - "note": "translateMetaTypes decorates GET /meta types with resolveMetadataTypeLabel and localizes every form schema through resolveMetadataFormLabels (labels/sections/fields by dotted path) — the Studio metadata-editor localization path. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, position AND prose — `:2058` had rotted into a docblock about `requiresObject` pruning ~815 lines above the seam (`:2062` was a bare line suffix), and the note called the function `translateMetaTypes`, which is no symbol in this repo; it is `translateMetaTypesResponse`. Re-closed by hand against 8cb96ec41." + "note": "translateMetaTypes decorates GET /meta types with resolveMetadataTypeLabel and localizes every form schema through resolveMetadataFormLabels (labels/sections/fields by dotted path) — the Studio metadata-editor localization path. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED, position AND prose — `:2058` had rotted into a docblock about `requiresObject` pruning ~815 lines above the seam (`:2062` was a bare line suffix), and the note called the function `translateMetaTypes`, which is no symbol in this repo; it is `translateMetaTypesResponse`. Re-closed by hand against 8cb96ec41." }, "settingsCommon": { "status": "live", diff --git a/packages/spec/liveness/validation.json b/packages/spec/liveness/validation.json index c98e4de8d73..a2fac312550 100644 --- a/packages/spec/liveness/validation.json +++ b/packages/spec/liveness/validation.json @@ -1,12 +1,12 @@ { "type": "validation", - "_note": "ValidationRuleSchema — the ADR-0020 carrier, where a wrong verdict is expensive, so the call graph was closed with extra care. GOVERNED VIA THE GATE'S SPEC-ONLY OVERRIDE (SPEC_ONLY_SCHEMAS) since #4509: `validation` is no longer a registered metadata KIND, but the rule vocabulary is entirely live, so the ledger must keep governing the schema — being off the registry is exactly the state in which a drift can survive unnoticed (the same reason `webhook` and `query` sit there). The walked shape is the discriminated union's FIRST object member (the base keys + `script`'s type/condition — the #3095 union rule); per-variant keys (state_machine's field/transitions/initialStates, format's regex/format, json_schema's schema, conditional's when/then/otherwise, cross_field's fields) sit OUTSIDE the walk — an explicit blind spot recorded here (the union analog of the z.record rule), governed by the evaluator's own tests, not ledger rows. Consumer: the engine write path calls evaluateValidationRules on insert and on every matched update row (packages/objectql/src/engine.ts:3931, :4248, :4321) with rules from the OBJECT's embedded `validations` array (+ object_extension merge, engine.ts:1572). The evaluator provably honors every execution-control key — the zod header's prose claiming it 'only reads type/condition/field/events/severity/message' is STALE (it predates enforcement of active/priority) and should not be trusted over the ledger. TYPE-LEVEL GAP CLOSED 2026-08-02 (#4509) by RETIRING THE KIND (ADR-0088), not by building a bridge: a STANDALONE `validation` item (file `*.validation.ts` or Studio) never reached any object's write path, because the schema has no object-binding key and — every variant being `.strict()` — an author could not add one; no merge code existed, and only the reference tracker even expected one (a row that scanned a key the schema would have stripped; both are now gone). A state machine authored through that door saved cleanly and gated nothing. The kind failed the ADR-0088 admission test on its first clause — no independent lifecycle: a rule only means something against an object. Rules are authored where they have always been evaluated, as `validations:` on the object, and the per-prop verdicts below describe exactly that path (the same schema instance). Evidence lines restamped 2026-08-02 — the seeded engine.ts refs had drifted ~220 lines. Seeded 2026-08-01. 2026-08-28 (#13003): all ten `path:NNN` citations in this file were re-anchored to their consuming symbols, and all ten were wrong. This is the batch's clearest specimen of BLOCK drift: nine of the ten pointed into one 70-line band (640-710) that is today an ADR-0124 docblock and a `readonlyWhen` helper, ~1,200 lines above `evaluateValidationRules`. Each pointer stayed in range and each looked plausible beside its neighbours, which is exactly the condition under which a whole ledger rots at once and nothing reports it.", + "_note": "ValidationRuleSchema — the ADR-0020 carrier, where a wrong verdict is expensive, so the call graph was closed with extra care. GOVERNED VIA THE GATE'S SPEC-ONLY OVERRIDE (SPEC_ONLY_SCHEMAS) since #4509: `validation` is no longer a registered metadata KIND, but the rule vocabulary is entirely live, so the ledger must keep governing the schema — being off the registry is exactly the state in which a drift can survive unnoticed (the same reason `webhook` and `query` sit there). The walked shape is the discriminated union's FIRST object member (the base keys + `script`'s type/condition — the #3095 union rule); per-variant keys (state_machine's field/transitions/initialStates, format's regex/format, json_schema's schema, conditional's when/then/otherwise, cross_field's fields) sit OUTSIDE the walk — an explicit blind spot recorded here (the union analog of the z.record rule), governed by the evaluator's own tests, not ledger rows. Consumer: the engine write path calls evaluateValidationRules on insert and on every matched update row (packages/objectql/src/engine.ts:3931, :4248, :4321) with rules from the OBJECT's embedded `validations` array (+ object_extension merge, engine.ts:1572). The evaluator provably honors every execution-control key — the zod header's prose claiming it 'only reads type/condition/field/events/severity/message' is STALE (it predates enforcement of active/priority) and should not be trusted over the ledger. TYPE-LEVEL GAP CLOSED 2026-08-02 (#4509) by RETIRING THE KIND (ADR-0088), not by building a bridge: a STANDALONE `validation` item (file `*.validation.ts` or Studio) never reached any object's write path, because the schema has no object-binding key and — every variant being `.strict()` — an author could not add one; no merge code existed, and only the reference tracker even expected one (a row that scanned a key the schema would have stripped; both are now gone). A state machine authored through that door saved cleanly and gated nothing. The kind failed the ADR-0088 admission test on its first clause — no independent lifecycle: a rule only means something against an object. Rules are authored where they have always been evaluated, as `validations:` on the object, and the per-prop verdicts below describe exactly that path (the same schema instance). Evidence lines restamped 2026-08-02 — the seeded engine.ts refs had drifted ~220 lines. Seeded 2026-08-01. 2026-08-28 (commit 8f10a79f7): all ten `path:NNN` citations in this file were re-anchored to their consuming symbols, and all ten were wrong. This is the batch's clearest specimen of BLOCK drift: nine of the ten pointed into one 70-line band (640-710) that is today an ADR-0124 docblock and a `readonlyWhen` helper, ~1,200 lines above `evaluateValidationRules`. Each pointer stayed in range and each looked plausible beside its neighbours, which is exactly the condition under which a whole ledger rots at once and nothing reports it.", "props": { "name": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`Validation rule '${rule.name}' threw — skipped`, and the violation log line that names the rule and its severity); packages/objectql/src/validation/rule-validator.ts#checkPredicate (`unevaluableRuleError(rule.name, …)` — the rule name is what the rejection names)", - "note": "names the rule in violation logs and the broken-rule skip warning. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:665` and `:676` had BOTH rotted into the same ADR-0124 docblock above `isReadonlyWhenLocked`, ~1,180 lines above the evaluator. Nine of this file's ten citations pointed into the 640-710 band, which today is that docblock and a `readonlyWhen` helper: the whole ledger had drifted as ONE block when the module grew its conditional-field section, and every pointer stayed IN RANGE, so no check could see any of it. Re-closed by hand against 8cb96ec41." + "note": "names the rule in violation logs and the broken-rule skip warning. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:665` and `:676` had BOTH rotted into the same ADR-0124 docblock above `isReadonlyWhenLocked`, ~1,180 lines above the evaluator. Nine of this file's ten citations pointed into the 640-710 band, which today is that docblock and a `readonlyWhen` helper: the whole ledger had drifted as ONE block when the module grew its conditional-field section, and every pointer stayed IN RANGE, so no check could see any of it. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", @@ -28,19 +28,19 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`.filter((r) => r.active !== false)` — filtered out before evaluation, not merely skipped inside it)", - "note": "`active: false` filters the rule out before evaluation — genuinely enforced, unlike the retired flow.active/tool.active (worth stating on a validation surface: an rls.enabled-shaped failure here would be a data-integrity hole). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:647` had rotted onto the `## Two faults, two answers (#4889)` heading inside the ADR-0124 docblock (see `name` for the block drift). Re-closed by hand against 8cb96ec41." + "note": "`active: false` filters the rule out before evaluation — genuinely enforced, unlike the retired flow.active/tool.active (worth stating on a validation surface: an rls.enabled-shaped failure here would be a data-integrity hole). 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:647` had rotted onto the `## Two faults, two answers (#4889)` heading inside the ADR-0124 docblock (see `name` for the block drift). Re-closed by hand against 8cb96ec41." }, "events": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`const events = r.events ?? ['insert', 'update']; return events.includes(mode)`)", - "note": "insert/update dispatch (default both). `delete` was removed from the enum in #3184 after being proven a silent no-op — guard deletions with a beforeDelete hook. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:654` had rotted into the same ADR-0124 docblock (see `name`). Re-closed by hand against 8cb96ec41." + "note": "insert/update dispatch (default both). `delete` was removed from the enum in #3184 after being proven a silent no-op — guard deletions with a beforeDelete hook. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:654` had rotted into the same ADR-0124 docblock (see `name`). Re-closed by hand against 8cb96ec41." }, "priority": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100))` — stable low-first ordering of the evaluation)", - "note": "stable low-number-first sort of the evaluation order. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:657` had rotted onto a blank line of that docblock (see `name`). Re-closed by hand against 8cb96ec41." + "note": "stable low-number-first sort of the evaluation order. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:657` had rotted onto a blank line of that docblock (see `name`). Re-closed by hand against 8cb96ec41." }, "tags": { "status": "live", @@ -54,25 +54,25 @@ "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`const severity = rule.severity ?? 'error'` — only `error` blocks the write; `warning`/`info` are logged and the write proceeds)", - "note": "only 'error' blocks the write; 'warning'/'info' violations are logged and let the write proceed. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the range's endpoint `:678` had rotted onto `function isReadonlyWhenLocked`, a different feature's helper (see `name`). Re-closed by hand against 8cb96ec41." + "note": "only 'error' blocks the write; 'warning'/'info' violations are logged and let the write proceed. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — the range's endpoint `:678` had rotted onto `function isReadonlyWhenLocked`, a different feature's helper (see `name`). Re-closed by hand against 8cb96ec41." }, "message": { "status": "live", "verifiedAt": "2026-09-02", "evidence": "packages/objectql/src/validation/rule-validator.ts#checkPredicate (`message: authoredRuleMessage(rule, messages)` on the violation); packages/objectql/src/validation/rule-validator.ts#authoredRuleMessage (resolves the authored text against `objects.._validations..message` through the engine's i18n service, falling back to the authored value); packages/objectql/src/validation/rule-validator.ts#checkStateMachine (an author-written message wins over the generated fallback); packages/objectql/src/engine.ts#update (the bulk per-row path re-wraps the authored text with the row id before rethrowing `ValidationError`)", - "note": "the author-written violation text carried on every FieldValidationError (surfaced as 400 VALIDATION_FAILED). 2026-09-02 (#14253): the overrides clause was CORRECTED, not extended — it read \"per-deployment overrides resolve via validationMessages in the translation bundle (#3957)\", and `validationMessages` had been removed in 17.0.0 (#4667), so the cell described a route that had not existed for a major version. The route now named is real and has a reader in the same change: `objects.._validations..message`, resolved by `authoredRuleMessage` through the SAME i18n service #3957 wired for built-in messages and field labels — a key shape, not a second channel. The authored value is still untouched at rest; the lookup happens as the violation is built, and a miss returns it verbatim. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, both legs — `:676` had rotted into the ADR-0124 docblock (see `name`), and `engine.ts:3703` onto a docblock about a probe's own failure disposition, ~6,990 lines above the only place engine.ts touches this key. Recorded honestly rather than dressed up: the engine leg is NARROW — it re-wraps the message with a record id, it neither authors nor resolves it — and `#update` is a WEAK anchor, because `update` occurs throughout that file and so cannot go red if the method is deleted. The rule-validator anchors are what carry this row. Re-closed by hand against 8cb96ec41." + "note": "the author-written violation text carried on every FieldValidationError (surfaced as 400 VALIDATION_FAILED). 2026-09-02 (#14253): the overrides clause was CORRECTED, not extended — it read \"per-deployment overrides resolve via validationMessages in the translation bundle (#3957)\", and `validationMessages` had been removed in 17.0.0 (#4667), so the cell described a route that had not existed for a major version. The route now named is real and has a reader in the same change: `objects.._validations..message`, resolved by `authoredRuleMessage` through the SAME i18n service #3957 wired for built-in messages and field labels — a key shape, not a second channel. The authored value is still untouched at rest; the lookup happens as the violation is built, and a miss returns it verbatim. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED, both legs — `:676` had rotted into the ADR-0124 docblock (see `name`), and `engine.ts:3703` onto a docblock about a probe's own failure disposition, ~6,990 lines above the only place engine.ts touches this key. Recorded honestly rather than dressed up: the engine leg is NARROW — it re-wraps the message with a record id, it neither authors nor resolves it — and `#update` is a WEAK anchor, because `update` occurs throughout that file and so cannot go red if the method is deleted. The rule-validator anchors are what carry this row. Re-closed by hand against 8cb96ec41." }, "type": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateRule (`switch (rule.type)` — dispatches to the state_machine / predicate / format / json_schema / conditional checkers); packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`r.type === 'state_machine'` — the seed-write skip, #3433)", - "note": "the union discriminant: dispatches to the state_machine/predicate/format/json_schema/conditional checkers; the schema admits exactly the handled set. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — the range's endpoint `:706` had rotted onto a closing brace in the `readonlyWhen` unbound-root branch (see `name`). Re-closed by hand against 8cb96ec41." + "note": "the union discriminant: dispatches to the state_machine/predicate/format/json_schema/conditional checkers; the schema admits exactly the handled set. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — the range's endpoint `:706` had rotted onto a closing brace in the `readonlyWhen` unbound-root branch (see `name`). Re-closed by hand against 8cb96ec41." }, "condition": { "status": "live", "verifiedAt": "2026-08-28", "evidence": "packages/objectql/src/validation/rule-validator.ts#checkPredicate (`const expr = toExpression(rule.condition)` — the CEL predicate; TRUE fails the write)", - "note": "the CEL predicate (script/cross_field variants), evaluated against the merged record + previous — TRUE fails the write. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:697` had rotted onto the `logger?.warn?.(` of the `readonlyWhen` unbound-root diagnostic, ~1,325 lines above the predicate compiler (see `name`). Re-closed by hand against 8cb96ec41." + "note": "the CEL predicate (script/cross_field variants), evaluated against the merged record + previous — TRUE fails the write. 2026-08-28: RE-ANCHORED (commit 8f10a79f7) and REPOINTED — `:697` had rotted onto the `logger?.warn?.(` of the `readonlyWhen` unbound-root diagnostic, ~1,325 lines above the predicate compiler (see `name`). Re-closed by hand against 8cb96ec41." } } }