diff --git a/.changeset/20299-display-annotations-ledger.md b/.changeset/20299-display-annotations-ledger.md new file mode 100644 index 00000000000..f2147ad88b3 --- /dev/null +++ b/.changeset/20299-display-annotations-ledger.md @@ -0,0 +1,12 @@ +--- +"@objectstack/spec": patch +--- + +Liveness ledger: `flow.description`, `hook.label` and `hook.description` are now `live`, not `dead`. Studio already shows all three to a human. Ledger data, one README cell per type and one gate test fixture only. ⛔ No schema, parse, `.describe()` or accept-set change. + +The ledgers ship inside this package (`files[]` includes `liveness`), and `@objectstack/lint` reads them to decide which authored keys draw an advisory warning. None of the three rows sets `authorWarn`, so the set of warnings does not change. + +- **What shows them.** These are display keys, so under the ledger's "Designer previews count as consumers" ruling, being shown to a human is the whole of their claimed effect. Neither `flow` nor `hook` registers its own list columns in the Studio metadata admin, so the Studio metadata list page falls back to its default columns: name, `label` and `description`. The Studio metadata quick-find indexes and shows every item's `label` and `description` too. Each row cites that reader at the `.objectui-sha` pin `dd3f7e1be`. +- **Where the values come from.** Each row names its producer: the Studio route that mounts the list page, the metadata client's `GET /api/v1/meta/:type` read, and this repo's shared list answer (`createMetaListAnswer`), which adds no projection for either type that would drop the keys. A booted read of the showcase app confirms it: `GET /api/v1/meta/flow` served 30 flows and `GET /api/v1/meta/hook` served 4 hooks, each with its authored `label` and `description` and the showcase's project-scoped package id. +- **Still kept, still not warned.** The re-grade reverses no ADR-0033 decision. All three rows stay docs-shaped annotation, deliberately kept and exempt from enforce-or-remove. Each row keeps the note it carried while `dead`, as history. +- The regenerated liveness counts are the `liveness/state-counts/flow.md` and `liveness/state-counts/hook.md` shards. `flow` has 35 live and 5 dead (was 34 and 6). `hook` has 21 live and 1 dead (was 19 and 3). The README's `flow` and `hook` Notes cells no longer list these keys as dead. The liveness gate test that borrowed `flow.description` as its sample `dead` row now uses the `flow.active` tombstone, which the gate holds at `dead`. diff --git a/packages/spec/liveness/README.md b/packages/spec/liveness/README.md index 8482295f2c9..34161962988 100644 --- a/packages/spec/liveness/README.md +++ b/packages/spec/liveness/README.md @@ -908,9 +908,9 @@ marker where the Notes cell goes, never a guess at what belongs there. |---|---| | object | aspirational tier (versioning/softDelete/search/recordName/keyPrefix) + tags/active/abstract REMOVED (#2377) — tombstoned in UNKNOWN_KEY_GUIDANCE; `enable.trash`/`mru` REMOVED (#2377 close-out) — tombstoned in the now-`.strict()` ObjectCapabilities; `isSystem` + `enable.searchable` CORRECTED to live (#2377 — sharing default-model + global-search opt-out; 2026-06 audit missed both readers); `tenancy.strategy`/`crossTenantAccess` REMOVED post-15.0 (#2763). **#19054** REMOVES `tenancy.organizationField` at protocol 18 (ADR-0049 enforce-or-remove) — the STRICT-deletion route, so the row leaves this ledger with the key rather than staying as a tombstone: the `tenancy` block is a `strictObject`, the key is gone from the walked shape, and a surviving row would read as an ORPHAN. It was classified `live` on one real consumer (`resolveRecordOrganizationField`'s limb 0) and one real declaration, both of them ours — the key was authorable by every application and declared, repo-wide, only on `sys_api_key`. ⛔ Not a correction of that `live` verdict: the consumer still reads the same column for the same table, now from `PLATFORM_STAMP_ORGANIZATION_COLUMNS` in `@objectstack/metadata-core`, which is not an authorable surface and therefore has no row here | | field | full dead set (vectorConfig/fileAttachmentConfig/dependencies, then referenceFilters/columnName/index) REMOVED (#2377); columnName also dropped the ADR-0062 D7 lint + StorageNameMapping column helpers. **#13043** ends the empty dead column this type had carried since that sweep — the reason the cell said "healthy" until 2026-08-29: `conditionalRequired` is re-classified `live` → `dead` with no key added or removed. It has been a `retiredKey` tombstone since 2026-07-28 (protocol 17, #3855), so the row stays (the `rls.priority` precedent) while the verdict does not. BOTH halves of its evidence were falsified, not just the citation: the `.transform` lowering `conditionalRequired` → `requiredWhen` that the row credited does not exist (field.zod.ts has zero `.transform` calls), and the objectql rule-validator `requiredWhen ?? conditionalRequired` fallback its note leaned on was retired by #3903, which replays the ADR-0087 conversion chain at rehydration instead — so a stored pre-17 row reaches the validator already lowered. The rot was invisible to every citation check (pointer in range, right file, file names the key) and the entry carried no `verifiedAt`, so nothing ever re-asked — the #12516 class, the same shape `action.execute` turned out to have. It was also the ledger's LAST `path:NNN` citation, so retiring it took #13003's line-citation counter to zero **#19187** flips `relatedListFilter` `planned` → `live` 2026-09-20, the fourth member of the related-list family joining its three siblings. ⛔ NOT this type's first flip of that direction, which is what an earlier draft of this cell claimed: `valueDomain` went `planned` → `live` in `fa125f3bfe` (#15316) once the record validator's call into `isValueDomainMember` landed, and it stands `live` with `verifiedAt` 2026-09-04. The correction is kept rather than quietly deleted because the false clause was the same species as the row it was describing — a confident sentence in the file whose job is to say true things about the ledger, falsified by one `git log -p` over this file. The row is the clean case the `app.navigation.runAction` (#10068) and `list.map` (#11442) flips established: #8704 seeded it contract-first with `authorWarn` and wrote the flip condition into its own note, objectui#4664 satisfied that condition, and the flip was taken by re-measuring at the `.objectui-sha` pin rather than on objectui main — `deriveRelatedLists` puts the authored value on the derived descriptor and `RecordDetailView` writes it onto the synthesized `record:related_list` node, so the rows and the tab badge answer one composed question. What makes it a DEFECT rather than bookkeeping is the direction a stale `planned` row fails in: its `authorHint` was a sentence `packages/lint` repeated at every compile — 「the auto-derived related list does not apply this filter yet」 — about a key the pinned console applies, so the ledger was steering authors off a working key rather than merely lagging it. It is also the direction no citation check can see: a `planned` row cites nothing, so nothing rots, and only the consumer landing falsifies it. With it, `field` carries NO `authorWarn` row at any depth, which gates the lint's field walk off entirely (`if (fieldWarn.size > 0)`) — recorded because the next warned field row re-opens that walk, and the #11385 field-walk pin in `packages/lint` is narrowed until one does | -| flow | dead count = **5 tombstone entries** + the kept docs field: `active`/`template`/nodes.`outputSchema`/errorHandling.`fallbackNodeId` REMOVED 2026-07-30 (#3896 close-out sweep — `active: false` never stopped a flow, `status` is the enforced lifecycle; faults route via per-node fault edges), plus errorHandling.`retryDelayMs` RENAMED to `backoffMs` 2026-08-04 (#4964). The rename is why the dead column moved while live did not: a rename is a removal on this ledger, so the old spelling is tombstoned (`retiredKey` keeps it in the walked shape) and the new spelling enters as its own `live` row. Read it beside the four above as the one entry here that cost an author nothing — the block was a THIRD encoding of the retry policy #4661 converged, invisible to that pass because it is an anonymous inline block with no exported name, and #4964 spelled its base delay `backoffMs` to match `job.retryPolicy` and a `try_catch` node's `retry`. Remaining dead = `description`, KEPT deliberately: docs-shaped, exempt from enforce-or-remove | +| flow | dead count = **5 tombstone entries**: `active`/`template`/nodes.`outputSchema`/errorHandling.`fallbackNodeId` REMOVED 2026-07-30 (#3896 close-out sweep — `active: false` never stopped a flow, `status` is the enforced lifecycle; faults route via per-node fault edges), plus errorHandling.`retryDelayMs` RENAMED to `backoffMs` 2026-08-04 (#4964). The rename is why the dead column moved while live did not: a rename is a removal on this ledger, so the old spelling is tombstoned (`retiredKey` keeps it in the walked shape) and the new spelling enters as its own `live` row. Read it beside the four above as the one entry here that cost an author nothing — the block was a THIRD encoding of the retry policy #4661 converged, invisible to that pass because it is an anonymous inline block with no exported name, and #4964 spelled its base delay `backoffMs` to match `job.retryPolicy` and a `try_catch` node's `retry`. `description` is the kept docs field — KEPT deliberately, docs-shaped, exempt from enforce-or-remove — and it left the dead set in #20299: the Studio metadata list's default columns and the metadata quick-find draw it for every flow, which for a display key is the whole of the claimed effect (the #7131 ruling above). Still not authorWarn'd | | action | `type:'form'` CORRECTED to live (objectui ActionRunner.executeForm, #2377); dead `timeout` REMOVED (#2377); `disabled` live since objectui#2863; `undoable` CORRECTED to live (#3714); `shortcut` + `bulkEnabled` REMOVED 2026-07-30 (#3896 close-out sweep — no keydown path dispatches shortcuts; the multi-select toolbar reads the view's bulkActions). **#7367** (PR #7430) adds `description` as an authorable key, `live` on arrival — the only row this type has gained since that sweep. **#13036** makes the dead set three: `execute` joins it, re-classified `live` → `dead` 2026-08-29 with no key added or removed. Its `live` verdict rested on a `.transform` lowering `execute` → `target` that protocol 17 (#3855) removed along with the alias; the key has been a `retiredKey` tombstone since 2026-07-28, so the row stays (the `rls.priority` precedent) while the verdict does not. The rot was invisible to every citation check — the pointer was in range, in the right file, and the file names the key — and the entry carried no `verifiedAt`, so nothing ever re-asked. **#20323** makes the dead set four: `aria` re-classified `live` → `dead` 2026-09-28 and tombstoned (the `rls.priority` precedent again). Its `live` verdict rested on an uncited 「PARTIAL — honored by a few objectui renderers」 note; re-measured at the `.objectui-sha` pin, no surface that renders an action reads an action's `aria`, and each takes the accessible name from the required `label` | -| hook | model-healthy; label/description dead but KEPT deliberately (2026-07-30 sweep) — docs-shaped annotation fields, exempt from enforce-or-remove | +| hook | model-healthy; label/description KEPT deliberately (2026-07-30 sweep) — docs-shaped annotation fields, exempt from enforce-or-remove — and **`live` since #20299**: `hook` has no registered preview, but the Studio metadata list's default columns and the metadata quick-find draw both keys for every hook, which for a display key is the whole of the claimed effect (the #7131 ruling above). Still not authorWarn'd | | permission | CRUD/FLS/RLS live; dead `contextVariables` REMOVED (ADR-0105 D11 — RLS resolves only the `current_user.*` built-ins plus runtime-staged `rlsMembership` sets). 2026-07-30 security-subset re-verification (all 33 entries `verifiedAt`-stamped): `rowLevelSecurity.enabled` was live-with-wrong-evidence and UNREAD — a disabled policy kept contributing its OR-branch grant; ENFORCED same day in rls-compiler (`getApplicablePolicies`), the `positions` ADR-0049 resolution repeated. `rowLevelSecurity.priority` CORRECTED to dead+authorWarn — semantically void under OR-combination (no conflict exists to order), a REMOVE candidate. `rls.label`/`description`/`tags` CORRECTED to dead (benign display, no consumer in either repo). `tabPermissions` was UNDERSTATED ("only hidden read" → the rank merge reads all four values; me-apps dogfood test exercises it). `allowExport` re-verified TRUE end-to-end (server-side 403 gate, not just the /me projection). `objects.allowRestore`/`allowPurge` REMOVED 2026-08-26 (#12497, ADR-0049 — the `restore`/`purge` ops never existed; the 2026-07-30 'live' verdict cited only the evaluator pre-mapping, retired in the same batch; `retiredKey` tombstones, keys return with M2 per the #1883 ruling). `rowLevelSecurity.tags` REMOVED 2026-09-27 (#20321, ADR-0049 — graded RETIRE by the maintainer's criterion: no mainstream platform tags a row-level policy; a `retiredKey` tombstone, so the row stays `dead` beside `priority`'s) | | position | (role's ADR-0090 successor) fully live; all 4 `verifiedAt`-stamped 2026-07-30 | | agent | dead `tenantId` + `planning.strategy`/`allowReplan` REMOVED (#2377); autonomy tier experimental; `knowledge` REMOVED 2026-07-30 (#3896 close-out sweep — declaring sources never scoped retrieval; AIKnowledgeSchema removed with it, the topics→sources rename absorbed pre-release); **#18304** re-classifies `tools` `live` -> `dead` with no key added or removed — the row asserted `live` on a key `agent.zod.ts` had tombstoned in protocol 17 (#3894), and it sat that way from the 2026-06 audit because its citation was EXEMPT from resolution rather than resolved (`packages/services/service-ai/...` matched `FOREIGN_PATH_PREFIXES`; the `cloud` realm marker that replaced it in #13309 is equally unresolvable, so no gate could ever fail on it). The load-bearing evidence is local and re-measurable — the `retiredKey` tombstone plus the `agent-tools-to-skills` strip cover authored and stored input respectively, so nothing can carry a value for any consumer to read; the cloud zero-consumer census (cloud @cb8ee7ff, #13272, 2026-09-15) is attributed, not re-taken. `live-elsewhere` is refused for want of a foreign enforcer, not left undeclared | diff --git a/packages/spec/liveness/flow.json b/packages/spec/liveness/flow.json index 1e785938200..5d9d77ae13c 100644 --- a/packages/spec/liveness/flow.json +++ b/packages/spec/liveness/flow.json @@ -23,9 +23,12 @@ "note": "display." }, "description": { - "status": "dead", - "evidence": "no reader either layer", - "note": "KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader (per ADR-0033, often a model) even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): 'no reader either layer' is a cross-repo absence claim, so the previews ruling (#7131; README, 'Designer previews count as consumers') was applied to it mechanically. At objectui @e9ab52f9 FlowPreview IS registered (previews/index.ts:58) and reachable (ResourceEditPage.tsx:949), and it does NOT read the flow description — the only `description` token in the file is a TypeScript interface member at FlowPreview.tsx:64, not a draft read; the preview keys off `d.name`, the node graph and the edges. The claim survives the look; the verdict is unchanged and this note re-litigates nothing." + "status": "live", + "verifiedAt": "2026-09-29", + "evidenceScope": "cross-repo", + "evidence": "objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `description` column: `flow` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `description` through `defaultCell`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `description` off the same list read and draws it under the item's name)", + "producer": "objectui @dd3f7e1be: packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`, which the metadata directory tile and the quick-find both navigate to); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `flow` registration, which sets no `ListPage` and no `listColumns`); objectui @dd3f7e1be: packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('flow')` reads `GET /api/v1/meta/flow` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve: after the per-caller gate it runs no projection for `flow` except the translation step, so the authored `description` reaches the body)", + "note": "RE-GRADED dead → live 2026-09-29 (#20299) under the #7131 previews ruling (README, 'Designer previews count as consumers'): for a display key, being shown to a human is the whole of the claimed effect. The lane's call on #20299 (claim 5880838693) counts a Studio list column and the metadata quick-find as that showing. The superseded evidence, 'no reader either layer', was answered by the preview lookup below, which looked at previews only. READER, read at the `.objectui-sha` pin dd3f7e1be: `MetadataResourceListPage` falls through to `DefaultMetadataList` because no registration gives `flow` a `ListPage`, and that page takes `config.listColumns ?? defaultColumns(...)`, whose columns are the primary key, `label` and `description`. Each cited string counts the same at objectui main 5d689c3. PRODUCER, measured booted rather than read: a throwaway `@objectstack/verify` boot of the real examples/app-showcase composition, signed in as the dev admin, answered `GET /api/v1/meta/flow` 200 with a top-level `items` array of 30 flows, and all 30 carried their authored `label` and `description` with `_packageId: com.example.showcase`. `GET /api/v1/meta/package` listed that package with `scope: project`, which is what the list page's package scope admits. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT (2026-07-30 sweep, ADR-0033) and not authorWarn'd. `live` here does not mean the automation engine acquired a use for it. HISTORY, kept as history — the note this row carried while `dead`: KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader (per ADR-0033, often a model) even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): 'no reader either layer' is a cross-repo absence claim, so the previews ruling (#7131; README, 'Designer previews count as consumers') was applied to it mechanically. At objectui @e9ab52f9 FlowPreview IS registered (previews/index.ts:58) and reachable (ResourceEditPage.tsx:949), and it does NOT read the flow description — the only `description` token in the file is a TypeScript interface member at FlowPreview.tsx:64, not a draft read; the preview keys off `d.name`, the node graph and the edges. The claim survives the look; the verdict is unchanged and this note re-litigates nothing." }, "version": { "status": "live", diff --git a/packages/spec/liveness/hook.json b/packages/spec/liveness/hook.json index 0b4c62022a7..767b5128801 100644 --- a/packages/spec/liveness/hook.json +++ b/packages/spec/liveness/hook.json @@ -1,6 +1,6 @@ { "type": "hook", - "_note": "HookSchema. Seeded from docs/audits/2026-06-hookschema-property-liveness.md — a model-healthy schema (near-total liveness). Consumers: objectql hook-binder/engine + runtime sandbox. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): the two `dead` rows here (`label`, `description`) are the docs-shaped precedent the rest of this ledger cites, so the maintainer's 2026-08-10 previews ruling (#7131 — see README, 'Designer previews count as consumers') was applied to them mechanically, and the answer is an ABSENCE. There is NO registered metadata-admin preview for `hook` at objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts registers 22 types and `hook` is not among them. Recorded rather than skipped, because 'the type has no registered preview' is the sentence the README asks for and the one `translation.label`'s superseded note was missing. Both verdicts stand unchanged; the ruling gives them nothing to re-litigate. 2026-08-28 (#13003): all eight `path:NNN` citations in this file were re-anchored. The five `evidence` pointers into `hook-binder.ts` were all wrong and all IN RANGE — two keys even shared one wrong line — while the three `producer` pointers at `:221` were accurate and are a straight grammar migration. The declarative trio's `evidence` was a bare path with no line, which no line bound can falsify; those are anchors now.", + "_note": "HookSchema. Seeded from docs/audits/2026-06-hookschema-property-liveness.md — a model-healthy schema (near-total liveness). Consumers: objectql hook-binder/engine + runtime sandbox. PREVIEW LOOKUP RECORDED 2026-08-10 (#7427): the two `dead` rows here (`label`, `description`) are the docs-shaped precedent the rest of this ledger cites, so the maintainer's 2026-08-10 previews ruling (#7131 — see README, 'Designer previews count as consumers') was applied to them mechanically, and the answer is an ABSENCE. There is NO registered metadata-admin preview for `hook` at objectui @e9ab52f9: packages/app-shell/src/views/metadata-admin/previews/index.ts registers 22 types and `hook` is not among them. Recorded rather than skipped, because 'the type has no registered preview' is the sentence the README asks for and the one `translation.label`'s superseded note was missing. Both verdicts stand unchanged; the ruling gives them nothing to re-litigate. 2026-08-28 (#13003): all eight `path:NNN` citations in this file were re-anchored. The five `evidence` pointers into `hook-binder.ts` were all wrong and all IN RANGE — two keys even shared one wrong line — while the three `producer` pointers at `:221` were accurate and are a straight grammar migration. The declarative trio's `evidence` was a bare path with no line, which no line bound can falsify; those are anchors now. 2026-09-29 (#20299): `label` and `description` are RE-GRADED dead → live. The 2026-08-10 lookup above still reads true, since `hook` still has no registered preview at the `.objectui-sha` pin dd3f7e1be, but it looked at previews only. The Studio metadata list and the metadata quick-find draw both keys for every hook, and a booted read of `GET /api/v1/meta/hook` serves them; the rows carry the reader, the producer and the measurement. They are still KEPT, docs-shaped and not authorWarn'd.", "props": { "name": { "status": "live", @@ -85,14 +85,20 @@ "note": "#14010 (ruling 2026-09-01): 'system' | 'user' | 'inherit', default 'inherit' = the pre-runAs behaviour. Scope is ctx.api data operations only (condition, readonly strip, session, async untouched). Honoured on both the in-process handler and the sandboxed body because the sandbox reads ctx.api from the engine context at call time (runtime/src/sandbox/body-runner.ts buildSandboxApi). Pinned in packages/objectql/src/hook-run-as.test.ts and packages/runtime/src/sandbox/hook-run-as.integration.test.ts." }, "label": { - "status": "dead", - "evidence": "pure docs, zero runtime readers (runtime uses only name)", - "note": "KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate." + "status": "live", + "verifiedAt": "2026-09-29", + "evidenceScope": "cross-repo", + "evidence": "objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `label` column: `hook` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `label` through `defaultCell`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `label` off the same list read and draws it beside the item's name)", + "producer": "objectui @dd3f7e1be: packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`, which the metadata directory tile and the quick-find both navigate to); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `hook` registration, which sets no `ListPage` and no `listColumns`); objectui @dd3f7e1be: packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('hook')` reads `GET /api/v1/meta/hook` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the one list answer both transports serve: after the per-caller gate it runs no projection for `hook` except the translation step, so the authored `label` reaches the body)", + "note": "RE-GRADED dead → live 2026-09-29 (#20299) under the #7131 previews ruling (README, 'Designer previews count as consumers'): for a display key, being shown to a human is the whole of the claimed effect. The lane's call on #20299 (claim 5880838693) counts a Studio list column and the metadata quick-find as that showing. The superseded evidence, 'pure docs, zero runtime readers (runtime uses only name)', was true of the hook RUNTIME and false as a whole-system claim, and the 2026-08-10 preview lookup (file `_note`) looked at previews only: `hook` still has no registered preview. READER, read at the `.objectui-sha` pin dd3f7e1be: `MetadataResourceListPage` falls through to `DefaultMetadataList` because no registration gives `hook` a `ListPage`, and that page takes `config.listColumns ?? defaultColumns(...)`, whose columns are the primary key, `label` and `description`. Each cited string counts the same at objectui main 5d689c3. PRODUCER, measured booted rather than read: a throwaway `@objectstack/verify` boot of the real examples/app-showcase composition, signed in as the dev admin, answered `GET /api/v1/meta/hook` 200 with a top-level `items` array of 4 hooks, and all 4 carried their authored `label` and `description` with `_packageId: com.example.showcase`. `GET /api/v1/meta/package` listed that package with `scope: project`, which is what the list page's package scope admits. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT (2026-07-30 sweep, ADR-0033) and not authorWarn'd. `live` here does not mean the hook runtime acquired a use for it. HISTORY, kept as history — the note this row carried while `dead`: KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate." }, "description": { - "status": "dead", - "evidence": "pure docs, zero runtime readers; redundant with label", - "note": "KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate." + "status": "live", + "verifiedAt": "2026-09-29", + "evidenceScope": "cross-repo", + "evidence": "objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#defaultColumns (the Studio metadata list's default `description` column: `hook` registers no `listColumns`, so `DefaultMetadataList` takes these defaults and draws each row's `description` through `defaultCell`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/QuickFind.tsx#MetadataQuickFind (the Studio metadata quick-find indexes every item's `description` off the same list read and draws it under the item's name)", + "producer": "objectui @dd3f7e1be: packages/app-shell/src/console/AppContent.tsx#AppContent (mounts `MetadataResourceListPage` on the Studio route `metadata/:type`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/ResourceListPage.tsx#MetadataResourceListPage (renders a registered custom `ListPage` and otherwise `DefaultMetadataList`); objectui @dd3f7e1be: packages/app-shell/src/views/metadata-admin/anchors.ts#registerBuiltinAnchors (the only `hook` registration, which sets no `ListPage` and no `listColumns`); objectui @dd3f7e1be: packages/data-objectstack/src/metadata-client.ts#MetadataClient (`list('hook')` reads `GET /api/v1/meta/hook` and takes its `items`); framework: packages/rest/src/meta-item-read-gate.ts#createMetaListAnswer (the same list answer as `label`, with no projection that drops `description`)", + "note": "RE-GRADED dead → live 2026-09-29 (#20299) with `label`, same ruling, same lane call, same pin and the same booted measurement: all 4 showcase hooks served by `GET /api/v1/meta/hook` carried an authored `description`, and the Studio list's default `description` column draws it. The superseded evidence, 'pure docs, zero runtime readers; redundant with label', was true of the hook runtime. The two keys are not redundant on screen: the list draws them as two columns and the quick-find draws `label` beside the name and `description` under it. UNCHANGED by the re-grade: still docs-shaped annotation, deliberately KEPT (2026-07-30 sweep, ADR-0033) and not authorWarn'd. HISTORY, kept as history — the note this row carried while `dead`: KEPT deliberately (2026-07-30 sweep): docs-shaped, not capability-shaped — an annotation field documents intent for the next reader even without a runtime consumer. Exempt from enforce-or-remove; do not re-litigate." } } } diff --git a/packages/spec/liveness/state-counts/flow.md b/packages/spec/liveness/state-counts/flow.md index 7f9a304cac9..08faba4d15d 100644 --- a/packages/spec/liveness/state-counts/flow.md +++ b/packages/spec/liveness/state-counts/flow.md @@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them. | Type | live | exp | elsewhere | dead | planned | classified | |---|---|---|---|---|---|---| -| `flow` | 34 | 0 | 0 | 6 | 0 | 40 | +| `flow` | 35 | 0 | 0 | 5 | 0 | 40 | diff --git a/packages/spec/liveness/state-counts/hook.md b/packages/spec/liveness/state-counts/hook.md index a5baed4958b..87162fb9537 100644 --- a/packages/spec/liveness/state-counts/hook.md +++ b/packages/spec/liveness/state-counts/hook.md @@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them. | Type | live | exp | elsewhere | dead | planned | classified | |---|---|---|---|---|---|---| -| `hook` | 19 | 0 | 0 | 3 | 0 | 22 | +| `hook` | 21 | 0 | 0 | 1 | 0 | 22 | diff --git a/packages/spec/scripts/liveness/check-liveness.test.ts b/packages/spec/scripts/liveness/check-liveness.test.ts index 696094c2eb6..949898cfbd8 100644 --- a/packages/spec/scripts/liveness/check-liveness.test.ts +++ b/packages/spec/scripts/liveness/check-liveness.test.ts @@ -428,14 +428,23 @@ describe('check:liveness — the evidence-scan population (#13041)', () => { // row's pointer is the retirement story in `note` — which no check scans — so // scanning `dead.evidence` would hold 6 rows to a standard, read nothing of // the other 74, and publish that as coverage of the class. + // + // The carrier is `flow.active`, a `retiredKey()` tombstone, because the gate + // itself holds a tombstoned row at `dead` (the #19062 join) — so it cannot be + // re-graded out from under this case. The carrier used to be `flow.description`, + // a docs-shaped row that WAS re-graded `live` (#20299): a borrowed `dead` row is + // a claim with a timestamp, and the precondition below says so if it moves. it('stays GREEN when a `dead` entry carries the SAME rotted pointer', () => { + const shipped = JSON.parse(readFileSync(path.join(LEDGERS, 'flow.json'), 'utf8')); + expect(shipped.props.active.status, 'the carrier row must be `dead` in the shipped ledger').toBe('dead'); + const root = path.join(tmp, 'dead-excluded'); cpSync(LEDGERS, root, { recursive: true }); - setEvidence(root, 'flow', 'description', `${ROTTED} (rotted by the self-test)`); + setEvidence(root, 'flow', 'active', `${ROTTED} (rotted by the self-test)`); const { status, output } = runGate(root); expect(status, output).toBe(0); - expect(output).not.toContain(`flow/description → ${ROTTED}`); + expect(output).not.toContain(`flow/active → ${ROTTED}`); }); // The partition itself, pinned at the source — the precedent is the