Skip to content

Commit cb02062

Browse files
committed
fix(spec): grade flow.description and hook.label/description live — the Studio metadata list and quick-find show them
Three docs-shaped liveness rows move dead -> live. Neither `flow` nor `hook` registers list columns in the Studio metadata admin, so the list page's default columns draw `label` and `description`, and the metadata quick-find draws both as well. Each row cites that reader at the `.objectui-sha` pin, names the producer (route, metadata client read and the shared `createMetaListAnswer` list answer), and keeps its old note as history. A booted showcase read of GET /api/v1/meta/flow and /meta/hook served every authored label and description. state-counts.md regenerated; the README flow/hook Notes cells and the liveness gate test that borrowed flow.description as its sample dead row move with the flip. No schema, parse or describe change. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
1 parent fb38607 commit cb02062

6 files changed

Lines changed: 47 additions & 17 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
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.
6+
7+
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.
8+
9+
- **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`.
10+
- **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.
11+
- **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.
12+
- `state-counts.md` is regenerated. `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`.

‎packages/spec/liveness/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -903,9 +903,9 @@ marker where the Notes cell goes, never a guess at what belongs there.
903903
|---|---|
904904
| 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 |
905905
| 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 |
906-
| 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 |
906+
| 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 |
907907
| 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` |
908-
| hook | model-healthy; label/description dead but KEPT deliberately (2026-07-30 sweep) — docs-shaped annotation fields, exempt from enforce-or-remove |
908+
| 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 |
909909
| 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) |
910910
| position | (role's ADR-0090 successor) fully live; all 4 `verifiedAt`-stamped 2026-07-30 |
911911
| 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 |

‎packages/spec/liveness/flow.json‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
"note": "display."
2424
},
2525
"description": {
26-
"status": "dead",
27-
"evidence": "no reader either layer",
28-
"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."
26+
"status": "live",
27+
"verifiedAt": "2026-09-29",
28+
"evidenceScope": "cross-repo",
29+
"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)",
30+
"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)",
31+
"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."
2932
},
3033
"version": {
3134
"status": "live",

0 commit comments

Comments
 (0)