Skip to content

Commit 9f30a18

Browse files
committed
revert(spec): drop the predicate narrowing — #18638 owns every evaluated slot
Decision batch #160 item 1 (letter A, maintainer 「同意」 2026-09-18T11:58Z) on #19003: this PR drops its triad-slot edits, the `PredicateSchema` / `PredicateInputSchema` rebinding, the `field-rule-predicate-evaluated-slot-source-required` ledger entry and every api-surface / reference-page row that existed only because of them. Decision batch #122 item 2 (card #15811) had already ruled the same narrowing across all 36 evaluated slots — the field-rule triad named in its own census — and PR #18638 lands it under ONE ADR-0087 id. Card #17778 ruled fault semantics, not the carrier symbol; nothing ruled is lost. Reverted to the merged-main content byte-for-byte (`git checkout d8b12fc --` for the three sources and the field.zod anchor; empty `git diff` against that tree for each), so the aliases are again plain aliases of the persistence contract, wide, with zero slot users. KEPT, per the same ruling: the producer fix. `cel()` / `expression()` still declare the `EvaluatedExpression` they always emitted — narrowing a return type removes nothing from a caller — with the docblock rewritten so it no longer rests on a triad requirement this commit removes. ⛔ No gate weakened. The pin test and the ADR-0087 entry are removed because the behaviour they recorded no longer happens in this PR, not to get green: the entry file is deleted and `gen:migration-registry` re-emitted `registry.ts`, which is byte-identical to merged main. `check:generated` proved exactly two artifacts stale and `--fix` regenerated only those; the residue against merged main is 4 lines in api-surface-declarations, all of them the kept producer fix, and `content/docs/references/**` is byte-identical. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
1 parent e1978a0 commit 9f30a18

17 files changed

Lines changed: 525 additions & 924 deletions

‎content/docs/references/data/field.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ const result = CurrencyConfigSchema.parse(data);
102102
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
103103
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
104104
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |
105-
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is shown only when TRUE (else hidden). Needs a non-blank `source`: an evaluated slot is held to what the engine can run (ADR-0136 D1). A fault at RENDER is fail-open (the field shows) and refuses the SUBMIT, naming the field and this rule (D2/D3). e.g. P`record.type == 'invoice'` |
106-
| **readonlyWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is read-only when TRUE. Needs a non-blank `source` (ADR-0136 D1); a fault refuses the submit and names the field and this rule (D2). e.g. P`record.status == 'paid'` |
107-
| **requiredWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is required when TRUE. Needs a non-blank `source` (ADR-0136 D1); a fault refuses the submit and names the field and this rule (D2). A TRANSITION GATE, not an invariant: the write is refused only when the merged record violates the requirement AND the pre-write record complied — so the write that flips the predicate TRUE, an INSERT born inside the gate, and a write that clears the cell are all refused, while a row that was already missing the value keeps passing unrelated edits and state moves that stay inside the gate (ADR-0113 non-regression: adding the rule to a deployed object never bricks existing rows). Need an invariant every write must satisfy instead ('X may never exceed Y') — declare a `validations[]` `script` rule, which re-checks the merged record with no exemption. Enforced by `evaluateValidationRules`. The only slot; the `conditionalRequired` alias was removed in protocol 17. |
105+
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is shown only when TRUE (else hidden). e.g. P`record.type == 'invoice'` |
106+
| **readonlyWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is read-only when TRUE. e.g. P`record.status == 'paid'` |
107+
| **requiredWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is required when TRUE. A TRANSITION GATE, not an invariant: the write is refused only when the merged record violates the requirement AND the pre-write record complied — so the write that flips the predicate TRUE, an INSERT born inside the gate, and a write that clears the cell are all refused, while a row that was already missing the value keeps passing unrelated edits and state moves that stay inside the gate (ADR-0113 non-regression: adding the rule to a deployed object never bricks existing rows). Need an invariant every write must satisfy instead ('X may never exceed Y') — declare a `validations[]` `script` rule, which re-checks the merged record with no exemption. Enforced by `evaluateValidationRules`. The only slot; the `conditionalRequired` alias was removed in protocol 17. |
108108
| **conditionalRequired** | `never` | optional | [REMOVED] `conditionalRequired` was removed in @objectstack/spec 17 — use `requiredWhen`. Rename the key; the value (a CEL predicate) is unchanged. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
109109
| **widget** | `string` | optional | Form widget override — names a registered field component (resolved as `field:<widget>`) to render this field instead of the `type` default. Degrades to the `type` renderer when unregistered. e.g. "object-ref", "filter-condition", "recipient-picker". |
110110
| **hidden** | `boolean` | optional (default: `false`) | Hidden from default UI |

