Skip to content

Commit 3fe9d25

Browse files
fix(spec): the liveness ledger grades field.settledWhen live (#26918)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} --> _Requested by **Yibo Wang** · [project thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBGq53dJp4Qhk7xzCoABTvh2)_ Fixes #26727 ## What changes - `packages/spec/liveness/field.json` › `settledWhen`: `planned` → `live`, `verifiedAt` 2026-10-27, `evidenceScope` `cross-repo` → `in-repo` (the renderer is `ui/` since #25712). Evidence: `ui/packages/core/src/evaluator/deadlineSettled.ts#isDeadlineSettled` (the one host helper), `ui/packages/fields/src/index.tsx#resolveDueLike` (the date and datetime cells drop overdue wording and colour on a settled verdict), `ui/packages/plugin-grid/src/objectGridGenerateColumns.tsx#useObjectGridGenerateColumns`, `ui/packages/app-shell/src/views/approval-decision/RecordSnapshotSummary.tsx#FieldFace`. New `producer`: the grid's three column paths and `plugin-detail` `ENRICHED_FIELD_METADATA_KEYS` copy the key onto cell metadata; `objectGridRowLoad.ts#useObjectGridRowLoad` and `ListView.tsx#ListView` fetch the predicate's operands. The note names the other hosts that ask the same helper (DetailSection, HeaderHighlight, RelatedList, ObjectKanban, ObjectGallery, ListViewCompact, ObjectTree), each pinned by its own `deadlineSettled-11815` test. The row had no `authorWarn` / `authorHint`, so none is dropped. - Same file › `dueLike` note: its "PARTIAL until objectui#11815 lands" clause was false (the grid copies the key on all three column paths, and `DUE_LIKE_FIELD_NAME` is gone). Note only; the status was already `live`. - `packages/spec/liveness/state-counts/field.md`: regenerated by `gen:liveness-counts`. - `packages/spec/liveness/README.md`: one sentence on the `field` row recording the flip and its counts. - `packages/qa/dogfood/test/expression-conformance.ledger.ts` › `cel-field-deadline-settled`: `experimental` / `unevaluated` ("NO EVALUATOR FOUND … objectui @55e90fd has no `settledWhen` read") → `enforced` / `fail-soft-log`, enforcement naming `isDeadlineSettled → evalRowPredicate → evalFieldPredicate → celEngine`. A faulting predicate is warned once per predicate text (`warnPredicateFailure`, via `evalFieldPredicate`, since `isDeadlineSettled` passes no `warnOnError`) and answers not settled, which is the `cel-field-rule` tier. - `.changeset/26727-settled-when-ledger-live.md`: `@objectstack/spec` patch (the `liveness/` folder ships in the spec tarball). **`object.tenancy` is already right on main, and this PR leaves it alone.** The QA's "no `tenancy` row" reading is not true at 2720874: `object.json` has a `tenancy` container with two children, both `live` and both checked at this head. `enabled`: `isTenancyDisabled` (`object.zod.ts`) is called by `injected-system-columns.ts#resolveInjectedSystemColumns` (withholds `organization_id`), `sql-driver.ts#computeTenantField` (no driver org scope) and `security-plugin.ts#getObjectSecurityMeta` (`tenancyDisabled` posture). `tenantField`: `computeTenantField` honours a declared column that exists on the object. Like the other container rows here (`enable`), the container itself carries no status. **Moved counts:** - `state-counts/field.md` `field` row: live 99 → 100, planned 6 → 5 (106 classified, unchanged). It counts the `field` ledger rows by verdict, and one row moved verdict. - `check:liveness` producer census: live rows citing a producer reads 152 at this head. The `settledWhen` row is the only row that gained a `producer`, so main reads 151; that is inferred from the diff, not run on main. This count is not pinned; the gate only reports it. - No other pin moved. The expression-conformance ledger has no per-state count pin. Its `unevaluated` pin judges each `unevaluated` row's cell, and this row has left that set. No `.describe()` change: the `settledWhen` / `dueLike` describe strings never said planned. The `content/docs/data-modeling/fields.mdx` deadline section already describes the live behaviour. ## Why The ledger tells authors and agents whether a key does anything. A `planned` grade on a key the console reads makes an agent treat it as inert. Re-measured on main 2720874, after the QA's 3fcafcd. ## Verification Clause-②: no Metadata-protocol: none _Keep one value on each of the two lines above, each line starting with its key. [Human Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum) item 5 says what they mean._ Tests and gates, run on head 1e5831a: - `pnpm --filter @objectstack/spec check:liveness`: exit 0 (after `gen:liveness-counts`; before it, the gate reported `field.md` stale with exactly the 99/6 → 100/5 move above). `check:empty-state`: exit 0. - spec `vitest run scripts/liveness`: 12 files, 358 tests passed. lint `lint-liveness-properties.test.ts`: 110 passed. - dogfood `expression-conformance.test.ts`: 8 passed (with `@objectstack/verify` built). - `dispatch-gates --commands`: 59 commands. 58 exit 0, including `check:generated`, `check:doc-authoring`, `check-adr-0087-registration --base origin/main` and `check:published-files`. `check:dual-build-cjs-loads` exits 3: it needs a full repo build, so nothing was measured. That is not a finding. ## Human Minimum Each item is one rule from [CONTRIBUTING.md › Human Minimum](https://github.com/objectstack-ai/objectstack/blob/main/CONTRIBUTING.md#human-minimum), which links the AGENTS.md text that states it. - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [ ] 3. No formatter ran; `pnpm lint` passes. (No formatter ran. `pnpm lint` was not run locally: the diff is JSON, Markdown and one string-literal row, so CI's lint decides.) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01WGufzgGEj5Qdn1KEGPUrU8)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7a2818c commit 3fe9d25

5 files changed

Lines changed: 30 additions & 17 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the liveness ledger grades `field.settledWhen` live
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The liveness ledger shipped in `@objectstack/spec` (`liveness/field.json`) graded a deadline field's `settledWhen` as `planned`, with a note saying no reader existed. The console has read it for some time: while the predicate holds for a record, that record's deadline shows no overdue wording and no overdue colour, in the grid, list, gallery, kanban, tree, record detail and approval snapshot. The row is now `live` and names that reader. Nothing about what you can write, or what it does, has changed.
11+
12+
The `dueLike` row's note is corrected the same way: it said the grid did not carry the key yet, and it does.
13+
14+
`object.tenancy` already had correct rows (`tenancy.enabled` and `tenancy.tenantField`, both live), so it is unchanged.

‎packages/qa/dogfood/test/expression-conformance.ledger.ts‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,16 @@ export const EXPRESSION_SURFACE: ExprSurface[] = [
219219
},
220220
{
221221
// #22227 — the deadline's settle predicate (objectui#11815 ruling D). A row
222-
// of its own rather than a fourth `cel-field-rule` cover: that row is
223-
// `enforced` and names the evaluators of its three slots, and neither reads
224-
// this one. The CEL is judged at authoring as the family's is; what is
225-
// missing is the reader, so the honest tier is `unevaluated`.
222+
// of its own rather than a fourth `cel-field-rule` cover: that row names
223+
// the evaluators of its three slots, and this one has its own, the
224+
// console's `isDeadlineSettled`. It was `experimental` / `unevaluated`
225+
// until objectui#11815 landed that reader; re-stated in #26727.
226226
id: 'cel-field-deadline-settled',
227-
summary: "a date / datetime deadline's settle predicate (Field.settledWhen) — display only, declared ahead of its renderer",
228-
dialect: 'cel', mode: 'interpret', state: 'experimental', failPolicy: 'unevaluated',
227+
summary: "a date / datetime deadline's settle predicate (Field.settledWhen) — display only",
228+
dialect: 'cel', mode: 'interpret', state: 'enforced', failPolicy: 'fail-soft-log',
229229
enforcement:
230-
'NO EVALUATOR FOUND — no runtime consumer yet, in either repo: nothing in this checkout reads the slot, and objectui @55e90fd has no `settledWhen` read at all (measured by grep). PARSE ONLY at the spec seam: `FieldSchema` refuses it off `date` / `datetime` and without `dueLike: true`. Its build-time reader is lint\'s field-rule pass (`validate-expressions.ts`), which judges the CEL as it judges `visibleWhen` (it parses, `record.<field>` only, the family\'s bound roots) at `os build`, `os validate` and the object save door — a reader of the source, not an evaluator of it',
230+
'console (ui/) isDeadlineSettled (core/evaluator/deadlineSettled.ts) → evalRowPredicate → evalFieldPredicate (core/evaluator/fieldRules.ts) → @objectstack/formula celEngine (interpret), evaluated per record with the row bound as `record`, by every host that draws a date cell beside a row (grid, list, gallery, kanban, tree, the record detail sections and the approval snapshot); the verdict reaches the date / datetime cell as `deadlineSettled`, and a settled deadline paints no overdue wording and no overdue colour. A predicate that faults is logged once per predicate text (warnPredicateFailure, fieldRules.ts: no `warnOnError` is passed, so evalRowPredicate takes the evalFieldPredicate route) and answers NOT settled, so the deadline shows as it does without the key. Display only: nothing on the write path reads it. Judged at authoring by lint\'s field-rule pass (`validate-expressions.ts`) as `visibleWhen` is',
231231
covers: ['data/field.zod.ts:FieldSchema.settledWhen'],
232-
// The reader is objectui#11815, Blocked-by #22227.
233-
note: 'EXPERIMENTAL until its one reader lands: objectui\'s date cell, whose host evaluates the predicate per row and hands the cell one boolean. Re-state as `enforced`, naming that evaluator and its fail policy, when it lands. Display only by declaration: nothing on the write path will read it, so this row never carries a write-path refusal.',
234232
},
235233
{
236234
// Split out of `cel-field-rule` by #15500. Both keys used to be spelled

‎packages/spec/liveness/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ marker where the Notes cell goes, never a guess at what belongs there.
976976
| Type | Notes |
977977
|---|---|
978978
| 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. **#23268** REMOVES `externalSharingModel` at protocol 18 (ADR-0049 enforce-or-remove, ruling of record 6159300530) — the TOMBSTONE route, so its row stays as `dead` (retiredKey keeps the key in the walked shape); it was `planned` against a consumer (#2696) that closed unbuilt, and the authorization kernel's `EXTERNAL` rung is explicitly shared rows only |
979-
| field | full dead set (vectorConfig/fileAttachmentConfig/dependencies, then referenceFilters/columnName/index) REMOVED (#2377); columnName also dropped the ADR-0062 D7 lint + StorageNameMapping column helpers. **Commit e25e8397a** 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 the gate'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 |
979+
| field | full dead set (vectorConfig/fileAttachmentConfig/dependencies, then referenceFilters/columnName/index) REMOVED (#2377); columnName also dropped the ADR-0062 D7 lint + StorageNameMapping column helpers. **Commit e25e8397a** 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 the gate'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. **#26727** flips `settledWhen` `planned` → `live` 2026-10-27 (planned 6 → 5, live 99 → 100): the same stale-`planned` shape, found by a Studio QA run rather than by the gate. Its consumer, objectui#11815's `isDeadlineSettled`, had landed under `ui/` and is asked by every surface that draws a date cell, while the row still said no reader existed in either repo. It carried no `authorWarn`, so nothing had been steering authors off it; the cost was agents reading the ledger and treating a working key as inert |
980980
| 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 |
981981
| 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. **Commit cae2169cf** 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` |
982982
| 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 |

0 commit comments

Comments
 (0)