From 88a1b24e2a74949cd216cf6e80f1149b2e57e319 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Aug 2026 23:57:42 +0000 Subject: [PATCH] chore(spec): re-anchor the closing twelve liveness ledgers to consuming symbols (#13003 batch 4) Adoption batch 4 -- the closing batch -- of the #12516 symbol-anchor citation grammar, file-disjoint from batches 1-3 except for the one action.json entry batch 1 deliberately left standing. Eighty of the eighty-two remaining `path:NNN` citations across liveness/app.json, validation.json, translation.json, field.json, hook.json, mapping.json, capability.json, seed.json, view.json, dashboard.json, flow.json and qa.json become `path#symbol`, together with every path-only pointer in the same entries -- each re-closed by reading the code on the current tree rather than by shifting a line. The gate's own counters move 82 -> 2 line citations and 383 -> 499 symbol anchors. Sixty-seven of the eighty were already wrong, every one IN RANGE and so invisible to the existence check, the line bound and the key-mention check. Six files were 100% rotted (validation 10/10, seed 6/6, view 5/5, dashboard 4/4, flow 4/4, qa 4/4). The dominant shape is BLOCK drift: nine of validation.json's ten pointers had settled inside ONE 70-line ADR-0124 docblock ~1,200 lines above evaluateValidationRules, and app.json's name/label/description onto three consecutive lines of one unrelated action resolver. flow.status is the sharpest case -- it had come to rest on `flowLedgerDisabled`, the map ADJACENT to the one this key writes, after #10243 split the single flowEnabled map in two. Two silent classes close with them: seventeen positions across four files were bare `:NNN` suffixes with no path, which PATH_RE never matches, so nothing had ever resolved, bounded or key-checked them (translation.objects alone carried five); three entries (hook retryPolicy/timeout/onError) cited a path with no line, unfalsifiable by a line bound by construction. Three citations were falsified in PROSE as well as position: view.object credited getViewsByObject() to packages/objectql/src/engine.ts (it is in packages/metadata), field.requiredWhen named fieldRequiresParentRoot (no such symbol; it is hasParentScopedRequiredWhen), and translation.metadataForms named translateMetaTypes (it is translateMetaTypesResponse). Nothing is re-classified. Two entries stay byte-for-byte untouched and NOT re-stamped because their `live` VERDICT, not their citation, is what is falsified -- action.execute (reported as #13036) and field.conditionalRequired (reported as #13043), both retiredKey tombstones since protocol 17 whose cited `.transform` lowering no longer exists. Those are ADR-0049 re-classifications with their own card shape, so the line-citation counter reads 2 rather than 0 and the two non-vacuity floors stay in place. Part of #13003 Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 Co-Authored-By: Claude Fable 5 --- .changeset/liveness-symbol-anchors-batch-4.md | 51 +++++++++++++++++ packages/spec/liveness/app.json | 56 +++++++++---------- packages/spec/liveness/capability.json | 32 +++++------ packages/spec/liveness/dashboard.json | 20 +++---- packages/spec/liveness/field.json | 33 ++++++----- packages/spec/liveness/flow.json | 20 ++++--- packages/spec/liveness/hook.json | 51 +++++++++-------- packages/spec/liveness/mapping.json | 38 ++++++------- packages/spec/liveness/qa.json | 26 ++++----- packages/spec/liveness/seed.json | 34 +++++------ packages/spec/liveness/translation.json | 50 ++++++++--------- packages/spec/liveness/validation.json | 50 ++++++++--------- packages/spec/liveness/view.json | 28 ++++++---- 13 files changed, 280 insertions(+), 209 deletions(-) create mode 100644 .changeset/liveness-symbol-anchors-batch-4.md diff --git a/.changeset/liveness-symbol-anchors-batch-4.md b/.changeset/liveness-symbol-anchors-batch-4.md new file mode 100644 index 0000000000..8c66921972 --- /dev/null +++ b/.changeset/liveness-symbol-anchors-batch-4.md @@ -0,0 +1,51 @@ +--- +"@objectstack/spec": patch +--- + +chore(spec): re-anchor the closing twelve liveness ledgers to consuming symbols (#13003) + +Adoption batch 4 — the closing batch — of the symbol-anchor citation grammar +landed by #12516. The `liveness/` ledgers ship inside this package's npm tarball +(they are named in `files`), so this is a published-data change even though no +runtime behaviour moves and no schema key changes. + +Eighty of the eighty-two remaining `path:NNN` citations — across +`liveness/app.json`, `validation.json`, `translation.json`, `field.json`, +`hook.json`, `mapping.json`, `capability.json`, `seed.json`, `view.json`, +`dashboard.json`, `flow.json` and `qa.json` — are now written `path#symbol`, +each re-closed by reading the code on the current tree rather than by shifting a +line number. Every path-only pointer in the same entries is anchored with them. +The gate's own line-citation counter goes 82 to 2 and its symbol-anchor counter +383 to 499. + +Sixty-seven of the eighty were already wrong, every one IN RANGE and therefore +invisible to the existence check, the line bound and the key-mention check alike. +Six files were 100% rotted (validation 10/10, seed 6/6, view 5/5, dashboard 4/4, +flow 4/4, qa 4/4). The dominant shape is BLOCK drift: nine of `validation.json`'s +ten pointers had come to rest inside one 70-line ADR-0124 docblock roughly 1,200 +lines above `evaluateValidationRules`, and `app.json`'s `name` / `label` / +`description` had settled on three consecutive lines of one unrelated action +resolver. `flow.status` is the sharpest single case — its pointer landed on +`flowLedgerDisabled`, the map ADJACENT to the one this key writes, after #10243 +split the two apart. + +Two silent classes are closed with them. Seventeen positions across four files +were written as bare `:NNN` suffixes with no path in front of them, which the +scanner never matched as citations at all, so nothing ever resolved, bounded or +key-checked them; `translation.objects` alone carried five. Three more entries +(`hook.retryPolicy` / `timeout` / `onError`) cited a path with no line, which a +line bound cannot falsify by construction. All twenty are repo-rooted anchors now. + +Three citations were falsified in PROSE as well as position and are corrected +with their lines: `view.object` credited `getViewsByObject()` to +`packages/objectql/src/engine.ts` (it is in `packages/metadata`), +`field.requiredWhen` named `fieldRequiresParentRoot` (no such symbol; it is +`hasParentScopedRequiredWhen`), and `translation.metadataForms` named +`translateMetaTypes` (it is `translateMetaTypesResponse`). + +Nothing is re-classified. Two entries are deliberately left byte-for-byte +untouched and un-restamped because their `live` verdict — not their citation — +is what is falsified: `action.execute` (reported as #13036) and +`field.conditionalRequired` (reported as #13043), both `retiredKey` tombstones +since protocol 17 whose cited `.transform` lowering no longer exists. Those are +ADR-0049 re-classifications with their own card shape. diff --git a/packages/spec/liveness/app.json b/packages/spec/liveness/app.json index c8b2f94b53..53038a9cfb 100644 --- a/packages/spec/liveness/app.json +++ b/packages/spec/liveness/app.json @@ -1,24 +1,24 @@ { "type": "app", - "_note": "AppSchema — the navigation shell, the densest hand-authored surface on the platform. Consumers: the REST read layer's filterAppForUser (packages/rest/src/rest-server.ts:2651-2740 — the SERVER-side authority for app/nav permission + capability gating and the ADR-0045 publish gate, which judges `_unpublished` and NOT `hidden` since #4829), the spec i18n translateApp (i18n-resolver.ts:472), and objectui's shell (@940ba24: app-shell AppSidebar/ConsoleLayout/ContextSelectors, layout NavigationRenderer, console RootLandingRedirect). The #4001/#4142 app step already retired seven dead keys as retiredKey tombstones — they stay in the walked shape, so their rows stay here (tombstone rule, orphans.mts). WALK BOUNDARY (#3095 union rule): `navigation` drills into the union's FIRST member (the `object` variant + base keys); the other variants' payload keys sit outside the walk and were verified by hand — dashboardName (NavigationRenderer.tsx:433), pageName (:435-442), url/target (:462), reportName (:460), componentRef (:464,:644), group `expanded` (:856) all live. The one GAP found there is now CLOSED (#4509, objectui @e8bec83): an `action` item's click dispatches through a host-supplied `onAction` prop that no shipped shell passed, so `actionDef.actionName` reached no dispatcher and every such item dead-clicked. objectui's `useNavActionDispatch` (objectui: packages/app-shell/src/hooks/useNavActionDispatch.ts) resolves the name against `action` metadata and dispatches through the console action runtime, and UnifiedSidebar passes it (objectui: packages/app-shell/src/layout/UnifiedSidebar.tsx:473). A shell that still passes no handler now HIDES action items rather than rendering them dead (objectui: packages/layout/src/NavigationRenderer.tsx:971) — the renderer stops manufacturing the trap. Also note that filterAppForUser USED to walk only the top-level `navigation` tree — it never read `item.areas` at all (it returned early when `navigation` was absent), while the client area switcher renders every area. That made area-level `visible` / `requiredPermissions` FAIL-OPEN gates, not merely unread: a \"hidden\" or permission-gated area showed to everyone. Closed in #4722 for the layer that survived the retirement — the server now runs the same filterNav over every `areas[].navigation` — see the `areas.navigation` row below. AREA GATES, 17.0.0 (#4651): both keys REMOVED and their rows DELETED — NavigationAreaSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Route B (remove) over route A (enforce) was the maintainer's call: enforcing needs semantics decided first (does filtering an area remove its items everywhere? does the server bind `user` for area CEL?), which the 17.0.0 window could not hold, and a gate that never gated is strictly safer removed than shipped for a whole major. The strict rejection carries the prescription (ui/app.zod.ts AREA_VISIBLE_RETIRED / AREA_REQUIRED_PERMISSIONS_RETIRED) and names the layers that DO enforce. The boundary those prescriptions pointed at — per-item gating inside an area being shell-side only — was the real gap #4651 left behind, and #4722 closed it: item-level `requiredPermissions` / `requiresService` are now stripped server-side inside `areas[]` too, `visible` (CEL) deliberately not. Recorded on `areas.navigation` below. The area-LEVEL keys remain retired; they were not revived. Seeded 2026-08-01 (#4488). CONTEXT SELECTORS, 17.0.0 (#4509): `includeAll` and `placement` rows DELETED — AppContextSelectorSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Both were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied), which made removal the only channel that could reach an author. `includeAll` was the sharp one: not unread but deliberately DISOBEYED — selectors are mandatory-scope, and an All row would clear the scope, which on Studio's package selector means listing the platform's own system/cloud kernel packages. STUDIO_APP authored `includeAll: true` against a renderer that ignored it, and that authoring site went with the key.", + "_note": "AppSchema — the navigation shell, the densest hand-authored surface on the platform. Consumers: the REST read layer's filterAppForUser (packages/rest/src/rest-server.ts:2651-2740 — the SERVER-side authority for app/nav permission + capability gating and the ADR-0045 publish gate, which judges `_unpublished` and NOT `hidden` since #4829), the spec i18n translateApp (i18n-resolver.ts:472), and objectui's shell (@940ba24: app-shell AppSidebar/ConsoleLayout/ContextSelectors, layout NavigationRenderer, console RootLandingRedirect). The #4001/#4142 app step already retired seven dead keys as retiredKey tombstones — they stay in the walked shape, so their rows stay here (tombstone rule, orphans.mts). WALK BOUNDARY (#3095 union rule): `navigation` drills into the union's FIRST member (the `object` variant + base keys); the other variants' payload keys sit outside the walk and were verified by hand — dashboardName (NavigationRenderer.tsx:433), pageName (:435-442), url/target (:462), reportName (:460), componentRef (:464,:644), group `expanded` (:856) all live. The one GAP found there is now CLOSED (#4509, objectui @e8bec83): an `action` item's click dispatches through a host-supplied `onAction` prop that no shipped shell passed, so `actionDef.actionName` reached no dispatcher and every such item dead-clicked. objectui's `useNavActionDispatch` (objectui: packages/app-shell/src/hooks/useNavActionDispatch.ts) resolves the name against `action` metadata and dispatches through the console action runtime, and UnifiedSidebar passes it (objectui: packages/app-shell/src/layout/UnifiedSidebar.tsx:473). A shell that still passes no handler now HIDES action items rather than rendering them dead (objectui: packages/layout/src/NavigationRenderer.tsx:971) — the renderer stops manufacturing the trap. Also note that filterAppForUser USED to walk only the top-level `navigation` tree — it never read `item.areas` at all (it returned early when `navigation` was absent), while the client area switcher renders every area. That made area-level `visible` / `requiredPermissions` FAIL-OPEN gates, not merely unread: a \"hidden\" or permission-gated area showed to everyone. Closed in #4722 for the layer that survived the retirement — the server now runs the same filterNav over every `areas[].navigation` — see the `areas.navigation` row below. AREA GATES, 17.0.0 (#4651): both keys REMOVED and their rows DELETED — NavigationAreaSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Route B (remove) over route A (enforce) was the maintainer's call: enforcing needs semantics decided first (does filtering an area remove its items everywhere? does the server bind `user` for area CEL?), which the 17.0.0 window could not hold, and a gate that never gated is strictly safer removed than shipped for a whole major. The strict rejection carries the prescription (ui/app.zod.ts AREA_VISIBLE_RETIRED / AREA_REQUIRED_PERMISSIONS_RETIRED) and names the layers that DO enforce. The boundary those prescriptions pointed at — per-item gating inside an area being shell-side only — was the real gap #4651 left behind, and #4722 closed it: item-level `requiredPermissions` / `requiresService` are now stripped server-side inside `areas[]` too, `visible` (CEL) deliberately not. Recorded on `areas.navigation` below. The area-LEVEL keys remain retired; they were not revived. Seeded 2026-08-01 (#4488). CONTEXT SELECTORS, 17.0.0 (#4509): `includeAll` and `placement` rows DELETED — AppContextSelectorSchema is strict, so the keys left the walked shape and retained rows would report ORPHAN. Both were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied), which made removal the only channel that could reach an author. `includeAll` was the sharp one: not unread but deliberately DISOBEYED — selectors are mandatory-scope, and an All row would clear the scope, which on Studio's package selector means listing the platform's own system/cloud kernel packages. STUDIO_APP authored `includeAll: true` against a renderer that ignored it, and that authoring site went with the key. 2026-08-28 (#13003): every `path:NNN` citation in this file was re-anchored to its consuming symbol; nine of the ten were wrong, all of them IN RANGE. Two of this note's own pointers rotted the same way and are corrected here: `filterAppForUser` is a thin delegate today and the gate lives in `packages/rest/src/rest-server.ts#filterAppForUserWithReason`, and the spec i18n entry point is `packages/spec/src/system/i18n-resolver.ts#translateApp`.", "props": { "name": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:478", - "note": "routing identity (`/apps/`) and the translation-bundle key (`apps..*`); objectui RootLandingRedirect routes by it." + "verifiedAt": "2026-08-28", + "evidence": "packages/spec/src/system/i18n-resolver.ts#translateApp (`const appName = doc.name` — the routing identity is also the translation-bundle key, so every `apps..*` lookup in this file is addressed by it and a renamed app loses its whole bundle at once rather than half of it)", + "note": "routing identity (`/apps/`) and the translation-bundle key (`apps..*`); objectui RootLandingRedirect routes by it. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `i18n-resolver.ts:478` had rotted onto `opts?: ResolveOptions`, a PARAMETER in `resolveActionResultDialog`'s signature ~195 lines above the app resolvers. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:481; objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:327-355 (switcher/header)", - "note": "localized on serve by translateApp, rendered by the app switcher and shell header." + "verifiedAt": "2026-08-28", + "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (reads `apps..label` down the locale chain); packages/spec/src/system/i18n-resolver.ts#translateApp (applies it over the authored value on every /meta app read); objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:327-355 (switcher/header)", + "note": "localized on serve by translateApp, rendered by the app switcher and shell header. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:481` had rotted onto `if (!spec) return spec;`, a guard on the ACTION result-dialog path. Re-closed by hand against 8cb96ec41." }, "description": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:333; framework packages/spec/src/system/i18n-resolver.ts:482", - "note": "rendered under the active app's title; localized by translateApp." + "verifiedAt": "2026-08-28", + "evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:333; framework packages/spec/src/system/i18n-resolver.ts#lookupAppAttr (reads `apps..description`); packages/spec/src/system/i18n-resolver.ts#translateApp", + "note": "rendered under the active app's title; localized by translateApp. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:482` had rotted onto the `title` lookup inside `resolveActionResultDialog`; it, `:481` (label) and `:478` (name) were three consecutive lines of ONE unrelated function, which is what a block of pointers looks like after the file above them grows. Re-closed by hand against 8cb96ec41." }, "icon": { "status": "live", @@ -46,9 +46,9 @@ }, "hidden": { "status": "live", - "verifiedAt": "2026-08-09", - "evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:178; packages/platform-objects/src/apps/account.app.ts:40", - "note": "NAVIGATION PRESENTATION ONLY, and re-verified as such at #4829: keep the app out of the App Switcher; the shell surfaces it from the avatar menu (the built-in Account app is the canonical author). It is NOT an access gate — a hidden app stays fully routable and permission-checked for every user, which is its birth contract in app.zod.ts. Between ADR-0045 (2026-06-12) and its 2026-08-09 amendment the REST gate ALSO read this key as \"unpublished\", which erased the Account app from GET /meta/app for every non-builder; that reading now lives on `_unpublished`. The consumer is therefore the client switcher alone." + "verifiedAt": "2026-08-28", + "evidence": "objectui @940ba24: packages/app-shell/src/layout/AppSidebar.tsx:178; packages/platform-objects/src/apps/account.app.ts#ACCOUNT_APP (the canonical author — `hidden: true` under the comment that states why: surface via the avatar dropdown, not the App Switcher)", + "note": "NAVIGATION PRESENTATION ONLY, and re-verified as such at #4829: keep the app out of the App Switcher; the shell surfaces it from the avatar menu (the built-in Account app is the canonical author). It is NOT an access gate — a hidden app stays fully routable and permission-checked for every user, which is its birth contract in app.zod.ts. Between ADR-0045 (2026-06-12) and its 2026-08-09 amendment the REST gate ALSO read this key as \"unpublished\", which erased the Account app from GET /meta/app for every non-builder; that reading now lives on `_unpublished`. The consumer is therefore the client switcher alone. 2026-08-28: RE-ANCHORED (#13003) — the framework pointer was ACCURATE (`account.app.ts:40` is still `hidden: true`), so this leg is the grammar migration and not a repair. What the anchor buys is that it survives the file growing above it, which is exactly how this ledger's sibling citations rotted. Re-closed by hand against 8cb96ec41." }, "navigation": { "children": { @@ -60,9 +60,9 @@ }, "label": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:456; objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:284-316 (resolveNavItemLabel)", - "note": "rendered everywhere; translateApp swaps in the per-locale label by node id." + "verifiedAt": "2026-08-28", + "evidence": "packages/spec/src/system/i18n-resolver.ts#lookupNavLabel (reads `apps..navigation..label` — keyed by the node id, one flat keyspace regardless of tree depth); packages/spec/src/system/i18n-resolver.ts#translateApp (walks the tree and swaps the label in); objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:284-316 (resolveNavItemLabel)", + "note": "rendered everywhere; translateApp swaps in the per-locale label by node id. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:456` had rotted onto `opts?: ResolveOptions` in `lookupActionResultDialogText`'s signature, ~195 lines above `lookupNavLabel`. Re-closed by hand against 8cb96ec41." }, "icon": { "status": "live", @@ -95,9 +95,9 @@ }, "requiredPermissions": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/rest-server.ts:1844; objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:894", - "note": "enforced in BOTH layers: the server strips unsatisfied entries before serving — from the top-level navigation tree AND, since #4722, from every `areas[].navigation` tree through the same filterNav — and the client re-gates per item." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#filterNav (the server strips an entry whose `requiredPermissions` are not a subset of the caller's system permissions — from the top-level tree AND, since #4722, from every `areas[].navigation` tree through this same closure); objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:894", + "note": "enforced in BOTH layers: the server strips unsatisfied entries before serving — from the top-level navigation tree AND, since #4722, from every `areas[].navigation` tree through the same filterNav — and the client re-gates per item. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1844` had rotted onto a bare `try {` inside `computeExecCtx`'s auth-service lookup, ~320 lines above the gate. Re-closed by hand against 8cb96ec41." }, "requiresObject": { "status": "live", @@ -107,9 +107,9 @@ }, "requiresService": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/rest-server.ts:1832; objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:900-902", - "note": "ADR-0057 D10 capability gate, server + client." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#filterNav (ADR-0057 D10 — an entry naming a service the kernel has not registered is dropped before the tree is served; fail-open when the kernel cannot be probed); objectui @940ba24: packages/layout/src/NavigationRenderer.tsx:900-902", + "note": "ADR-0057 D10 capability gate, server + client. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1832` had rotted onto `environmentId = await this.resolveRequestEnvironmentId(environmentId, req)` in `computeExecCtx`, its sibling `requiredPermissions` twelve lines below it in the same unrelated block. Re-closed by hand against 8cb96ec41." }, "type": { "status": "live", @@ -241,9 +241,9 @@ }, "requiredPermissions": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/rest-server.ts:1814", - "note": "SERVER-enforced: an app whose required permissions are not a subset of the caller's system permissions is dropped from /meta entirely (and the single-item GET re-checks at rest-server.ts:3298)." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#filterAppForUserWithReason (`const reqApp = Array.isArray(item.requiredPermissions) ? item.requiredPermissions : []` — an app whose required permissions are not a subset of the caller's is dropped from /meta entirely, and the single-item GET re-checks through this same function)", + "note": "SERVER-enforced: an app whose required permissions are not a subset of the caller's system permissions is dropped from /meta entirely (and the single-item GET re-checks at rest-server.ts:3298). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1814` had rotted onto the closing `};` of a `booksRequest` literal in an unrelated metadata read, ~340 lines above the gate; the note's own \"single-item GET re-checks at rest-server.ts:3298\" had rotted with it, and that re-check now runs through `filterAppForUserWithReason` from the single-item branch ~2,000 lines further down. Re-closed by hand against 8cb96ec41." }, "defaultAgent": { "status": "live", @@ -288,9 +288,9 @@ }, "_unpublished": { "status": "live", - "verifiedAt": "2026-08-09", - "evidence": "packages/rest/src/rest-server.ts:2669; packages/runtime/src/domains/packages.ts:245", - "note": "MACHINE-MANAGED publish gate (ADR-0045 §3, amended 2026-08-09 / #4829) — never authored: written by the AI additive-materialization path (cloud) and cleared by POST /packages/:id/publish-drafts. SERVER-enforced: filterAppForUser withholds an unpublished app from every metadata response except a builder's (studio/setup access), for direct-URL preview. Declared on AppSchema rather than omitted because the write path validates against that schema (saveMetaItem → 422; Registry.validate('app') → AppSchema.parse), so the flip itself would be unwritable otherwise. Stored pre-amendment rows carrying `hidden: true` are rewritten here by the ADR-0087 conversion `app-hidden-to-unpublished`." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#filterAppForUserWithReason (`if (item._unpublished === true && !sysPerms.has('studio.access') && !sysPerms.has('setup.access'))` — the ADR-0045 publish gate, which judges THIS key and deliberately not `hidden`); packages/runtime/src/domains/packages.ts#handlePackagesRequest (POST /packages/:id/publish-drafts flips it to `false` — never deletes it, because ADR-0045 §3 keeps publish and unpublish symmetric)", + "note": "MACHINE-MANAGED publish gate (ADR-0045 §3, amended 2026-08-09 / #4829) — never authored: written by the AI additive-materialization path (cloud) and cleared by POST /packages/:id/publish-drafts. SERVER-enforced: filterAppForUser withholds an unpublished app from every metadata response except a builder's (studio/setup access), for direct-URL preview. Declared on AppSchema rather than omitted because the write path validates against that schema (saveMetaItem → 422; Registry.validate('app') → AppSchema.parse), so the flip itself would be unwritable otherwise. Stored pre-amendment rows carrying `hidden: true` are rewritten here by the ADR-0087 conversion `app-hidden-to-unpublished`. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, both legs — `rest-server.ts:2669` had rotted onto a docblock about a diagnostic view being neither translated nor cached, ~480 lines below the gate, and `packages.ts:245` onto the domain-wide anonymous-deny floor, ~270 lines above the flip. Re-closed by hand against 8cb96ec41." } } } diff --git a/packages/spec/liveness/capability.json b/packages/spec/liveness/capability.json index 48dbbc7ff0..bdc997f021 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.", + "_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.", "props": { "name": { "status": "live", - "verifiedAt": "2026-08-08", - "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts:203-210 (upsertPackageCapability — the name is the `sys_capability` upsert key, is matched against PLATFORM_CAPABILITY_NAMES to refuse shadowing a curated capability, and is the string `systemPermissions` / `requiredPermissions` resolve a grant by); packages/lint/src/validate-capability-references.ts:99 (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)." + "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." }, "label": { "status": "live", - "verifiedAt": "2026-08-08", - "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts:122 (capabilityRowFields — written to sys_capability.label, falling back to humanize(name) when absent)", - "note": "display (Setup's capability list/detail, rendered from the sys_capability row)." + "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." }, "description": { "status": "live", - "verifiedAt": "2026-08-08", - "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts:123 (capabilityRowFields — written to sys_capability.description, falling back to `Capability .` when absent)", - "note": "display." + "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." }, "scope": { "status": "live", - "verifiedAt": "2026-08-08", - "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts:124 (capabilityRowFields — written to sys_capability.scope, normalized to 'org' only 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`." + "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." }, "packageId": { "status": "live", - "verifiedAt": "2026-08-08", - "evidence": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts:294 (`const packageId = cap._packageId ?? cap.packageId` — the ADR-0086 D3 author-declared FALLBACK provenance, consulted when the registry stamp is absent; with neither, upsertPackageCapability takes the no-owning-package refusal at :214 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." + "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." }, "_lock": null, "_lockReason": null, diff --git a/packages/spec/liveness/dashboard.json b/packages/spec/liveness/dashboard.json index 5d02d881a5..e0cedc8009 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.", + "_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.", "props": { "name": { "status": "live", @@ -67,9 +67,9 @@ }, "requiresService": { "status": "live", - "verifiedAt": "2026-08-03", - "evidence": "packages/rest/src/rest-server.ts:1921-1931 (filterDashboardForUser — ADR-0057 D10), called at :3037 and :3476; packages/rest/src/rest.test.ts:3271-3305", - "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." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.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." }, "actionUrl": { "status": "dead", @@ -130,15 +130,15 @@ }, "filterBindings": { "status": "live", - "verifiedAt": "2026-08-03", - "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:280", - "note": "per-widget binding of a dashboard-level filter to one of this widget's fields, or `false` to opt out (framework#2501)." + "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." }, "suppressWarnings": { "status": "live", - "verifiedAt": "2026-08-03", - "evidence": "packages/lint/src/validate-widget-bindings.ts:367 (rule-id suppression consulted by every widget-binding diagnostic; ids emitted at :346, :417, :610, :640)", - "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)." + "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." }, "responsive": { "status": "dead", diff --git a/packages/spec/liveness/field.json b/packages/spec/liveness/field.json index 61a98b8d03..f3a611238f 100644 --- a/packages/spec/liveness/field.json +++ b/packages/spec/liveness/field.json @@ -1,6 +1,6 @@ { "type": "field", - "_note": "FieldSchema (flat — all field-type configs are top-level optional props). Seeded from docs/audits/2026-06-fieldschema-property-liveness.md. The wholly-dead nested config objects (vectorConfig/fileAttachmentConfig/encryptionConfig/maskingRule/cached/dataQuality) were removed (#2377 + earlier pruning); currencyConfig is LIVE via the objectui renderer. Naming-drift props are server-live but client-snake. Framework evidence cited with paths; objectui-renderer evidence as prose.", + "_note": "FieldSchema (flat — all field-type configs are top-level optional props). Seeded from docs/audits/2026-06-fieldschema-property-liveness.md. The wholly-dead nested config objects (vectorConfig/fileAttachmentConfig/encryptionConfig/maskingRule/cached/dataQuality) were removed (#2377 + earlier pruning); currencyConfig is LIVE via the objectui renderer. Naming-drift props are server-live but client-snake. Framework evidence cited with paths; objectui-renderer evidence as prose. 2026-08-28 (#13003): seven of this file's eight `path:NNN` citations were re-anchored to their consuming symbols. The eighth — `conditionalRequired` — is deliberately NOT re-anchored and NOT re-stamped: its cited `.transform` lowering was removed in protocol 17 (#3855), the key is now a `retiredKey` tombstone, and the fallback this row's note credits `rule-validator.ts` with keeping is gone too (that module documents this alias as retired and pins the absence with a test). The `live` verdict is therefore falsified, which is an ADR-0049 re-classification with its own card shape — the `action.execute` / #13036 precedent — and is reported on #13003 rather than executed here.", "props": { "name": { "status": "live", @@ -77,9 +77,9 @@ }, "requiredWhen": { "status": "live", - "verifiedAt": "2026-08-25", - "evidence": "packages/objectql/src/validation/rule-validator.ts:1792-1810 (evaluates the field's requiredWhen predicate on the write path and reports the unevaluable/unbound cases by name); packages/objectql/src/validation/rule-validator.ts:762-763 (fieldRequiresParentRoot — a parent-scoped predicate widens the binding, #4977)", - "note": "CEL. 2026-08-25: REPOINTED — the evidence cited its SIBLING packages/objectql/src/validation/record-validator.ts, which has 0 occurrences of `requiredWhen`: that module enforces the static ADR-0113 `required` contract, while the CEL predicate is evaluated one file over in rule-validator.ts. Both files exist and neither citation carried a line, so this was invisible to every check the gate had — the plausible-neighbour shape of citation rot rather than a code movement." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/validation/rule-validator.ts#evaluateValidationRules (`const pred = def?.requiredWhen` — evaluates the field predicate on the write path and reports the unevaluable and unbound cases by name); packages/objectql/src/validation/rule-validator.ts#hasParentScopedRequiredWhen (`readsParentRoot(def.requiredWhen)` — a parent-scoped predicate widens the binding, #4977)", + "note": "CEL. 2026-08-25: REPOINTED — the evidence cited its SIBLING packages/objectql/src/validation/record-validator.ts, which has 0 occurrences of `requiredWhen`: that module enforces the static ADR-0113 `required` contract, while the CEL predicate is evaluated one file over in rule-validator.ts. Both files exist and neither citation carried a line, so this was invisible to every check the gate had — the plausible-neighbour shape of citation rot rather than a code movement. 2026-08-28: RE-ANCHORED (#13003), and REPOINTED IN PROSE — both cited ranges still land inside their functions, so the LINES had not rotted; what had rotted is the second one's parenthetical, which named `fieldRequiresParentRoot`. That is no symbol in this repo (0 occurrences); the function is `hasParentScopedRequiredWhen`. A name written beside a line is exactly what a reader trusts when the line looks right, so it is corrected with it. Re-closed by hand against 8cb96ec41." }, "readonlyWhen": { "status": "live", @@ -172,8 +172,9 @@ }, "unique": { "status": "live", - "evidence": "packages/drivers/driver-sql/src/sql-driver.ts:1853", - "note": "CAVEAT — DDL-only; NOT validated on the write path (violations surface as raw driver errors)." + "verifiedAt": "2026-08-28", + "evidence": "packages/drivers/driver-sql/src/sql-driver.ts#registerManagedObjectMetadata (`isUniqueScopeDeclared(f?.unique)` — a declared unique is what makes the index sync run at all); packages/drivers/driver-sql/src/sql-driver.ts#uniqueIndexesFromFields (turns the declaration into index DDL, tenant-composite where an organization key applies)", + "note": "CAVEAT — DDL-only; NOT validated on the write path (violations surface as raw driver errors). 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1853` had rotted onto `filterArrayReachedDriverError`, a query-filter error helper ~7,650 lines above the first reader, in a 16,290-line file. A line citation into a file that size ages out of usefulness long before it ages out of RANGE, which is why no check could see it. Recorded with the repoint: field-level `unique` is no longer emitted inline by `createColumn` — a composite needs the tenant column to exist first — so the DDL leg genuinely moved, it was not merely displaced. Re-closed by hand against 8cb96ec41." }, "precision": { "status": "live", @@ -192,8 +193,9 @@ }, "reference": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:1672", - "note": "CAVEAT — $expand/cascade/seed live. There is no FK DDL: #11567 retired `createColumn`'s FOREIGN KEY emission, which was gated on the REJECTED alias `reference_to` and so could never fire for a spec-conformant lookup (measured: 0 FK constraints across all 44 exported platform objects on live PG 16.13 + MySQL 8.0.46). Referential integrity is the engine's, via deleteBehavior." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/engine.ts#cascadeDeleteRelations (`const ref = fdef.reference` — the delete-time traversal that IS this platform's referential integrity, per the note below); packages/objectql/src/engine.ts#planCascadeAtomicity (the same read one phase earlier, to plan the cascade); packages/objectql/src/engine.ts#expandRelatedRecords ($expand — asks `referenceTargetOf` rather than reading the key raw, so a type-fixed target such as `user` is not treated as targetless, cloud#983)", + "note": "CAVEAT — $expand/cascade/seed live. There is no FK DDL: #11567 retired `createColumn`'s FOREIGN KEY emission, which was gated on the REJECTED alias `reference_to` and so could never fire for a spec-conformant lookup (measured: 0 FK constraints across all 44 exported platform objects on live PG 16.13 + MySQL 8.0.46). Referential integrity is the engine's, via deleteBehavior. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1672` had rotted onto the closing `);` of a hook-registration error message about `excludeObjects`, ~9,250 lines above the cascade readers. Recorded with the anchors because it changes what the row claims: `$expand` no longer reads this key RAW, it goes through `referenceTargetOf`, so the anchor names the consuming function and the indirection is stated here instead of being implied by a line number. Re-closed by hand against 8cb96ec41." }, "referenceVia": { "status": "live", @@ -203,18 +205,21 @@ }, "autonumberFormat": { "status": "live", - "evidence": "packages/objectql/src/engine.ts:765", - "note": "CAVEAT — runtime sequence formatting live; UI AutoNumberField ignores it." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/engine.ts#applyAutonumbers (`const fmt = resolveAutonumberFormat(def as never)` — canonical `autonumberFormat`, then the `format` shorthand (#1603), then the declared default `{0000}`; one resolver shared with the SQL driver so a format-less field cannot render two shapes, #6555)", + "note": "CAVEAT — runtime sequence formatting live; UI AutoNumberField ignores it. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:765` had rotted onto a `// [#8690]` comment about temporal-filter materializability, ~3,495 lines above the autonumber path. Re-closed by hand against 8cb96ec41." }, "maxLength": { "status": "live", - "evidence": "packages/objectql/src/validation/record-validator.ts:127", - "note": "CAVEAT — server validation honors camelCase, but the client form reads snake_case max_length." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/validation/record-validator.ts#validateOne (`if (def.maxLength !== undefined && s.length > def.maxLength) return fail('max_length', { maxLength: def.maxLength, actual: s.length })` — the write-path seam that makes the declared bound bind)", + "note": "CAVEAT — server validation honors camelCase, but the client form reads snake_case max_length. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:127` had rotted onto `options?: string[]` in the `FieldValidationError` interface, ~443 lines above the check. Re-closed by hand against 8cb96ec41." }, "minLength": { "status": "live", - "evidence": "packages/objectql/src/validation/record-validator.ts:130", - "note": "CAVEAT — server camel; client form reads min_length." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/validation/record-validator.ts#validateOne (`if (def.minLength !== undefined && s.length < def.minLength) return fail('min_length', { minLength: def.minLength, actual: s.length })`)", + "note": "CAVEAT — server camel; client form reads min_length. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:130` had rotted onto `export class ValidationError extends Error`, the error class rather than any check; its sibling `maxLength` cited `:127` three lines above, so the pair had drifted together. Re-closed by hand against 8cb96ec41." }, "displayField": { "status": "live", diff --git a/packages/spec/liveness/flow.json b/packages/spec/liveness/flow.json index 47031052d8..1e78593820 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.", + "_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.", "props": { "name": { "status": "live", @@ -8,11 +8,15 @@ }, "successMessage": { "status": "live", - "evidence": "packages/services/service-automation/src/engine.ts:1292" + "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." }, "errorMessage": { "status": "live", - "evidence": "packages/services/service-automation/src/engine.ts:1348" + "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." }, "label": { "status": "live", @@ -29,8 +33,9 @@ }, "status": { "status": "live", - "evidence": "packages/services/service-automation/src/engine.ts:1374-1382 (obsolete/invalid -> flowEnabled=false), :1387 (gates activateFlowTrigger), :1704 + :3097 (execute-time 'Flow is disabled' guard)", - "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." + "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." }, "runAs": { "status": "live", @@ -40,8 +45,9 @@ }, "type": { "status": "live", - "evidence": "packages/services/service-automation/src/engine.ts:637", - "note": "flow.type==='schedule'/'api' drives trigger dispatch." + "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." }, "variables": { "status": "live", diff --git a/packages/spec/liveness/hook.json b/packages/spec/liveness/hook.json index 54316e59d2..7920f1b70f 100644 --- a/packages/spec/liveness/hook.json +++ b/packages/spec/liveness/hook.json @@ -1,21 +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.", + "_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.", "props": { "name": { "status": "live", - "evidence": "packages/objectql/src/hook-binder.ts:182", - "note": "registration key + log identity." + "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." }, "object": { "status": "live", - "evidence": "packages/objectql/src/hook-binder.ts:171", - "note": "single/array/'*' wildcard honored." + "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." }, "events": { "status": "live", - "evidence": "packages/objectql/src/hook-binder.ts:171", - "note": "per-event registration." + "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." }, "body": { "status": "live", @@ -24,13 +27,15 @@ }, "handler": { "status": "live", - "evidence": "packages/objectql/src/hook-binder.ts:237", - "note": "deprecated but fully wired; body takes precedence." + "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." }, "priority": { "status": "live", - "evidence": "packages/objectql/src/hook-binder.ts:177", - "note": "genuinely orders hooks (lower first)." + "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." }, "async": { "status": "live", @@ -44,27 +49,27 @@ }, "retryPolicy": { "status": "live", - "verifiedAt": "2026-08-09", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "evidence": "packages/objectql/src/hook-wrappers.ts", - "producer": "packages/objectql/src/hook-binder.ts:221 — the binder hands the AUTHORED hook to wrapDeclarativeHook, 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." + "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." }, "timeout": { "status": "live", - "verifiedAt": "2026-08-09", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "evidence": "packages/objectql/src/hook-wrappers.ts", - "producer": "packages/objectql/src/hook-binder.ts:221 (same wrapDeclarativeHook call as retryPolicy)", - "note": "wall-clock abort, independent of body.timeoutMs." + "evidence": "packages/objectql/src/hook-wrappers.ts#wrapDeclarativeHook (step 4 of the wrapper's ladder — a wall-clock abort around the handler, 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. 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." }, "onError": { "status": "live", - "verifiedAt": "2026-08-09", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "evidence": "packages/objectql/src/hook-wrappers.ts", - "producer": "packages/objectql/src/hook-binder.ts:221 (same wrapDeclarativeHook call as retryPolicy)", - "note": "'log' suppresses+continues; 'abort' rethrows." + "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." }, "label": { "status": "dead", diff --git a/packages/spec/liveness/mapping.json b/packages/spec/liveness/mapping.json index b04242281e..4add57c87a 100644 --- a/packages/spec/liveness/mapping.json +++ b/packages/spec/liveness/mapping.json @@ -1,12 +1,12 @@ { "type": "mapping", - "_note": "MappingSchema (#2611 reusable import mapping). Consumers: the REST import path — resolveNamedMapping fetches the artifact by name and validates it against the request (packages/rest/src/import-mapping.ts:60-107), applyMappingToRows runs the fieldMapping pipeline (:115-167), and import-prepare adopts the artifact's mode/upsertKey as request defaults (packages/rest/src/import-prepare.ts:321-326); objectui's ImportWizard offers registered mappings in a saved-mapping picker (@940ba24 packages/plugin-grid/src/ImportWizard.tsx:979). Seeded 2026-08-01 (#4488) at 8 of 11 live; 7 of 7 live since #4509 removed the three that were not. The IMPORT half of the schema is real and loudly enforced (unsupported transforms/formats are 400s, not silent skips — Prime Directive #10). What left in 17.0.0: the EXPORT half (`extractQuery` — no exporter reads a mapping artifact at all) and the two tuning knobs (`errorPolicy`, `batchSize` — error handling belongs to the import REQUEST, and the write path sizes its own batches). Rows DELETED rather than kept: MappingSchema is strict, so the keys left the walked shape and a retained row would report ORPHAN. Both knobs were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied) — removal was the only channel that could reach an author, which is why they went out inside the 17.0.0 window rather than after a deprecation cycle.", + "_note": "MappingSchema (#2611 reusable import mapping). Consumers: the REST import path — resolveNamedMapping fetches the artifact by name and validates it against the request (packages/rest/src/import-mapping.ts:60-107), applyMappingToRows runs the fieldMapping pipeline (:115-167), and import-prepare adopts the artifact's mode/upsertKey as request defaults (packages/rest/src/import-prepare.ts:321-326); objectui's ImportWizard offers registered mappings in a saved-mapping picker (@940ba24 packages/plugin-grid/src/ImportWizard.tsx:979). Seeded 2026-08-01 (#4488) at 8 of 11 live; 7 of 7 live since #4509 removed the three that were not. The IMPORT half of the schema is real and loudly enforced (unsupported transforms/formats are 400s, not silent skips — Prime Directive #10). What left in 17.0.0: the EXPORT half (`extractQuery` — no exporter reads a mapping artifact at all) and the two tuning knobs (`errorPolicy`, `batchSize` — error handling belongs to the import REQUEST, and the write path sizes its own batches). Rows DELETED rather than kept: MappingSchema is strict, so the keys left the walked shape and a retained row would report ORPHAN. Both knobs were unwarnable (schema defaults materialize at parse, so the lint could not tell authored from supplied) — removal was the only channel that could reach an author, which is why they went out inside the 17.0.0 window rather than after a deprecation cycle. 2026-08-28 (#13003): all seven `path:NNN` citations in this file were re-anchored to their consuming symbols. This is the one file in batch 4 whose lines were mostly RIGHT — repaired and re-measured by #11210 on 2026-08-23 — and it is kept as the counter-example: five of the seven landed on the read or inside the statement that performs it, and the two that did not (`mode`, `upsertKey`) had already drifted five lines in five days, onto a guard and a comment inside the right function. Correctness of a line is a property of the day it was measured; the anchor is what makes it a property of the code.", "props": { "name": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/import-mapping.ts:70", - "note": "artifact resolution key for the request's `mappingName` (missing → 404 MAPPING_NOT_FOUND)." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (the artifact is read by `{ type: 'mapping', name: mappingName }`; a miss is 404 MAPPING_NOT_FOUND)", + "note": "artifact resolution key for the request's `mappingName` (missing → 404 MAPPING_NOT_FOUND). 2026-08-28: RE-ANCHORED (#13003) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because #11210 re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", @@ -16,33 +16,33 @@ }, "sourceFormat": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/import-mapping.ts:81-97", - "note": "declared-format gate: `xml`/`sql` are rejected outright (the import endpoint accepts csv/json/xlsx), and a csv-declared mapping applies to xlsx too; a mismatch is a 400, never a silent reinterpretation." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (`const declared = artifact.sourceFormat` — `xml`/`sql` are refused outright with MAPPING_FORMAT_UNSUPPORTED, a csv-declared mapping applies to xlsx too, and any other mismatch is MAPPING_FORMAT_MISMATCH; never a silent reinterpretation)", + "note": "declared-format gate: `xml`/`sql` are rejected outright (the import endpoint accepts csv/json/xlsx), and a csv-declared mapping applies to xlsx too; a mismatch is a 400, never a silent reinterpretation. 2026-08-28: RE-ANCHORED (#13003) — the enclosing function is unchanged, but the cited RANGE's endpoint (`:97`) had drifted onto the sibling `transform === 'javascript'` refusal, a different key's check inside the same function. That is precisely why the anchor names the function and not a span. Re-closed by hand against 8cb96ec41." }, "targetObject": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/import-mapping.ts:75", - "note": "must equal the URL object or the import 400s (MAPPING_TARGET_MISMATCH)." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (`if (artifact.targetObject !== objectName)` → 400 MAPPING_TARGET_MISMATCH)", + "note": "must equal the URL object or the import 400s (MAPPING_TARGET_MISMATCH). 2026-08-28: RE-ANCHORED (#13003) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because #11210 re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." }, "fieldMapping": { "status": "live", - "verifiedAt": "2026-08-23", - "evidence": "packages/rest/src/import-mapping.ts:95-102, packages/rest/src/import-mapping.ts:112-164", - "note": "Lines re-measured 2026-08-23 (#11210): the second range ended at :167 in a 164-line file — the file shrank under the citation and no gate could see it, since the FILE still resolved. The consumer never moved: the javascript rejection is the loop at :95-102 (was :98-105) and the pipeline is applyMappingToRows at :112-164 (was :115-167); the three inline line refs below were off by the same three lines and are corrected with them. the pipeline itself: source/target/transform/params all consumed. none/constant/map/split/join applied in applyMappingToRows (`params.separator` :121, `.value` :129, `.valueMap` :134); `lookup` copies through for the pipeline's metaMap reference resolution; `javascript` is REJECTED with a 400 (no server sandbox — implement-or-reject-loudly). SUB-WALK BOUNDARY, resolved: `params`' lookup-specific keys (`object`/`fromField`/`toField`/`autoCreate`) were read by nothing — reference resolution comes from the target object's own field definitions, not from these — and were REMOVED in the 17.x line (#10329, ADR-0049 enforce-or-remove; strict deletion, so nothing dead remains one level below the drill). Every spelling — the four canonical keys and their eleven ex-aliases — now lands on a guidance prescription at parse; the mapping-lookup-params-removed conversion (protocol 18) strips them from stored sources. The surviving params keys (`value`/`valueMap`/`separator`) are all consumed by applyMappingToRows." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/import-mapping.ts#resolveNamedMapping (the `transform === 'javascript'` refusal — implement-or-reject-loudly, there is no server sandbox); packages/rest/src/import-mapping.ts#applyMappingToRows (the pipeline itself: source/target/transform/params, with none/constant/map/split/join applied here and `lookup` copied through for the pipeline's reference resolution)", + "note": "Lines re-measured 2026-08-23 (#11210): the second range ended at :167 in a 164-line file — the file shrank under the citation and no gate could see it, since the FILE still resolved. The consumer never moved: the javascript rejection is the loop at :95-102 (was :98-105) and the pipeline is applyMappingToRows at :112-164 (was :115-167); the three inline line refs below were off by the same three lines and are corrected with them. the pipeline itself: source/target/transform/params all consumed. none/constant/map/split/join applied in applyMappingToRows (`params.separator` :121, `.value` :129, `.valueMap` :134); `lookup` copies through for the pipeline's metaMap reference resolution; `javascript` is REJECTED with a 400 (no server sandbox — implement-or-reject-loudly). SUB-WALK BOUNDARY, resolved: `params`' lookup-specific keys (`object`/`fromField`/`toField`/`autoCreate`) were read by nothing — reference resolution comes from the target object's own field definitions, not from these — and were REMOVED in the 17.x line (#10329, ADR-0049 enforce-or-remove; strict deletion, so nothing dead remains one level below the drill). Every spelling — the four canonical keys and their eleven ex-aliases — now lands on a guidance prescription at parse; the mapping-lookup-params-removed conversion (protocol 18) strips them from stored sources. The surviving params keys (`value`/`valueMap`/`separator`) are all consumed by applyMappingToRows. 2026-08-28: RE-ANCHORED (#13003) — both ranges land inside their functions (their endpoints `:102` and `:164` are the functions' own closing braces), so this is the grammar migration. Worth recording beside the 2026-08-23 entry above: that repair moved these pointers three lines and they were right for five days; the anchor is the version that does not need re-measuring. Re-closed by hand against 8cb96ec41." }, "mode": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/import-prepare.ts:322", - "note": "an artifact declaring update/upsert sets the import's writeMode default (an explicit request `writeMode` still wins)." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/import-prepare.ts#prepareImportRequest (`if (body?.writeMode === undefined && (mappingArtifact.mode === 'update' || mappingArtifact.mode === 'upsert')) writeMode = mappingArtifact.mode` — a DEFAULT; an explicit request `writeMode` still wins)", + "note": "an artifact declaring update/upsert sets the import's writeMode default (an explicit request `writeMode` still wins). 2026-08-28: RE-ANCHORED (#13003) — the cited line was inside the consuming function, so this is the grammar migration rather than a repair. This file is the batch's counter-example and worth keeping as one: its lines were right because #11210 re-measured them five days ago, and the anchor is what stops the next edit ABOVE the function from making them wrong again. Re-closed by hand against 8cb96ec41." }, "upsertKey": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/import-prepare.ts:325", - "note": "adopted as the upsert match fields when the request names none." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/import-prepare.ts#prepareImportRequest (`if (matchFields.length === 0 && Array.isArray(mappingArtifact.upsertKey))` — adopted as the upsert match fields only when the request names none)", + "note": "adopted as the upsert match fields when the request names none. 2026-08-28: RE-ANCHORED (#13003) — `:325` sat on a comment line two lines above the read, inside the right function. The grammar migration, not a repair. Re-closed by hand against 8cb96ec41." } } } diff --git a/packages/spec/liveness/qa.json b/packages/spec/liveness/qa.json index bbe0631104..239b2d6d0a 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.", + "_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.", "props": { "name": { "status": "dead", @@ -12,10 +12,10 @@ "children": { "id": { "status": "live", - "evidence": "packages/core/src/qa/runner.ts:101 (TestResult.scenarioId), :47 (the same id on the setup-failure path)", - "verifiedAt": "2026-08-10", + "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 (packages/cli/src/commands/test.ts:104) and the ONLY human-readable handle a failing run gives you — `name` is not printed anywhere. 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." + "note": "The scenario identity in every result the CLI prints (packages/cli/src/commands/test.ts:104) and the ONLY human-readable handle a failing run gives you — `name` is not printed anywhere. 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." }, "name": { "status": "dead", @@ -37,24 +37,24 @@ }, "setup": { "status": "live", - "evidence": "packages/core/src/qa/runner.ts:41-54 (run before the main steps; a throw here aborts the scenario with 'Setup failed' and no step results)", - "verifiedAt": "2026-08-10", + "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." + "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." }, "steps": { "status": "live", - "evidence": "packages/core/src/qa/runner.ts:62-83 (the main sequence; stops at the first failing step)", - "verifiedAt": "2026-08-10", + "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`." + "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." }, "teardown": { "status": "live", - "evidence": "packages/core/src/qa/runner.ts:86-98 (runs even after a failed step, and does not mask an existing failure)", - "verifiedAt": "2026-08-10", + "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." + "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." }, "requires": { "status": "dead", diff --git a/packages/spec/liveness/seed.json b/packages/spec/liveness/seed.json index c0a99d973c..a25907adf9 100644 --- a/packages/spec/liveness/seed.json +++ b/packages/spec/liveness/seed.json @@ -1,38 +1,38 @@ { "type": "seed", - "_note": "SeedSchema. Fully live — the smallest and healthiest surface in the ledger. 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).", + "_note": "SeedSchema. Fully live — the smallest and healthiest surface in the ledger. 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.", "props": { "object": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/metadata-protocol/src/seed-loader.ts:98", - "note": "target object; also the dependency-graph node key (topological insert order)." + "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." }, "externalId": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/metadata-protocol/src/seed-loader.ts:119", - "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)." + "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." }, "mode": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/metadata-protocol/src/seed-loader.ts:245", - "note": "insert/update/upsert/replace/ignore — drives decideWriteAction/writeRecord." + "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." }, "env": { "status": "live", - "verifiedAt": "2026-08-09", + "verifiedAt": "2026-08-28", "evidenceScope": "in-repo", - "evidence": "packages/metadata-protocol/src/seed-loader.ts:191 (filterByEnv drops datasets whose env list excludes the running environment)", - "producer": "packages/metadata-protocol/src/seed-loader.ts:174 — `load()` resolves the comparison environment ITSELF (resolveEnvConfig, :1809, off NODE_ENV) before anything reads config, rather than trusting a caller to pass it", - "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." + "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." }, "records": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/metadata-protocol/src/seed-loader.ts:434", - "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." + "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." } } } diff --git a/packages/spec/liveness/translation.json b/packages/spec/liveness/translation.json index d177ea7d2c..6ffe641c3f 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 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, settings..keys..options., …) are governed by the resolvers cited per row, not by ledger rows. 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. 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. Seeded 2026-08-01 (#4488).", + "_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 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, settings..keys..options., …) are governed by the resolvers cited per row, not by ledger rows. 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. 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. 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.", "props": { "name": { "status": "live", @@ -20,45 +20,45 @@ }, "locale": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/core/src/fallbacks/authored-translation-sync.ts:140-148", - "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." + "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." }, "objects": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:735, :751, :159, :197, :873, :900; 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." + "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." }, "apps": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:442, :456; packages/rest/src/rest-server.ts:2001", - "note": "translateApp swaps app label/description and walks the navigation tree replacing node labels by id — applied on every /meta app read." + "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." }, "messages": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/plugins/plugin-audit/src/audit-writers.ts:562-579, :744-745", - "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." + "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." }, "globalActions": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:200, :249", - "note": "the object-less fallback for action label/confirmText/successMessage/params/resultDialog — resolveAction* checks objects.._actions first, then here." + "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." }, "dashboards": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:538, :554", - "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." + "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#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." }, "pages": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/spec/src/system/i18n-resolver.ts:636", - "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)." + "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." }, "flows": { "status": "planned", @@ -87,9 +87,9 @@ }, "metadataForms": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/rest/src/rest-server.ts:2058, :2062", - "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." + "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." }, "settingsCommon": { "status": "live", diff --git a/packages/spec/liveness/validation.json b/packages/spec/liveness/validation.json index 82a6ea278b..8d5b57e229 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.", + "_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.", "props": { "name": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:665, packages/objectql/src/validation/rule-validator.ts:676", - "note": "names the rule in violation logs and the broken-rule skip warning." + "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." }, "label": { "status": "dead", @@ -22,21 +22,21 @@ }, "active": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:647", - "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)." + "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." }, "events": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:654", - "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." + "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." }, "priority": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:657", - "note": "stable low-number-first sort of the evaluation order." + "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." }, "tags": { "status": "dead", @@ -46,27 +46,27 @@ }, "severity": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:671-678", - "note": "only 'error' blocks the write; 'warning'/'info' violations are logged and let the write proceed." + "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." }, "message": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:676, packages/objectql/src/engine.ts:3703", - "note": "the author-written violation text carried on every FieldValidationError (surfaced as 400 VALIDATION_FAILED); per-deployment overrides resolve via validationMessages in the translation bundle (#3957) without touching the authored value." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/validation/rule-validator.ts#checkPredicate (`message: rule.message` on the violation); packages/objectql/src/validation/rule-validator.ts#checkStateMachine (an author-written message wins over the generated fallback, untouched); 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); per-deployment overrides resolve via validationMessages in the translation bundle (#3957) without touching the authored value. 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." }, "type": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:692-706", - "note": "the union discriminant: dispatches to the state_machine/predicate/format/json_schema/conditional checkers; the schema admits exactly the handled set." + "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." }, "condition": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/validation/rule-validator.ts:697", - "note": "the CEL predicate (script/cross_field variants), evaluated against the merged record + previous — TRUE fails the write." + "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." } } } diff --git a/packages/spec/liveness/view.json b/packages/spec/liveness/view.json index edc97eddd0..f203b78f2d 100644 --- a/packages/spec/liveness/view.json +++ b/packages/spec/liveness/view.json @@ -1,6 +1,6 @@ { "type": "view", - "_note": "ViewSchema (object view container: list/form/listViews/formViews). Seeded from docs/audits/2026-06-viewschema-property-liveness.md (objectui consumer cross-reference) and re-verified against objectui@fb35e48 (2026-07-16) — several audit-era DEAD findings have since gone LIVE (submitBehavior, sharing.lockedBy, ViewData providers on the list path) and are classified from the current reads, not the stale audit lines. objectui paths cited as 'objectui: ' prose (not resolved against this repo). Sub-sub-key divergences (userActions.buttons, addRecord.mode/formView, tabs[].order) can't be drilled (one level only) — captured in the parent entry's note and tracked in the enforce-or-remove worklist (#2998 Track B / ADR-0049). 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. widget.performance (no ledger file of its own) was verified dead the corrected way — zero readers in either repo; objectui virtual scrolling reads the LIVE top-level virtualScroll — and removed with them.", + "_note": "ViewSchema (object view container: list/form/listViews/formViews). Seeded from docs/audits/2026-06-viewschema-property-liveness.md (objectui consumer cross-reference) and re-verified against objectui@fb35e48 (2026-07-16) — several audit-era DEAD findings have since gone LIVE (submitBehavior, sharing.lockedBy, ViewData providers on the list path) and are classified from the current reads, not the stale audit lines. objectui paths cited as 'objectui: ' prose (not resolved against this repo). Sub-sub-key divergences (userActions.buttons, addRecord.mode/formView, tabs[].order) can't be drilled (one level only) — captured in the parent entry's note and tracked in the enforce-or-remove worklist (#2998 Track B / ADR-0049). 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. widget.performance (no ledger file of its own) was verified dead the corrected way — zero readers in either repo; objectui virtual scrolling reads the LIVE top-level virtualScroll — and removed with them. 2026-08-28 (#13003): all five `path:NNN` citations in this file were re-anchored to their consuming symbols, and all five were wrong. Two pairs shared one wrong line each — `list.name`/`listViews` on a schema declaration, `form.sharing`/`formViews` on a comment about API routes — and `object` was falsified in PROSE as well as position, naming a function that lives in another package.", "props": { "name": { "status": "dead", @@ -16,16 +16,17 @@ }, "object": { "status": "live", - "verifiedAt": "2026-08-01", - "evidence": "packages/objectql/src/engine.ts:1653 (getViewsByObject() / GET /meta/view?object=)", - "note": "How a stack-level `views: [...]` entry says which object its views belong to — the container is 'view definitions for a specific object' (this file's own note on ObjectListViewSchema), and the object index is what reads the binding. Declared in #4001 batch 6e: it was undeclared and therefore stripped, so a container's object binding was dropped on every parse. Found by the CLI's migrate e2e, whose fixture carries it." + "verifiedAt": "2026-08-28", + "evidence": "packages/objectql/src/engine.ts#resolveMetadataItemName (`return item?.object || item?.list?.data?.object || item?.form?.data?.object` — a stack-level `views:` container has no top-level name, so this key IS its registry key and therefore the object every expanded ViewItem is stamped with); packages/metadata/src/metadata-manager.ts#getViewsByObject (`v.object === object` — the index `GET /meta/view?object=` answers from)", + "note": "How a stack-level `views: [...]` entry says which object its views belong to — the container is 'view definitions for a specific object' (this file's own note on ObjectListViewSchema), and the object index is what reads the binding. Declared in #4001 batch 6e: it was undeclared and therefore stripped, so a container's object binding was dropped on every parse. Found by the CLI's migrate e2e, whose fixture carries it. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED, position AND prose — `engine.ts:1653` had rotted onto a hook `excludeObjects` guard ~361 lines below the resolver, and the parenthetical credited `getViewsByObject()` to THAT file, where it does not exist: it lives in `packages/metadata/src/metadata-manager.ts`. Both halves of a citation can be wrong at once, and the prose half is the one a reader trusts when the line still looks plausible. Re-closed by hand against 8cb96ec41." }, "list": { "children": { "name": { "status": "live", - "evidence": "packages/spec/src/ui/view.zod.ts:1432", - "note": "view identity — expandViewContainer key; objectui ListView/ViewTabBar." + "verifiedAt": "2026-08-28", + "evidence": "packages/spec/src/ui/view.zod.ts#expandViewContainerWithDiagnostics (`const key = typeof defaultList.name === 'string' && defaultList.name ? defaultList.name : 'default'` — the default list view's own name becomes its `.` identity, and a collision with a `listViews` key is renamed and diagnosed)", + "note": "view identity — expandViewContainer key; objectui ListView/ViewTabBar. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1432` had rotted onto `}, {`, a shape-boundary line of the view-config schema ~2,974 lines above the expander. It was a DECLARATION site rather than a consumer even when it was right; the key's liveness rests on the expander reading it, which is what the anchor names. Re-closed by hand against 8cb96ec41." }, "label": { "status": "live", @@ -335,8 +336,9 @@ }, "sharing": { "status": "live", - "evidence": "packages/rest/src/rest-server.ts:4384", - "note": "Framework-side consumer: public (anonymous) form endpoints opt in via FormView.sharing — /forms/:slug resolves sharing.publicLink across form + formViews (rest-server.ts:1716, :4366-4405) and grants publicFormGrant. Renderer-side the config is not read (audit L20) — the public form is served, not re-parsed client-side." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#findPublicFormView (`slugMatchesPublicLink(sharing.publicLink, slug)` — scanned across `form` and every `formViews` entry); packages/rest/src/rest-server.ts#resolveFormBySlug (grants `publicFormGrant` for the matched form's object)", + "note": "Framework-side consumer: public (anonymous) form endpoints opt in via FormView.sharing — /forms/:slug resolves sharing.publicLink across form + formViews (rest-server.ts:1716, :4366-4405) and grants publicFormGrant. Renderer-side the config is not read (audit L20) — the public form is served, not re-parsed client-side. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:4384` had rotted onto a comment about `api`-only route enumeration, ~4,020 lines above the form endpoints; the note's own inline pointers (`:1716`, `:4366-4405`) had gone with it and are superseded by the anchors. Re-closed by hand against 8cb96ec41." }, "submitBehavior": { "status": "live", @@ -364,13 +366,15 @@ }, "listViews": { "status": "live", - "evidence": "packages/spec/src/ui/view.zod.ts:1432", - "note": "Named list views. Framework: expandViewContainer flattens container views into per-object view items (collision diagnostics via expandViewContainerWithDiagnostics). objectui: MetadataProvider.tsx:118-146 merges view metadata into objectDef.listViews — the saved-view switcher (ViewTabBar) reads only listViews (ADR-0047 'views' mode). Same inner shape as `list`; per-key classification lives under list.children." + "verifiedAt": "2026-08-28", + "evidence": "packages/spec/src/ui/view.zod.ts#expandViewContainerWithDiagnostics (`container.listViews` — each entry becomes an independent ViewItem named `.`, with collisions renamed and reported)", + "note": "Named list views. Framework: expandViewContainer flattens container views into per-object view items (collision diagnostics via expandViewContainerWithDiagnostics). objectui: MetadataProvider.tsx:118-146 merges view metadata into objectDef.listViews — the saved-view switcher (ViewTabBar) reads only listViews (ADR-0047 'views' mode). Same inner shape as `list`; per-key classification lives under list.children. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:1432` had rotted onto the same schema-declaration line its sibling `list.name` carried; two keys shared one wrong pointer. Re-closed by hand against 8cb96ec41." }, "formViews": { "status": "live", - "evidence": "packages/rest/src/rest-server.ts:4384", - "note": "Named form views. Framework: public-form slug resolution scans form + every formViews entry (rest-server.ts:4384-4405); expandViewContainer flattens them. objectui: MetadataProvider routes viewKind:'form' items into objectDef.formViews (never into the list switcher). Same inner shape as `form`; per-key classification lives under form.children." + "verifiedAt": "2026-08-28", + "evidence": "packages/rest/src/rest-server.ts#findPublicFormView (`const formViews = view.formViews` — every named form view is a public-link candidate); packages/spec/src/ui/view.zod.ts#expandViewContainerWithDiagnostics (flattens them into independent ViewItems)", + "note": "Named form views. Framework: public-form slug resolution scans form + every formViews entry (rest-server.ts:4384-4405); expandViewContainer flattens them. objectui: MetadataProvider routes viewKind:'form' items into objectDef.formViews (never into the list switcher). Same inner shape as `form`; per-key classification lives under form.children. 2026-08-28: RE-ANCHORED (#13003) and REPOINTED — `:4384` had rotted onto the same `api`-only comment its sibling `form.sharing` cited; the note's `rest-server.ts:4384-4405` range went with it. Re-closed by hand against 8cb96ec41." } } }