‎content/docs/references/data/object.mdx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,9 @@ const result = ApiMethod.parse(data);
264264
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
265265
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
266266
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |
267-
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is shown only when TRUE (else hidden). Needs a non-blank `source`: an evaluated slot is held to what the engine can run (ADR-0136 D1). A fault at RENDER is fail-open (the field shows) and refuses the SUBMIT, naming the field and this rule (D2/D3). e.g. P`record.type == 'invoice'` |
268-
| **readonlyWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is read-only when TRUE. Needs a non-blank `source` (ADR-0136 D1); a fault refuses the submit and names the field and this rule (D2). e.g. P`record.status == 'paid'` |
269-
| **requiredWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is required when TRUE. Needs a non-blank `source` (ADR-0136 D1); a fault refuses the submit and names the field and this rule (D2). A TRANSITION GATE, not an invariant: the write is refused only when the merged record violates the requirement AND the pre-write record complied — so the write that flips the predicate TRUE, an INSERT born inside the gate, and a write that clears the cell are all refused, while a row that was already missing the value keeps passing unrelated edits and state moves that stay inside the gate (ADR-0113 non-regression: adding the rule to a deployed object never bricks existing rows). Need an invariant every write must satisfy instead ('X may never exceed Y') — declare a `validations[]` `script` rule, which re-checks the merged record with no exemption. Enforced by `evaluateValidationRules`. The only slot; the `conditionalRequired` alias was removed in protocol 17. |
267+
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is shown only when TRUE (else hidden). e.g. P`record.type == 'invoice'` |
268+
| **readonlyWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is read-only when TRUE. e.g. P`record.status == 'paid'` |
269+
| **requiredWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is required when TRUE. A TRANSITION GATE, not an invariant: the write is refused only when the merged record violates the requirement AND the pre-write record complied — so the write that flips the predicate TRUE, an INSERT born inside the gate, and a write that clears the cell are all refused, while a row that was already missing the value keeps passing unrelated edits and state moves that stay inside the gate (ADR-0113 non-regression: adding the rule to a deployed object never bricks existing rows). Need an invariant every write must satisfy instead ('X may never exceed Y') — declare a `validations[]` `script` rule, which re-checks the merged record with no exemption. Enforced by `evaluateValidationRules`. The only slot; the `conditionalRequired` alias was removed in protocol 17. |
270270
| **conditionalRequired** | `never` | optional | [REMOVED] `conditionalRequired` was removed in @objectstack/spec 17 — use `requiredWhen`. Rename the key; the value (a CEL predicate) is unchanged. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
271271
| **widget** | `string` | optional | Form widget override — names a registered field component (resolved as `field:<widget>`) to render this field instead of the `type` default. Degrades to the `type` renderer when unregistered. e.g. "object-ref", "filter-condition", "recipient-picker". |
272272
| **hidden** | `boolean` | optional (default: `false`) | Hidden from default UI |
@@ -597,9 +597,9 @@ const result = ApiMethod.parse(data);
597597
| **dimensions** | `integer` | optional | Vector dimensionality (e.g., 1536 for OpenAI embeddings) |
598598
| **trackHistory** | `boolean` | optional | Render this field's value changes as human-readable entries on the record activity timeline (ADR-0052 §5b). Opt-in per field. |
599599
| **group** | `string` | optional | Field group name for organizing fields in forms and layouts (e.g., "contact_info", "billing", "system") |
600-
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is shown only when TRUE (else hidden). Needs a non-blank `source`: an evaluated slot is held to what the engine can run (ADR-0136 D1). A fault at RENDER is fail-open (the field shows) and refuses the SUBMIT, naming the field and this rule (D2/D3). e.g. P`record.type == 'invoice'` |
601-
| **readonlyWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is read-only when TRUE. Needs a non-blank `source` (ADR-0136 D1); a fault refuses the submit and names the field and this rule (D2). e.g. P`record.status == 'paid'` |
602-
| **requiredWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is required when TRUE. Needs a non-blank `source` (ADR-0136 D1); a fault refuses the submit and names the field and this rule (D2). A TRANSITION GATE, not an invariant: the write is refused only when the merged record violates the requirement AND the pre-write record complied — so the write that flips the predicate TRUE, an INSERT born inside the gate, and a write that clears the cell are all refused, while a row that was already missing the value keeps passing unrelated edits and state moves that stay inside the gate (ADR-0113 non-regression: adding the rule to a deployed object never bricks existing rows). Need an invariant every write must satisfy instead ('X may never exceed Y') — declare a `validations[]` `script` rule, which re-checks the merged record with no exemption. Enforced by `evaluateValidationRules`. The only slot; the `conditionalRequired` alias was removed in protocol 17. |
600+
| **visibleWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is shown only when TRUE (else hidden). e.g. P`record.type == 'invoice'` |
601+
| **readonlyWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is read-only when TRUE. e.g. P`record.status == 'paid'` |
602+
| **requiredWhen** | `string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Predicate (CEL) — field is required when TRUE. A TRANSITION GATE, not an invariant: the write is refused only when the merged record violates the requirement AND the pre-write record complied — so the write that flips the predicate TRUE, an INSERT born inside the gate, and a write that clears the cell are all refused, while a row that was already missing the value keeps passing unrelated edits and state moves that stay inside the gate (ADR-0113 non-regression: adding the rule to a deployed object never bricks existing rows). Need an invariant every write must satisfy instead ('X may never exceed Y') — declare a `validations[]` `script` rule, which re-checks the merged record with no exemption. Enforced by `evaluateValidationRules`. The only slot; the `conditionalRequired` alias was removed in protocol 17. |
603603
| **conditionalRequired** | `never` | optional | [REMOVED] `conditionalRequired` was removed in @objectstack/spec 17 — use `requiredWhen`. Rename the key; the value (a CEL predicate) is unchanged. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
604604
| **widget** | `string` | optional | Form widget override — names a registered field component (resolved as `field:<widget>`) to render this field instead of the `type` default. Degrades to the `type` renderer when unregistered. e.g. "object-ref", "filter-condition", "recipient-picker". |
605605
| **hidden** | `boolean` | optional (default: `false`) | Hidden from default UI |

‎content/docs/references/shared/expression.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Type: `string`
208208
| Property | Type | Required | Description |
209209
| :--- | :--- | :--- | :--- |
210210
| **dialect** | `Enum<'cel' \| 'cron' \| 'template'>` | ✅ | |
211-
| **source** | `string` | ✅ | |
211+
| **source** | `string` | optional | |
212212
| **ast** | `any` | optional | |
213213
| **meta** | `{ rationale?: string; generatedBy?: string }` | optional | |
214214

@@ -234,7 +234,7 @@ Type: `string`
234234
| Property | Type | Required | Description |
235235
| :--- | :--- | :--- | :--- |
236236
| **dialect** | `Enum<'cel' \| 'cron' \| 'template'>` | ✅ | |
237-
| **source** | `string` | ✅ | |
237+
| **source** | `string` | optional | |
238238
| **ast** | `any` | optional | |
239239
| **meta** | `{ rationale?: string; generatedBy?: string }` | optional | |
240240

0 commit comments

Comments
 (0)