dsl: `must:` is the one expression position stored as source text — re-parsed and re-checked on every compile, refused by a walker no other position uses, and evaluated through the uncached path with an `env.Extend` per check
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 9
- Forks
- 0
- Avg merge
- 3h 3m
- Merged PRs (30d)
- 509
Description
Observed behavior
Every expression position in a compiled specification is a ParsedExpr (value.proto:143) — except must:. InputDeclaration.must (proto/flowstate/v1/workflow.proto:1043) and OutputDeclaration.must (:1121) are optional string. Consequences at 40cc365:
compileMustIn(pkg/flowstate/v1/constraints.go:231-259) parses, converts to a proto, runsrefuseNondeterministicMust, checks, and tests the output type — on every call. It is called from validate (:414,:628), from submit for every declared input (:832, underBindRunInputs) and from run-output evaluation (:874). A workflow with twenty constrained inputs parses and checks twenty expressions per submission and percall:with:binding (docs/DSL.md:4115-4118lists the four enforcement points).- Evaluation goes through
Evaluator.Eval(constraints.go:888,:929), the deliberately uncached path (celenv.go:296-306): each check re-runsorderMapComprehensionsAST,env.Extend(orderedMapEnvOption(...))andenv.Program. refuseNondeterministicMustwalks the proto fornow(constraints.go:262); every other position learns what is in scope from the validator'srefScopeandcollectReferences(validate.go:1952). A second refusal mechanism for one position is the #1437 shape.flow fmtnormalizes every expression through theAstToStringfixed point (flowfile/value.go:432) exceptmust:, which is written back as typed; and #332's proposedflow fixrewrite ofmust: this in [...]intotype: enumneeds an AST it does not have.- The spec digest and
flow breakingcomparemust:as text, sothis>=1andthis >= 1are different constraints.
Desired outcome
must: compiles to a ParsedExpr like every other position; it is checked once at compile, its refusal rules are the validator's, and its evaluation is EvalParsed on the cached program.
Acceptance criteria
InputDeclarationandOutputDeclarationgain aParsedExpr-typed field written by the compiler; the string stays for old specs (invariant 10) and is compiled on read only when the new field is absent.BindRunInputsevaluates a constrained input throughEvalParsed; a benchmark with twenty constrained inputs shows no per-submit parse.refuseNondeterministicMustis deleted in favour of the validator's scope rules applied to themust:position (thisin scope,nownot).flow fmtnormalizesmust:the way it normalizesif:; the examples corpus is byte-identical afterflow fmtand a deliberately unnormalized fixture changes.
Constraints and dependencies
- #332 (the
must:scope ladder) decides whatmust:may read; this decides how it is stored and does not widen the scope. - #1436 / #1452 decide what
thismay be typed as;mustEnvForkeeps bindingthisfrom the declared type. buf breaking: adding a field is safe; the string is not removed.
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 InputDeclaration and OutputDeclaration in proto/flowstate/v1/workflow.proto, then trace compileMustIn and its callers in pkg/flowstate/v1/constraints.go. Read ParsedExpr in value.proto and formatting in flowfile/value.go, and review the validator scope logic in validate.go. Done means must: is stored and compiled once, uses cached evaluation and validator scope rules, and flow fmt normalizes it with the stated tests and benchmark passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- compilers
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100