Unfenced expressions: let a field that is an expression just hold one
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
Design exploration raised by the owner, 2026-08-13. Not a decision. The question is whether ${...} should be required in positions the schema already knows are expressions, or whether those fields should accept CEL directly.
The idea
Today every expression is fenced, everywhere:
- id: deploy
if: ${steps.approval.outcome == "deployed"}
But if: cannot hold anything except an expression. The fence carries no information there: it is punctuation the author writes because the parser wants it, not because the meaning is ambiguous. The proposal is that fields whose type is "expression" accept CEL directly, and ${...} survives only where it earns its place, interpolating a value into a string:
- id: deploy
if: steps.approval.outcome == "deployed"
log:
message: deploying ${inputs.version} to ${inputs.environment}
The pull of this is that the file stops looking like a template and starts looking like a language with identifiers and scopes. must: this in ["staging", "production"] reads as a constraint. must: ${this in ["staging", "production"]} reads as a constraint wrapped in ceremony.
There is proven prior art: GitHub Actions takes bare expressions in if: and reserves ${{ }} for interpolation, which is exactly this split, at very large scale.
What YAML actually allows, measured
This is where the idea meets a constraint, and the constraint is real. Parsing each form as YAML:
| Form | Result |
|---|---|
if: steps.approval.outcome == "deployed" |
parses |
must: this in ["staging", "production"] |
parses |
must: this.matches(r'^v?[0-9]+$') |
parses |
subject: issuer + "#" + name |
parses |
subject: "https://issuer.example.com#" + inputs.approver |
fails to parse |
message: "hi %s".format([x]) |
fails to parse |
outcome: a ? b : c |
fails to parse |
must: 'this in ["staging", "production"]' |
parses |
Two rules explain all of it. A plain scalar cannot begin with a quote, so any expression starting with a string literal is a YAML error. And : inside a plain scalar ends it, so a spaced ternary is a YAML error. Both are fixed by quoting the whole expression, which is what the last row shows.
So the honest form of the proposal is not "fences disappear". It is "fences are replaced by YAML's own quoting rules, which apply to some expressions and not others".
That trade deserves a clear head. A fence is uniform: every expression looks the same and the rule is syntactic. Unfenced CEL is lighter in the common case and requires the author to know when YAML forces quotes, which depends on the shape of the expression rather than on anything about Flowstate. Whether that is a better experience is a genuine question, not a foregone conclusion, and it is worth testing on real files rather than on the examples that flatter it.
Where the split would fall
The rule that makes this coherent is that the field's type decides, not the syntax:
- Expression-typed fields hold CEL directly:
if:,until:,items:,must:, a loop'sinit:andupdate:, a switch's discriminant, a signal rule'ssubject:, and the shaped output entries #533 just unified. - String-typed fields stay literal by default, with
${...}interpolating into them:message:,url:, a description. #413 made multi-fence interpolation work, so this half is already good.
This also answers what a bare word means. In an expression-typed field, foo is an identifier and resolves or fails to resolve, with a diagnostic. In a string field it is text. That distinction is currently blurred by the fence doing double duty.
What it would cost
Editions and a rewriter. The fenced form must keep working, so this is edition-gated, and flow fix migrates. That machinery exists and has been exercised repeatedly this week, but flow fix has corrupted valid files three times, so a rewrite touching every expression in every file is the highest-risk rewriter change yet attempted. It would need byte-comparison tests over the whole corpus and a very deliberate rollout.
Editor colour outside our own tooling. Our LSP is schema-aware and can highlight an expression field correctly with no fence. A generic YAML highlighter, including the one rendering these files on GitHub, cannot: the fence is what tells it something special is here. Losing that is a real cost paid by readers who are not in an editor we control, and the mitigation, shipping our own grammar for the common highlighters, is a genuine ongoing commitment rather than a one-off.
Ambiguity for tools that are not us. A .yaml file that is valid YAML but whose scalars are a second language is harder for other people's tooling to reason about. That is already true with fences, but fences at least mark where it happens.
What it would not cost
Nothing in the runtime. The DSL is a facade over the protobuf specification, and the IR already holds parsed expressions rather than source text. A frontend change of this kind does not touch semantics, both drivers, or the plugin boundary, which is also the argument that someone could bring an entirely different frontend, a visual builder or another surface syntax, and produce the same specification. That property is worth protecting for its own sake, and this proposal exercises it.
What to do next
Not build it. Establish whether it is better, with evidence:
- Take a representative set of real workflows, including the enterprise examples, and write each one both ways. Compare them as a reader, not as an author. The claim to test is legibility, and legibility is only visible at the scale of a whole file.
- Count how many expressions in the corpus would need quotes under the unfenced rule. If it is rare, the rule is easy to teach. If it is common, the unfenced form is a coin flip and the fence is better.
- Decide the highlighting question honestly before committing, since it is the cost most likely to be discovered late and resented.
- Only then consider an edition, and treat the rewriter as the most dangerous one this project has attempted.
Related
- #543, the style charter, and its distinction between what is legal, what is canonical, and what we show
- #413, multi-fence interpolation, which makes the string half of this split work
- #533, the mapping form, which unified how shaped outputs are written
- #539, the signal policy rule, whose readability is one of the motivating cases
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with a representative set of real workflows, including the enterprise examples, and write each in fenced and unfenced forms. Count expressions requiring YAML quotes and evaluate whole-file legibility and highlighting; done means producing evidence for a decision before considering an edition or rewriter.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- yaml
- Domain
- developer-experience, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100