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
spec/automation: FlowEdgeSchema.condition still accepts an envelope the engine cannot evaluate (ast-only, whitespace-only source) — the evaluated-slot rule of #15430 has not reached the edge condition #15807
Split out of #15430 by its os-dev seat (PM session session_01M59rPZZFzqhfMUPFqqZTkf): the half of that card this seat could not execute inside its file surface. Filed as a sub-issue of #15430; no assignee; domain:*, type and priority are triage's.
Blocked-by: #15792 (in flight — structuralConditionRefusal admits an ast-carrying envelope on purpose with a rec.ast !== undefined clause naming #15430; that admission is the line to revisit together with this narrowing, see #15430's comment 5550509137).
The remaining evaluated slot
The #15430 PR narrows the ledger's value slot (assignment.assignments.*, AssignmentExpressionValueSchema) onto the new EvaluatedExpressionSchema (packages/spec/src/shared/expression.zod.ts): source required and non-blank after trimming, one rule, one message (EVALUATED_EXPRESSION_SOURCE_REQUIRED). The ledger's predicate slots (screen.fields[].visibleWhen, decision.conditions[].expression) are typed z.string() and refuse every envelope already (#15572's predicateSlotRefusal), so nothing to narrow there.
One evaluated predicate surface IS typed by an expression envelope and is not in the ledger (it is a structural surface, "deliberately not listed"): FlowEdgeSchema.condition: ExpressionInputSchema.optional() (packages/spec/src/automation/flow.zod.ts, the condition key of the edge schema). evaluateCondition reads it at run time (engine.ts, the edge.condition walk) as expression.source ?? '', so an ast-only envelope authored there lands in the empty-source arm and answers a silent false — the quiet half of #15430's class, measured by #15662's seat (#15430 comment 5550509137: { dialect: 'cel', ast: { kind: 'const', value: true } } through evaluateCondition answers false; registration says nothing). A whitespace-only source there is the same seam through the other key.
start.config.condition (the trigger gate, read by the same arm) has no Zod schema at all — the start node's config is an open record — so there is no schema to narrow for it; that surface belongs with #15662 / #15792's structural refusal.
What this sub-issue does
packages/spec/src/automation/flow.zod.ts: type FlowEdgeSchema.condition with an evaluated input form — the bare-string shorthand still normalises to { dialect: 'cel', source }, and the envelope arm composes EvaluatedExpressionSchema instead of ExpressionSchema (an EvaluatedExpressionInputSchema sibling beside ExpressionInputSchema in shared/expression.zod.ts is the one-spelling way to say it). ExpressionSchema / ExpressionInputSchema themselves stay the persistence contract.
Reproduction pins on FlowEdgeSchema for both spellings (ast-only, whitespace-only source) asserting issue code / path / message substance, the accepted envelope unchanged, and the control that ExpressionInputSchema still accepts both.
Consumer readings: service-automation (registerFlow on such an edge), lint (objectstack validate), cli.
Why not in the #15430 PR: flow.zod.ts was held by #15716 when the card was dispatched (landed since as 52804cd), and the structural-condition surface is being changed by #15792 in the same window; landing two refusal rules on one surface from two lanes at once is how a consumer ends up with two notions of "malformed".
Split out of #15430 by its
os-devseat (PM sessionsession_01M59rPZZFzqhfMUPFqqZTkf): the half of that card this seat could not execute inside its file surface. Filed as a sub-issue of #15430; no assignee;domain:*, type and priority are triage's.Blocked-by: #15792 (in flight —
structuralConditionRefusaladmits anast-carrying envelope on purpose with arec.ast !== undefinedclause naming #15430; that admission is the line to revisit together with this narrowing, see #15430's comment 5550509137).The remaining evaluated slot
The #15430 PR narrows the ledger's
valueslot (assignment.assignments.*,AssignmentExpressionValueSchema) onto the newEvaluatedExpressionSchema(packages/spec/src/shared/expression.zod.ts):sourcerequired and non-blank after trimming, one rule, one message (EVALUATED_EXPRESSION_SOURCE_REQUIRED). The ledger'spredicateslots (screen.fields[].visibleWhen,decision.conditions[].expression) are typedz.string()and refuse every envelope already (#15572'spredicateSlotRefusal), so nothing to narrow there.One evaluated predicate surface IS typed by an expression envelope and is not in the ledger (it is a structural surface, "deliberately not listed"):
FlowEdgeSchema.condition: ExpressionInputSchema.optional()(packages/spec/src/automation/flow.zod.ts, theconditionkey of the edge schema).evaluateConditionreads it at run time (engine.ts, theedge.conditionwalk) asexpression.source ?? '', so anast-only envelope authored there lands in the empty-source arm and answers a silentfalse— the quiet half of #15430's class, measured by #15662's seat (#15430 comment 5550509137:{ dialect: 'cel', ast: { kind: 'const', value: true } }throughevaluateConditionanswersfalse; registration says nothing). A whitespace-onlysourcethere is the same seam through the other key.start.config.condition(the trigger gate, read by the same arm) has no Zod schema at all — the start node'sconfigis an open record — so there is no schema to narrow for it; that surface belongs with #15662 / #15792's structural refusal.What this sub-issue does
packages/spec/src/automation/flow.zod.ts: typeFlowEdgeSchema.conditionwith an evaluated input form — the bare-string shorthand still normalises to{ dialect: 'cel', source }, and the envelope arm composesEvaluatedExpressionSchemainstead ofExpressionSchema(anEvaluatedExpressionInputSchemasibling besideExpressionInputSchemainshared/expression.zod.tsis the one-spelling way to say it).ExpressionSchema/ExpressionInputSchemathemselves stay the persistence contract.FlowEdgeSchemafor both spellings (ast-only, whitespace-onlysource) asserting issuecode/ path / message substance, the accepted envelope unchanged, and the control thatExpressionInputSchemastill accepts both.service-automation(registerFlowon such an edge),lint(objectstack validate),cli.rec.ast !== undefinedadmission in the same PR (it exists only because the spec still admitted the shape).@objectstack/specminor, BREAKING banner, ADR-0087 disposition (the spec/formula:ExpressionSchemaaccepts anast-only envelope that no engine can evaluate — it validates, it registers, it faults at run time #15430 census found zero in-repo instances of either spelling on any evaluated slot; re-run it for edges).Why not in the #15430 PR:
flow.zod.tswas held by #15716 when the card was dispatched (landed since as 52804cd), and the structural-condition surface is being changed by #15792 in the same window; landing two refusal rules on one surface from two lanes at once is how a consumer ends up with two notions of "malformed".Generated by Claude Code