Found by the Clause-② contract review of PR #15432 (card #15137) at CONTRACT_REVIEW_TIER, hunting past the four items it was sent to check. Filed by the domain:services execution seat (session 03324ae2-0f5b-5ad2-8a2e-cf4aaff5a909). ⛔ domain:*, type and priority are triage's — this seat does not produce them.
⛔ Pre-existing; not introduced by PR #15432 and outside its surface. That PR bound the value role on assignment. This is the predicate role on decision, which nothing in that PR touches.
The gap
Four declarations that are individually reasonable and jointly leave a hole:
|
|
DecisionConditionSchema.expression |
z.string() — schemaless-node-config.zod.ts:381 |
| the parse-time schema walk |
exempts decision — engine.ts:6907 |
| the ledger's predicate arm |
emits strings only — flow-node-expression-paths.ts:304 |
evaluateCondition at run time |
accepts { dialect, source, ast } — engine.ts:8079 |
⇒ An authored decision condition written as an envelope is invisible to both validators and is nevertheless executed.
Measured, driven through the real doors
resolveFlowNodeExpressions('decision', …envelope…) → []
registerFlow → REGISTERED, for source: ' ' and for the un-parseable 'rows.map(r,'
- lint → 0 issues for both
- at run time: the whitespace envelope returns
false silently (engine.ts:8090); the bad-CEL envelope throws
The silent false is the serious half. A decision node whose condition quietly evaluates false takes the other branch, with no error anywhere — the flow runs to completion down the wrong path. The throwing case at least announces itself.
⚠️ One measurement in the above is weaker than it looks — the reviewer said so itself
The two "lint → 0 issues" readings were taken with another envelope as the control, i.e. a control that could plausibly be the answer, which makes it not a control. Those zeros are explained by reading flow-node-expression-paths.ts:304 (the arm emits strings only) and by the existing string-form tests at validate-expressions.test.ts:678 — not by a control that could have failed. ⇒ Whoever takes this should re-establish the lint half with a control that can go red. The registerFlow and run-time readings were driven and do not depend on it.
⭐ Worth keeping: the reviewer noticed and reported this about its own evidence rather than shipping the zero unqualified.
Not a defect, stated so nobody "fixes" it
A whitespace-only string predicate behaves consistently on both sides — no seam there. The hole is specifically the envelope shape reaching a slot declared as z.string().
The shape this belongs to
Three cards now describe one family — a declaration that two layers disagree about, where the disagreement surfaces only at run time:
Whoever triages may want to decide whether the first two are one card. ⚠️ Do not assume they are: #15430 is a shape both validators see and accept; this one is a shape both validators cannot see at all. Different mechanism, possibly one fix.
Refs: PR #15432 / #15137 (the review that found it) · #14149 (the maintainer ruling behind the value role) · #15430.
Found by the Clause-② contract review of PR #15432 (card #15137) at
CONTRACT_REVIEW_TIER, hunting past the four items it was sent to check. Filed by thedomain:servicesexecution seat (session03324ae2-0f5b-5ad2-8a2e-cf4aaff5a909). ⛔domain:*, type and priority are triage's — this seat does not produce them.⛔ Pre-existing; not introduced by PR #15432 and outside its surface. That PR bound the
valuerole onassignment. This is thepredicaterole ondecision, which nothing in that PR touches.The gap
Four declarations that are individually reasonable and jointly leave a hole:
DecisionConditionSchema.expressionz.string()—schemaless-node-config.zod.ts:381decision—engine.ts:6907flow-node-expression-paths.ts:304evaluateConditionat run time{ dialect, source, ast }—engine.ts:8079⇒ An authored
decisioncondition written as an envelope is invisible to both validators and is nevertheless executed.Measured, driven through the real doors
resolveFlowNodeExpressions('decision', …envelope…)→[]registerFlow→ REGISTERED, forsource: ' 'and for the un-parseable'rows.map(r,'falsesilently (engine.ts:8090); the bad-CEL envelope throwsThe silent
falseis the serious half. A decision node whose condition quietly evaluates false takes the other branch, with no error anywhere — the flow runs to completion down the wrong path. The throwing case at least announces itself.The two "lint → 0 issues" readings were taken with another envelope as the control, i.e. a control that could plausibly be the answer, which makes it not a control. Those zeros are explained by reading
flow-node-expression-paths.ts:304(the arm emits strings only) and by the existing string-form tests atvalidate-expressions.test.ts:678— not by a control that could have failed. ⇒ Whoever takes this should re-establish the lint half with a control that can go red. TheregisterFlowand run-time readings were driven and do not depend on it.⭐ Worth keeping: the reviewer noticed and reported this about its own evidence rather than shipping the zero unqualified.
Not a defect, stated so nobody "fixes" it
A whitespace-only string predicate behaves consistently on both sides — no seam there. The hole is specifically the envelope shape reaching a slot declared as
z.string().The shape this belongs to
Three cards now describe one family — a declaration that two layers disagree about, where the disagreement surfaces only at run time:
ExpressionSchemaaccepts anast-only envelope that no engine can evaluate — it validates, it registers, it faults at run time #15430 —ExpressionSchemaaccepts asource-or-astenvelope that both validators pass and no engine can run (the whitespace-onlysourcewas folded in as a second spelling, comment 5543587545).z.string()that the evaluator nonetheless accepts as an envelope.journalConsumedSuspensionand thestatus: 'stranded'stamp makes a REPAIRABLE strand reportrepairable: false— the operator is told not to attempt a repair that works #15555 / service-automation: a subflow'sbubbleToParentfailure is swallowed, so an approval decision answers 200resumed: truewhile the run behind it is stranded — #13807's three-outcome shape, one level up #15556 on the resume path.Whoever triages may want to decide whether the first two are one card.⚠️ Do not assume they are: #15430 is a shape both validators see and accept; this one is a shape both validators cannot see at all. Different mechanism, possibly one fix.
Refs: PR #15432 / #15137 (the review that found it) · #14149 (the maintainer ruling behind the
valuerole) · #15430.