Skip to content

Commit e25e839

Browse files
os-trumpclaude
andauthored
chore(spec): re-classify the field.conditionalRequired liveness entry dead (#13043) (#13069)
The entry was ledgered `live` on a FieldSchema `.transform` lowering conditionalRequired -> requiredWhen and dropping the alias. No such transform exists: field.zod.ts has ZERO `.transform` calls, and its two occurrences of the token are comments recording that `.overwrite()` was chosen instead (#6926). The note's second leg - that objectql rule-validator.ts keeps a `requiredWhen ?? conditionalRequired` fallback on purpose - was retired by #3903, which replays the ADR-0087 conversion chain at rehydration so a stored pre-17 row arrives already lowered; its absence is pinned by that module's own test. Status flips to `dead` with the ROW STAYING - the rls.priority precedent, a retiredKey tombstone keeps the key in the walked shape. The falsified `evidence` goes, the #3754 / #3713 lowering history is preserved in the note. This was the LAST `path:NNN` citation in any ledger. The gate's line-citation counter goes 1 -> 0, which is the end of the #13003 anchor-adoption worklist and the trigger for the delete-at-zero instruction the two non-vacuity floors carried in their own guard comments: both floors, both guard comments and both named companions (`outOfRange`; the equality check) are deleted here. The two sibling floors on non-draining populations (`local` paths, `evidence paths`) stay at `> 100`, verified by grep. Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 5cf61be commit e25e839

6 files changed

Lines changed: 75 additions & 77 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
liveness ledger: re-classify `field.conditionalRequired` `live``dead` (#13043)
6+
7+
Both halves of the entry's evidence were falsified, not just its citation. It
8+
claimed a `FieldSchema .transform` lowering `conditionalRequired``requiredWhen`
9+
that drops the alias: `packages/spec/src/data/field.zod.ts` has **zero**
10+
`.transform` calls — its only two occurrences of that token are comments recording
11+
that `.overwrite()` was chosen instead (#6926). And its note leaned on
12+
`packages/objectql/src/validation/rule-validator.ts` keeping a
13+
`requiredWhen ?? conditionalRequired` fallback "on purpose"; #3903 retired that
14+
fallback, because every path that hands the validator a stored field definition now
15+
replays the ADR-0087 conversion chain at rehydration, so a pre-17 row arrives with
16+
the alias already lowered. The absence is pinned by that module's own test ("does
17+
NOT read the retired conditionalRequired alias — PD #12, no dialect fallback").
18+
19+
The alias and its lowering were removed together in protocol 17 (#3855, PR #3883,
20+
landed 2026-07-28); the key has been a `retiredKey` tombstone ever since.
21+
22+
Data-only: no schema, no runtime, no authoring-surface change — authoring
23+
`conditionalRequired` already failed `tsc` and the parse before this, and still
24+
does. The row STAYS: per the `rls.priority` precedent a `retiredKey()` tombstone
25+
keeps the key in the walked shape, so deleting the row would report UNCLASSIFIED.
26+
`liveness/` is in this package's `files` array, so these ledgers ship in the npm
27+
tarball and this is published data.
28+
29+
This was the last `path:NNN` citation in any ledger. Retiring it takes the gate's
30+
line-citation counter to zero and closes #13003's symbol-anchor adoption worklist,
31+
which is why the same change deletes the two non-vacuity floors that guarded that
32+
population (and their guard comments) exactly as those comments instructed.

packages/spec/liveness/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ marker where the Notes cell goes, never a guess at what belongs there.
810810
| Type | Notes |
811811
|---|---|
812812
| 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) |
813-
| field | healthy — full dead set (vectorConfig/fileAttachmentConfig/dependencies, then referenceFilters/columnName/index) REMOVED (#2377); columnName also dropped the ADR-0062 D7 lint + StorageNameMapping column helpers |
813+
| 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 |
814814
| 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 |
815815
| 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 |
816816
| hook | model-healthy; label/description dead but KEPT deliberately (2026-07-30 sweep) — docs-shaped annotation fields, exempt from enforce-or-remove |

packages/spec/liveness/field.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@
9191
"note": "renderer CEL."
9292
},
9393
"conditionalRequired": {
94-
"status": "live",
95-
"evidence": "packages/spec/src/data/field.zod.ts:639 — the FieldSchema .transform lowers conditionalRequired -> requiredWhen and DROPS the alias, so authoring it changes what the validator enforces",
96-
"note": "#3754: folded at parse time and removed from the parsed output (same shape as action.execute -> target in #3742, agent.knowledge.topics -> sources in #1891). Previously BOTH keys stayed live in the output with no canonicalization, so each consumer re-derived the precedence — the condition that produced the #3713 divergence. objectql rule-validator.ts keeps its `requiredWhen ?? conditionalRequired` fallback on purpose: raw (unparsed) metadata still reaches evaluateValidationRules. Removal of the input key is still the endgame."
94+
"status": "dead",
95+
"verifiedAt": "2026-08-29",
96+
"note": "REMOVED 2026-07-28 in protocol 17 (#3855, PR #3883 — the commit that retired the last three deprecated authorable aliases) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error) and renamed out of sources by the protocol-17 conversion `field-conditionalRequired-to-requiredWhen`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); use `requiredWhen`, the only predicate slot — rename the key, the value (a CEL predicate) is unchanged, and `os migrate meta --from 16` lists the mechanical edits. The tombstone is packages/spec/src/data/field.zod.ts#conditionalRequired. The consumer-side refusal is packages/objectql/src/validation/rule-validator.ts#ConditionalFieldDef, which declares `requiredWhen` THE ONLY predicate slot and states PD #12 — no consumer-side dialect fallback returns here; that absence is PINNED by the case named `does NOT read the retired conditionalRequired alias (PD #12 — no dialect fallback)` in packages/objectql/src/validation/rule-validator.test.ts, which sits beside a positive case asserting the rule DOES fire once the stored-conversion chain has lowered the alias. LEDGER CORRECTED 2026-08-29 (#13043) — the VERDICT was falsified, not the citation, and in BOTH halves of its evidence. (a) This row read `live` on the claim that the FieldSchema .transform lowers conditionalRequired -> requiredWhen and DROPS the alias. No such transform exists: field.zod.ts contains ZERO .transform calls, its only two occurrences of that token are comments recording that `.overwrite()` was chosen over `.transform()` per the measured #6926 precedent, and the describe() on `requiredWhen` says out loud that the alias was removed in protocol 17. (b) The note's own stated reason for the verdict — that objectql rule-validator.ts keeps its `requiredWhen ?? conditionalRequired` fallback on purpose because raw (unparsed) metadata still reaches evaluateValidationRules — was retired by #3903: every path that hands the validator a STORED field definition now replays the ADR-0087 conversion chain at rehydration (applyConversionsToStoredItem), so a pre-17 row arrives with the alias already lowered, and a caller handing raw unconverted legacy input is off-contract. WHY NOTHING SAW THE ROT: the old pointer packages/spec/src/data/field.zod.ts:639 was IN RANGE in a 2,132-line file that names the key five times, so the existence check, the line bound and the key-mention check were all green on it — what :639 holds today is the tail of FieldMaskingRuleSchema, unrelated in every respect — and the entry carried no verifiedAt, so the re-verification clock never asked (the #12516 class). It was also the LAST `path:NNN` citation left in any ledger, which is why retiring it here closes #13003's anchor-adoption worklist. HISTORY PRESERVED, because it is the argument for `requiredWhen` being the single slot: #3754 folded the alias at parse time and removed it from the parsed output (same shape as action.execute -> target in #3742, agent.knowledge.topics -> sources in #1891); before that fix BOTH keys stayed live in the output with no canonicalization, so each consumer re-derived the precedence — the condition that produced the #3713 divergence. That note called removal of the input key the endgame, and protocol 17 is where the endgame landed. RETIREMENT CHAIN, COMPLETE — the close-out machinery is what remains, and it is not a consumer: the ADR-0087 conversion `field-conditionalRequired-to-requiredWhen` in packages/spec/src/conversions/registry.ts, its `RETIRED_KEYS_BY_MAJOR` entry and D3 chain step in packages/spec/src/migrations/registry.ts, and the explicit `NOTE: no entry for conditionalRequired` in packages/spec/src/data/authoring-key-lint.ts. Two INERT survivors are recorded so the next reader does not mistake either for a reader that gives the key meaning: packages/lint/src/validate-expressions.ts still walks the key, and its own docblock calls that branch inert on the parsed compile path because strict FieldSchema rejects the key by name (it gets a slot-agnostic consequence clause rather than a fabricated fourth measurement); packages/drivers/driver-sql/src/builtin-column-collision.ts lists it in a key-classification map as `presentation`, which classifies a name, not a value anyone reads. Re-closed by hand against cae2169cf."
9797
},
9898
"readonly": {
9999
"status": "live",

packages/spec/liveness/state-counts.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ for both corollaries.
2828
| Type | live | exp | dead | planned | classified |
2929
|---|---|---|---|---|---|
3030
| `object` | 51 | 0 | 0 | 1 | 52 |
31-
| `field` | 89 | 0 | 0 | 2 | 91 |
31+
| `field` | 88 | 0 | 1 | 2 | 91 |
3232
| `flow` | 34 | 0 | 6 | 0 | 40 |
3333
| `action` | 41 | 0 | 3 | 2 | 46 |
3434
| `hook` | 18 | 0 | 2 | 0 | 20 |
@@ -58,4 +58,4 @@ for both corollaries.
5858
| `capability` | 12 | 0 | 0 | 0 | 12 |
5959
| `qa` | 4 | 0 | 5 | 0 | 9 |
6060
| `manifest` | 22 | 0 | 21 | 0 | 43 |
61-
| **total** | **820** | **5** | **79** | **10** | **914** |
61+
| **total** | **819** | **5** | **80** | **10** | **914** |

packages/spec/scripts/liveness/check-liveness.test.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -183,33 +183,29 @@ describe('check:liveness — evidence pointers (#5623)', () => {
183183
expect(output).not.toContain('name a line the cited file does not have');
184184
});
185185

186-
it('prints the citation count and how many are in range, equal on a green run', () => {
187-
// The #5623 lesson applied to the new counter: printing only "in range"
188-
// would read as a pass on a run where the parser extracted no citations.
189-
// Hence two numbers, and a floor under the first — see the floor's own note
190-
// at the assertion below for why that floor is `> 0` and when to delete it.
186+
it('prints the citation count and how many are in range, in the documented two-number shape', () => {
187+
// The #5623 lesson applied to the counter: printing only "in range" would
188+
// read as a pass on a run where the parser extracted no citations. Hence two
189+
// numbers, and the gate prints them on every run — including this one, where
190+
// the population is zero.
191+
//
192+
// The non-vacuity FLOOR that stood here (`> 0`, ruled 2026-08-28 on #13003,
193+
// comment 5458356183) and the equality check beside it were DELETED
194+
// 2026-08-29 (#13043), by the standing instruction the floor's own guard
195+
// comment carried, at the moment that instruction names: #13003 retired the
196+
// last line citation and the population legitimately reached zero. The floor
197+
// reds at exactly that moment BY DESIGN, so that reaching zero is a conscious
198+
// decision rather than a silent pass; the equality check went with it because
199+
// it now compares two zeroes. What the case still pins is real and is what
200+
// the deletion could otherwise cost: the gate must keep EMITTING the line, in
201+
// the shape this regex documents — drop the line, rename it, or collapse it
202+
// to one number and this reds. A `path:NNN` citation written again gives this
203+
// case a population back; restore a floor with it.
191204
const { status, output } = runGate(path.join(tmp, 'liveness'));
192205
expect(status, output).toBe(0);
193206
const line = output.split('\n').find((l) => l.startsWith('line citations:')) ?? '';
194207
const m = /line citations: (\d+) pointer\(s\) written .*?, (\d+) inside the cited file/.exec(line);
195208
expect(m, line).not.toBeNull();
196-
// Non-vacuity. The failure mode this guards is the parser silently
197-
// extracting NOTHING (#5623), and that failure mode is exactly zero — so
198-
// zero is exactly what the floor tests. It was 100 until #13003, which is
199-
// the migration adopting #12516's `path#symbol` grammar and therefore
200-
// RETIRING line citations by design (300 at that card's filing, 175 after
201-
// batch 2, 82 after batch 3). Any floor above zero reds on legitimate
202-
// drainage and re-opens the same escalation one batch later; `> 0` never
203-
// lies during the migration and still catches extracts-nothing at full
204-
// strength. Ruled 2026-08-28 on #13003, comment 5458356183.
205-
//
206-
// ⛔ WHEN THIS POPULATION LEGITIMATELY REACHES ZERO — the last line citation
207-
// retired — DELETE this assertion AND this comment IN THE SAME PR that
208-
// retires it, along with the equality check below, which then compares two
209-
// zeroes and asserts nothing. A conscious decision at zero, never a silent
210-
// one; that is the whole reason the floor sits at `> 0` rather than gone.
211-
expect(Number(m![1])).toBeGreaterThan(0);
212-
expect(m![2]).toBe(m![1]);
213209
expect(line).not.toContain('PAST EOF');
214210
});
215211

0 commit comments

Comments
 (0)