You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(spec)!: an evaluated expression slot requires a non-blank source — EvaluatedExpressionSchema, composed by the assignment value envelope (#15430) (#15810)
* feat(spec)!: an evaluated expression slot requires a non-blank source — EvaluatedExpressionSchema, composed by the assignment value envelope
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
* chore(spec): regenerate api-surface, export-origins, declaration-map and docs for EvaluatedExpressionSchema
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
* fix(spec): declare EvaluatedExpressionParsed beside the author-state alias (ADR-0122) and regenerate
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
* chore(changeset): say the remedy without a migration label — nothing is renamed, the refusal carries the prescription
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
* test(service-automation): narrow the defence-in-depth EvalResult pin on its discriminant before reading error
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
---------
Co-authored-by: Claude <noreply@anthropic.com>
feat(spec)!: an evaluated expression slot requires a non-blank `source` — `EvaluatedExpressionSchema`, composed by the `assignment` value envelope (#15430)
6
+
7
+
<!-- adr-0087: not-required (no-migration-prescription) No authorable key is renamed, retired or re-typed: `source` keeps its name and meaning, and every envelope that carried a non-blank `source` parses byte-identically. The two newly refused spellings — an envelope carrying only `ast`, and a `source` that is blank after trimming — never evaluated on any release (the `ast`-only one faulted at run time with the engine's own "persist `source`" prescription, the blank one with a parse error), and a repo-wide census found no in-repo instance of either, so `objectstack migrate meta` has nothing to rewrite and the remedy is authoring a `source`, which the refusal itself prescribes. -->
8
+
9
+
**BREAKING** in the accept-set sense, landing in the launch window as `minor`
10
+
(the lockstep convention): on the schemas that type an EVALUATED expression
11
+
slot — today the `assignment` node's value envelope,
12
+
`AssignmentExpressionValueSchema` — an envelope with no `source` the engine can
13
+
evaluate is now **refused at authoring**, where it used to parse, register,
14
+
pass `objectstack validate`, and then fault at run time.
15
+
16
+
Two spellings of one seam, refused by ONE rule with one message at `source`
17
+
(`EVALUATED_EXPRESSION_SOURCE_REQUIRED`):
18
+
19
+
```yaml
20
+
assignments:
21
+
digest: { dialect: cel, ast: { kind: const } } # `ast` only — no engine evaluates it
22
+
greeting: { dialect: cel, source: ' ' } # blank after trimming — parses to EOF
23
+
```
24
+
25
+
> An expression in an evaluated slot needs a non-blank `source`: the expression
26
+
> engine evaluates `source` (the canonical persisted form of phase M9.1) and
27
+
> cannot evaluate `ast` alone, so an envelope carrying only `ast`, or a `source`
28
+
> that is blank after trimming, would validate and register and then fault at
29
+
> run time. Write `{ dialect: 'cel', source: '…' }`.
30
+
31
+
- **`ExpressionSchema` is NOT narrowed.** It is the persistence contract —
32
+
`source`OR `ast` — and its docblock declares that `ast` becomes required in
33
+
build output at phase M9.2. The new export `EvaluatedExpressionSchema` (and
34
+
its type `EvaluatedExpression`) is a sibling: the same envelope with `source`
35
+
required and non-blank, spelled once and composed by every evaluated slot, so
36
+
when AST-only evaluation lands the flip is one edit there rather than a
37
+
per-slot unwinding. The rule is worded as "an evaluated slot requires whatever
38
+
the engine can actually evaluate"; what that is today is `source`.
39
+
- **The notion of blank is the engine's own** — `.trim()`, which
40
+
`cel-engine.ts`'s helpers already apply — not a third one beside the shape
41
+
rule's `min(1)` and `validateExpression`'s trim.
42
+
- **Three doors agree.** `registerFlow` refuses the flow, `objectstack validate`
43
+
and the runtime publish gate report a located `error` at the author's own
44
+
variable (`config.assignments.<name>.source`), and the executor's own shape
45
+
pass refuses the same set — all through the spec schema, so none of them
46
+
grew a rule of its own.
47
+
48
+
**What an author does with a refused envelope.** An assignment value that
49
+
carried only `ast` has no evaluable form under M9.1: author its `source`. A
50
+
whitespace-only `source` was never an expression: delete the entry, or write
51
+
the expression. Every envelope with a non-blank `source` is unchanged, and
52
+
nothing is renamed, retired or rewritten — the refusal itself carries the
53
+
prescription.
54
+
55
+
Not touched here: the `predicate` half of the same seam — `evaluateCondition`'s
56
+
silent `false` on an envelope without a `source` — is a behaviour change on a
57
+
live path with its own card, and the edge-condition schema that carries that
58
+
envelope is narrowed in a follow-up once the in-flight change to
0 commit comments