Found while implementing #15430 (PR #15810), outside that card's dispatched surface and deliberately not fixed there. Filed by the os-dev seat, PM session session_01M59rPZZFzqhfMUPFqqZTkf; no assignee; domain:*, type and priority are triage's.
The gap
#15430's fix scopes the evaluated-slot rule to the flow-node expression ledger (FLOW_NODE_EXPRESSION_PATHS): the assignment value envelope now composes EvaluatedExpressionSchema (packages/spec/src/shared/expression.zod.ts) — source required and non-blank after trimming, one rule, one message — and the edge condition is #15807. ExpressionSchema / ExpressionInputSchema themselves stay the persistence contract (source OR ast; min(1) on source), by design.
The same seam exists on every OTHER slot that ExpressionInputSchema types and some engine evaluates. Measured consumer map on origin/main 8e500f2 (git grep of ExpressionInputSchema in packages/spec/src, non-test):
For each, { dialect: 'cel', ast: {...} } and { dialect: 'cel', source: ' ' } parse today. What happens next differs per evaluator and is the part that needs measuring before any narrowing: objectql/src/validation/rule-validator.ts documents that an AST-only rule condition answers the LOUD branch (its readsUserRoot note: "anything this reader cannot see is a predicate the evaluator could not have run either"); ADR-0078 pins rejects an AST-only or non-CEL visible loudly (kernel/public-auth-features.test.ts, ui/action.test.ts); ui/view-form-features-root.test.ts pins "an AST-only envelope is opaque at this layer and passes (documented boundary)"; metadata-core/src/form-predicate-root-policy.ts passes an opaque predicate. So some of these are already loud, some documented-opaque, and some (formula expression, the *When field predicates, sharing condition) unmeasured.
What a fix would be
Not a blanket swap of ExpressionInputSchema — that is the persistence contract and its M9.2 note. Per slot: census which engine reads it and how an ast-only / blank source envelope fails today (loud, silent, or documented-opaque); where the answer is "silent" or "faults at run time", compose the evaluated form (an EvaluatedExpressionInputSchema sibling — bare string → { dialect: 'cel', source }, envelope → EvaluatedExpressionSchema — is the one-spelling way; #15807 needs the same sibling). Each slot narrowed is an accept-set change on a published schema (Clause ②), so this is a spec-lane decision per slot family rather than one sweep.
Back-link: #15430, PR #15810 (the rule and its H1 measurement), #15807 (the edge-condition half), #15028 (adjacent: the dialect is fixed only on the bare-string arm of CronExpressionInputSchema / TemplateExpressionInputSchema).
Generated by Claude Code
Found while implementing #15430 (PR #15810), outside that card's dispatched surface and deliberately not fixed there. Filed by the
os-devseat, PM sessionsession_01M59rPZZFzqhfMUPFqqZTkf; no assignee;domain:*, type and priority are triage's.The gap
#15430's fix scopes the evaluated-slot rule to the flow-node expression ledger (
FLOW_NODE_EXPRESSION_PATHS): theassignmentvalue envelope now composesEvaluatedExpressionSchema(packages/spec/src/shared/expression.zod.ts) —sourcerequired and non-blank after trimming, one rule, one message — and the edge condition is #15807.ExpressionSchema/ExpressionInputSchemathemselves stay the persistence contract (sourceORast;min(1)onsource), by design.The same seam exists on every OTHER slot that
ExpressionInputSchematypes and some engine evaluates. Measured consumer map onorigin/main8e500f2 (git grepofExpressionInputSchemainpackages/spec/src, non-test):data/field.zod.ts: formulaexpression; fieldvisibleWhen/readonlyWhen/requiredWhen; per-optionvisibleWhen; grid-columnreadonlyWhen/requiredWhendata/validation.zod.ts: rulecondition(×2),whendata/hook.zod.ts: hookconditiondata/object.zod.ts:visibleWhen/disabledWhen(list-view and action surfaces)security/sharing.zod.ts: sharing-ruleconditionkernel/plugin-security-advanced.zod.ts:condition;kernel/plugin-versioning.zod.ts: routingconditionui/action.zod.ts: per-optionvisibleWhen, paramvisible,ActionConditionInputSchema;ui/app.zod.ts:visiblesystem/settings-manifest.zod.ts:SettingsVisibilityInputSchema;system/metrics.zod.ts,system/tracing.zod.ts: expression members of unionsautomation/flow.zod.ts:FlowEdgeSchema.condition— this one is spec/automation:FlowEdgeSchema.conditionstill accepts an envelope the engine cannot evaluate (ast-only, whitespace-onlysource) — the evaluated-slot rule of #15430 has not reached the edge condition #15807For each,
{ dialect: 'cel', ast: {...} }and{ dialect: 'cel', source: ' ' }parse today. What happens next differs per evaluator and is the part that needs measuring before any narrowing:objectql/src/validation/rule-validator.tsdocuments that an AST-only rule condition answers the LOUD branch (itsreadsUserRootnote: "anything this reader cannot see is a predicate the evaluator could not have run either"); ADR-0078 pinsrejects an AST-only or non-CEL visible loudly(kernel/public-auth-features.test.ts,ui/action.test.ts);ui/view-form-features-root.test.tspins "an AST-only envelope is opaque at this layer and passes (documented boundary)";metadata-core/src/form-predicate-root-policy.tspasses an opaque predicate. So some of these are already loud, some documented-opaque, and some (formulaexpression, the*Whenfield predicates, sharingcondition) unmeasured.What a fix would be
Not a blanket swap of
ExpressionInputSchema— that is the persistence contract and its M9.2 note. Per slot: census which engine reads it and how anast-only / blanksourceenvelope fails today (loud, silent, or documented-opaque); where the answer is "silent" or "faults at run time", compose the evaluated form (anEvaluatedExpressionInputSchemasibling — bare string →{ dialect: 'cel', source }, envelope →EvaluatedExpressionSchema— is the one-spelling way; #15807 needs the same sibling). Each slot narrowed is an accept-set change on a published schema (Clause ②), so this is a spec-lane decision per slot family rather than one sweep.Back-link: #15430, PR #15810 (the rule and its H1 measurement), #15807 (the edge-condition half), #15028 (adjacent: the dialect is fixed only on the bare-string arm of
CronExpressionInputSchema/TemplateExpressionInputSchema).Generated by Claude Code