|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +--- |
| 4 | + |
| 5 | +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 |
| 59 | +`automation/flow.zod.ts` lands. |
0 commit comments