WS02 data and outcome semantics#170
Conversation
- Replace shared_variable with data "internal" blocks
- Replace shared_writes with write { target = ..., value = ... } blocks
- Replace string-form next with traversal syntax (step.foo, state.done, return, continue)
- Rename SharedVarStore to DataStore keyed by (kind, name)
- Update all examples, tests, docs, and self-workflows
- Add compile_data.go, data_store.go, and their tests
- Remove compile_shared_variables.go and shared_var_store.go
- Update VSCode grammar notes in workstream
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
brokenbot
left a comment
There was a problem hiding this comment.
WS02 complete. All workstream exit criteria met: blocks with write blocks, outcome now uses traversals (, , , ), legacy syntax (, , string-form ) properly rejected with migration hints, engine runtime renamed to with , all 60+ HCL files migrated, go test -race ./...
? github.com/brokenbots/criteria/cmd/criteria [no test files]
ok github.com/brokenbots/criteria/cmd/criteria-adapter-copilot (cached)
ok github.com/brokenbots/criteria/cmd/criteria-adapter-copilot/testfixtures/fake-copilot (cached)
ok github.com/brokenbots/criteria/cmd/criteria-adapter-mcp (cached)
ok github.com/brokenbots/criteria/cmd/criteria-adapter-mcp/mcpclient (cached)
? github.com/brokenbots/criteria/cmd/criteria-adapter-mcp/testfixtures/echo-mcp [no test files]
ok github.com/brokenbots/criteria/cmd/criteria-adapter-noop (cached)
ok github.com/brokenbots/criteria/events (cached)
? github.com/brokenbots/criteria/internal/adapter [no test files]
ok github.com/brokenbots/criteria/internal/adapter/audit (cached)
ok github.com/brokenbots/criteria/internal/adapter/conformance (cached)
? github.com/brokenbots/criteria/internal/adapter/conformance/testfixtures/broken [no test files]
ok github.com/brokenbots/criteria/internal/adapterhost (cached)
? github.com/brokenbots/criteria/internal/adapterhost/testfixtures/permissive [no test files]
? github.com/brokenbots/criteria/internal/adapterhost/testfixtures/publicsdk [no test files]
ok github.com/brokenbots/criteria/internal/adapters/shell (cached)
ok github.com/brokenbots/criteria/internal/cli (cached)
? github.com/brokenbots/criteria/internal/cli/applytest [no test files]
ok github.com/brokenbots/criteria/internal/cli/localresume (cached)
ok github.com/brokenbots/criteria/internal/engine (cached)
? github.com/brokenbots/criteria/internal/engine/runtime [no test files]
ok github.com/brokenbots/criteria/internal/run (cached)
? github.com/brokenbots/criteria/internal/testutil [no test files]
ok github.com/brokenbots/criteria/internal/transport/server (cached)
ok github.com/brokenbots/criteria/proto/criteria/v2 (cached)
ok github.com/brokenbots/criteria/tools/import-lint (cached)
ok github.com/brokenbots/criteria/tools/lint-baseline (cached)
ok github.com/brokenbots/criteria/tools/llmpack-check (cached)
ok github.com/brokenbots/criteria/tools/spec-gen (cached)
cd sdk && go test -race ./...
ok github.com/brokenbots/criteria/sdk (cached)
ok github.com/brokenbots/criteria/sdk/adapterhost (cached)
ok github.com/brokenbots/criteria/sdk/conformance (cached)
? github.com/brokenbots/criteria/sdk/pb/criteria/v1 [no test files]
? github.com/brokenbots/criteria/sdk/pb/criteria/v1/criteriav1connect [no test files]
cd workflow && go test -race ./...
ok github.com/brokenbots/criteria/workflow (cached) and mkdir -p bin
go build -o bin/criteria ./cmd/criteria
Validating examples/build_and_test...
examples/build_and_test: ok
Validating examples/copilot_planning_then_execution...
examples/copilot_planning_then_execution: ok
Validating examples/demo_tour_local...
examples/demo_tour_local: ok
Validating examples/file_function...
examples/file_function: ok
Validating examples/hello...
examples/hello: ok
Validating examples/fileset...
examples/fileset: ok
Validating examples/perf_1000_logs...
examples/perf_1000_logs: ok
Validating examples/phase3-environment...
examples/phase3-environment: ok
Validating examples/phase3-fold...
examples/phase3-fold: ok
Validating examples/phase3-multi-file...
examples/phase3-multi-file: ok
Validating examples/phase3-output...
examples/phase3-output: ok
Validating examples/phase3-subworkflow...
examples/phase3-subworkflow: ok
Validating examples/phase3-shared-variable...
examples/phase3-shared-variable: ok
Validating examples/phase3-parallel...
examples/phase3-parallel: ok
Validating examples/templatefile...
examples/templatefile: ok
Validating examples/hash-encoding...
examples/hash-encoding: ok
Validating examples/while...
examples/while: ok
Validating examples/llm-pack/01-linear...
examples/llm-pack/01-linear: ok
Validating examples/llm-pack/02-branching-switch...
examples/llm-pack/02-branching-switch: ok
Validating examples/llm-pack/03-iteration-for-each...
examples/llm-pack/03-iteration-for-each: ok
Validating examples/llm-pack/04-iteration-parallel...
examples/llm-pack/04-iteration-parallel: ok
Validating examples/llm-pack/05-subworkflow...
examples/llm-pack/05-subworkflow: ok
Validating examples/llm-pack/06-approval-and-wait...
examples/llm-pack/06-approval-and-wait: ok
Validating examples/llm-pack/07-shared-variable...
examples/llm-pack/07-shared-variable: ok
Validating examples/llm-pack/08-fileset-template...
examples/llm-pack/08-fileset-template: ok
Validating examples/plugins/greeter/example.hcl...
examples/plugins/greeter/example.hcl: ok
All examples validated. clean, import boundaries verified.
brokenbot
left a comment
There was a problem hiding this comment.
Verified all 13 remediations from prior review are addressed: 5 stale comments fixed, 6 new compile-time tests added, 3 doc files updated. make test, make validate, make build, go vet, make lint-imports, make spec-check, make validate-self-workflows all pass. HCL grep sweep is clean. All 8 plan steps confirmed implemented. PR meets the workstream acceptance bar.
WS02 —
datablock and outcome semanticsPhase: Language Cleanup · Track: Language · Owner: Workstream executor · Depends on: WS01 (cleaned schema, type expressions,
outcome "default"form). · Unblocks: future remote-data sources (data "http",data "remote_state", etc.). · Base branch:mainContext
Two semantic warts in the workflow HCL share enough underlying code (outcome compilation, runtime store, eval context shape) that they're best landed together:
Outcome
nextis a magic string.next = "step_name",next = "return",next = "_continue". Switch conditions already accept the traversal form (next = step.foo) viaresolveNextAttr. Outcomes should match — plus the"return"and"_continue"sentinels become bare keywords (return,continue). The big win is renaming: if a new target type (e.g. aninline_workflowstep) appears later, callers don't have to worry about naming conflicts because the kind is part of the reference.shared_variableis not Terraform-shaped. Terraform hasresource(read-write) anddata(read-only-from-the-perspective-of-Terraform). There's no clean Terraform parallel to a workflow-scoped mutable variable, butdatais the closer of the two and people already use that terminology to describe it. Renamingshared_variabletodata "internal" "name"(withdata.internal.name.valuereads) opens the door to future remotedatasources (data "http",data "remote_state", etc.) without re-shaping the surface. Internal-kind values are mutable; remote-kind values would be read-only — same as Terraform'sdatasemantics.Bundling these two changes is correct because:
compileOutcomeBlock,compileOutcomeRemain)..criteria/workflows/files.Migration strategy is hard break with helpful errors, same as WS01.
Prerequisites
OutcomeSpec, which lives next to many of the structs WS01 touches.cty.Objectnested-namespace eval contexts (already used byeach.*andstep.*references).In scope
Step 1 — Outcome
nexttraversalsToday:
next = "step_name",next = "return",next = "_continue"Target:
next = step.step_name,next = state.done,next = return,next = continueOutcomeSpec.Next string hcl:"next"(line 292) →Next hcl.Expression hcl:"next". The expression is resolved by the compiler, not stored as a string.resolveNextAttrto accept:step.<name>,state.<name>,switch.<name>,wait.<name>,approval.<name>(already supported).returnandcontinue(TraverseRoot with no attribute). These lower to the existing internal sentinels (ReturnSentinel = "return"and"_continue").compileOutcomeBlockto callresolveNextAttrfor every outcome.ReturnSentinel, the"_continue"string compared against in compile_steps_graph.go:293 and in the engine iteration path) become internal-only representations — surface syntax never quotes them. Keep the constants for now; just don't let them appear in user-authored HCL.next = "..."in an outcome and emitnext is now a node reference: write next = step.foo, next = state.done, next = return, or next = continue.Step 2 —
data "internal" "name"blockToday:
Target:
DataSpecin workflow/schema.go:DataNodemirroringSharedVariableNode:SpecandSpecContent: replaceSharedVariables []SharedVariableSpecwithData []DataSpec. TheFSMGraph.SharedVariablesmap andSharedVariableOrderslice are replaced byData map[string]map[string]*DataNode(keyed bykindthenname) andDataOrder []DataReffor stable iteration. (Implementation note: choose whichever flat shape compiles cleanly — the surface contract isdata.<kind>.<name>.value.)workflow/compile_data.goparalleling compile_shared_variables.go. Initially onlykind = "internal"is supported — emit a clearunsupported data kind %q; only "internal" is currently supportedfor anything else, paving a clean extension point for future kinds.Step 3 — Eval context: nested
datanamespaceBuildEvalContext(andBuildEvalContextWithOpts): replace the flatshared = cty.ObjectVal{...}entry with a nesteddata = cty.ObjectVal{ internal = cty.ObjectVal{ <name> = cty.ObjectVal{ value = <current value>, type = <type cty representation> } } }.data.internal.cycle_count.valuework via standard cty traversal — no special parsing needed.data "http"), they slot in as additional keys underdatawith the samevalue-bearing shape.Step 4 —
writeblock (replacesshared_writes)HCL grammar constraint: attribute LHSs must be a single bareword identifier;
data.internal.x.value = exprwill not parse. Use a block-per-write shape — matches Terraform'sprovisionerblock pattern. The block is singular (write) because each block updates exactly one target.Target:
WriteSpecin workflow/schema.go:Writes []WriteSpec hcl:"write,block"toOutcomeSpec.CompiledOutcome.SharedWrites(map[string]string) withWrites []CompiledWrite:targetmust be a four-segment traversaldata.<kind>.<name>.valuewhose<kind>.<name>resolves to a declared data block. Anything else is a compile error with a clear message.valuereferences are validated the same way today'sshared_writeskeys are — must reference an output key that exists in the outcome's projected output (ifoutput = { ... }is declared) or the adapter's output schema (if no projection).output = { ... }, never raw adapter output.shared_writes = { ... }in an outcome and emitshared_writes has been replaced by per-target write blocks: write { target = data.internal.<name>.value, value = output.<key> }.Step 5 — Engine runtime
SharedVarStore→DataStore, keyed by(kind, name). The runtime state machine treats onlykind == "internal"as mutable; other kinds are read-only (locked at compile time for now, but the lock point lives here so future kinds slot in cleanly).applySharedWriteswithapplyDataWrites:CompiledOutcome.Writes.ValueExpragainst the post-projection output scope.DataStore[kind][name]under the existing per-data lock (atomic across all writes from a single outcome — same guaranteeshared_writeshad).co.Next == "_continue"andco.Next == ReturnSentinelstill work. Surface form changed (continue/returnkeywords); the compiledNextstring is unchanged.Step 6 — VSCode grammar updates
Coordinated single update to criteria-vscode-extension-v1/syntaxes/criteria-hcl.tmLanguage.json:
datablock matcher:^(data)\s+("[^"]*")\s+("[^"]*")\s*\{withkind/namecapture groups.shared_variablematcher.next =value highlighting — recognizestep.x,state.x,switch.x,wait.x,approval.x(traversals) and barereturn/continue(keywords); demote string-formnext = "..."to a legacy-error class so users see the mismatch.writeblock inside outcome withtarget/valueattributes.shared_writesfrom the outcome attribute list.Step 7 — Migration rewrites
Workflows that use
shared_variable/shared_writesand need full rewriting:examples/phase3-shared-variable/main.hclexamples/llm-pack/07-shared-variable/main.hclexamples/archived/workstream_review_loop/**/*.hcl(heavy user).criteria/workflows/develop/main.hcl.criteria/workflows/pr_review/main.hclproposed_hcl.hclWorkflows that need
nextmigrated (string → traversal):.hclfiles underexamples/,.criteria/workflows/. Mechanical sed fornext = "x"→next = step.x/state.xdriven by graph context;next = "return"→next = return;next = "_continue"→next = continue. Spot-verify by compile.Consider renaming
examples/phase3-shared-variable/toexamples/phase3-data/for consistency.Step 8 — Tests
shared_variableblock,shared_writesattribute, and string-formnext— assert that each migration hint appears.data "internal" "x" { type = number, value = 0 }compiles; eval context exposesdata.internal.x.value.write { target = data.internal.x.value, value = ... }applies at runtime; concurrent writes use the same atomic lock semantics today'sshared_writesdoes.next = step.foo,next = state.done,next = return,next = continueall resolve to the correct compiled target.next = "step.foo"(quoted) is rejected with the migration message.write { target = data.unknown_kind.x.value, ... }is rejected withunsupported data kind.examples/phase3-shared-variable/main.hcl(rewritten asdata "internal" {}) — runs through with writes applied across steps..criteria/workflows/develop/main.hcl— exercises data reads in switchmatchand writes in outcomes. Confirm runtime semantics are byte-equivalent to the pre-migration run.rg 'shared_variable|shared_writes|next = "[^"]+"' workflow/ examples/ .criteria/returns zero hits in.hclfiles.Out of scope
datakinds beyondinternal. The compile and eval paths are designed to make addingdata "http",data "remote_state", etc. straightforward; the actual integrations are future work.type(currentshared_writesdoesn't type-check writes against the variable's declared type either; preserve parity).Reuse pointers
resolveNextAttr— extend it; don't rewrite.DataStore.Writesinstead ofSharedWrites.Behavior change
User-facing surface: every workflow file using
shared_variableor any outcome with a string-formnextchanges shape.Runtime semantics: unchanged. Same atomicity guarantees on writes, same iteration semantics for
return/continue, same eval order for outcome projections vs writes.Future extensibility:
data "<kind>" "<name>"is the extension point. The legacyshared_variableandshared_writesconstructs are gone permanently.Tests required
go vet ./...clean.criteria run examples/phase3-shared-variable/main.hcl(rewritten) executes successfully.Implementation Notes
Checklist
nexttraversalsOutcomeSpec.Nextchanged fromstringtohcl.ExpressionresolveNextAttrextended to accept barereturn/continuekeywordscompileOutcomeBlockwiresresolveNextAttrfor every outcomenext = "..."rejected with migration messagedata "internal" "name"blockDataSpec,DataNode,DataRefadded to schemaFSMGraph.SharedVariablesreplaced withDatamap keyed by(kind, name)workflow/compile_data.gocreated; onlykind = "internal"supportedworkflow/compile_shared_variables.goand its test deleteddatanamespaceBuildEvalContextWithOptsemits nesteddata = { internal = { name = { value = ..., type = ... } } }writeblock (replacesshared_writes)WriteSpecandCompiledWriteadded to schemaOutcomeSpec.WritesreplacesCompiledOutcome.SharedWritescompileWritesvalidates 4-segmentdata.<kind>.<name>.valuetarget traversalWritesshared_writesrejected with migration messageSharedVarStorerenamed toDataStorekeyed by(kind, name)applyDataWritesreplacesapplySharedWriteswith same atomicity guarantee_continue,ReturnSentinel).hclfiles underexamples/,.criteria/workflows/migratednext = "..."→ traversal syntax (step.foo,state.done,return,continue)shared_variable→data "internal"blocksshared_writes→write { target = ..., value = ... }blocksshared.name→data.internal.name.valueshared_variable,shared_writes, string-formnextdatacompilation,writeblock resolution, traversalnextgo test ./...passgo vet ./...cleanmake validatepasses on all examples.hclfilesOpportunistic Fixes
runtimeOnlyNamespacesinworkflow/compile_fold.go: replaced"shared"with"data"to prevent compile-time folding ofdata.*references in outcomeoutputblocks.internal/cli/compile_dot_test.gowriteTempSubworkflowwhich generated invalid HCLnext = step."done"after migration.workflow/compile_bench_test.gowhich hadnext = step.donein Go string context after migration script.proposed_hcl.hclto newdata "internal"and traversalnextsyntax (was missed in original migration sweep).tools/spec-gen/render.go,internal/engine/node_step.go,internal/engine/parallel_iteration.go,internal/engine/while_iteration.go,workflow/compile_outputs.go,workflow/compile_validation.go,workflow/compile_adapters.go, andworkflow/compile_steps_iteration.goto usedata/writeterminology instead ofshared_variable/shared_writes.examples/phase3-shared-variable/main.hclandexamples/llm-pack/07-shared-variable/main.hcl: corrected incorrectly migratedwriteblocks that referenced non-existent adapter outputs (output.next_status,output.next_value) back to literal string values.docs/llm/07-shared-variable.md: updated the embedded HCL example to match the correctedexamples/llm-pack/07-shared-variable/main.hcl, and replaced staleshared_variable/shared_writesterminology withdata/writeterminology throughout the text and cross-references.SharedVariableSpecandSharedVariableNodestructs fromworkflow/schema.go(superseded byDataSpec/DataNode).Reviewer Notes
datablock shape uses two labels (kind,name) matching Terraform'sdataresource pattern.kind = "internal"is supported at compile time; other kinds produce a clear error message.DataStoretreats non-"internal" kinds as read-only (enforced at compile time currently).shared_variableblocks are rejected at parse time byrejectLegacySharedVariableBlockbefore type-string checks would fire, so theTestLegacyReject_TypeString_QuotedSharedVartest was updated to testdatablocks instead.criteria-vscode-extension-v1repository.Security Checks
DataStore.SetBatchpreserves existing atomicity semantics (all writes from a single outcome applied together).resolveNextAttrrejects string-literalnextvalues to prevent accidental misinterpretation of user input.writeblock targets ensures only declareddatablocks can be mutated.Reviewer Notes
Review 2026-05-27 — changes-requested
Summary
The core implementation of WS02 is functionally correct: all 8 plan steps are implemented,
make test,make validate,make build,make lint-go,make lint-imports,go vet ./...,make spec-check, andmake validate-self-workflowsall pass, and the final.hclgrep sweep is clean. However, this review found 4 stale comments in production code, 3 significant documentation gaps, and 5 missing test cases. The stale comments and doc gaps are required remediations because they leave incorrect terminology in the codebase and user-facing documentation. The test gaps are blockers because the plan's Step 8 explicitly requires them and they represent untested compile-time validation paths.Plan Adherence
OutcomeSpec.Nextishcl.Expression;resolveNextAttrhandles barereturn/continueand two-segment traversals;compileOutcomeBlockwiresresolveNextAttr; string-literalnext = "..."rejected with migration message.DataSpec,DataNode,DataRefadded;FSMGraph.Datamap keyed by (kind, name);compile_data.gocreated;compile_shared_variables.godeleted.BuildEvalContextWithOptsandSeedDataSnapshotemit nesteddata = { internal = { ... } }structure.WriteSpec,CompiledWriteadded;OutcomeSpec.WritesreplacesSharedWrites;compileWritesvalidates 4-segment target; aggregate-iteration rule preserved;shared_writesrejected.DataStorereplacesSharedVarStore;applyDataWritesreplacesapplySharedWrites; sentinel handling unchanged..hclfiles migrated; final grep sweep clean forshared_variable,shared_writes, and string-formnext = "...".shared_variable,shared_writes, and string-formnext. Engine write integration tests cover 8 scenarios. Data store tests cover 17 scenarios. Gaps documented below.Required Remediations
[nit] Stale comment:
internal/engine/parallel_iteration.go:450— Comment saysSharedVarStore; should sayDataStore.DataStoreinstead ofSharedVarStore.[nit] Stale comment:
internal/engine/while_iteration.go:14— Comment saysshared.*; should saydata.*.data.*instead ofshared.*.[nit] Stale comment:
workflow/schema.go:152— Comment saysshared.*; should saydata.*.data.*instead ofshared.*.[nit] Stale comment:
workflow/eval.go:86— Comment saysshared; should saydata.datainstead ofshared.[nit] Stale comment:
workflow/compile_fold.go:26— Comment saysshared; should saydata.datainstead ofshared.[blocker] Missing test: bare
next = returnkeyword — The plan adds barereturnas a new surface form, but no test verifiesnext = returncompiles toReturnSentinel. The existingTestCompileOutcome_NextIsReturntestsnext = step.return(two-segment traversal), which is the pre-WS02 form, not the new bare keyword.TestCompileOutcome_NextIsBareReturnthat compilesoutcome "success" { next = return }and assertsco.Next == ReturnSentinel.[blocker] Missing test: unsupported data kind — Plan Step 8 specifies: "Negative:
write { target = data.unknown_kind.x.value, ... }is rejected withunsupported data kind."compile_data.go:30-35rejectskind != "internal"but no test exercises this path.data "http" "x" { type = string }and asserts the diagnostic contains"unsupported data kind".[blocker] Missing test: write target not declared —
compile_data.go:resolveWriteTargetemitstarget data "<kind>" "<name>" is not declaredwhen the referenced data block doesn't exist, but no test exercises this compile-time validation.write { target = data.internal.nonexistent.value, value = "x" }and assert the diagnostic contains"is not declared".[blocker] Missing test: write target malformed traversal —
compile_data.go:parseWriteTargetTraversalrejects traversals that are not exactly 4 segmentsdata.<kind>.<name>.value, but no test exercises this path.write { target = data.internal.x, value = "y" }(3 segments) and assert the diagnostic contains"target must be a traversal of the form data.<kind>.<name>.value".[blocker] Missing test: data block name collision —
compile_data.go:checkDataNameCollisionschecks against variables, locals, and duplicate data blocks, but no compile test exercises these validation paths.[blocker] Documentation:
docs/workflow.mdnot updated — Contains 19 references toshared_variable,shared_writes, andshared.*, plus 52 occurrences of string-formnext = "...". This is the primary user-facing reference document.shared_variable→data "internal", allshared_writes→write { }blocks, allshared.name→data.internal.name.value, allnext = "x"→ traversal form.[blocker] Documentation:
docs/LANGUAGE-SPEC.mdhand-written sections not updated — The auto-generated block tables were updated bymake spec-check, but the hand-written EBNF grammar (line 22, 35) and prose (lines 302, 414, 466) still referenceshared_variable,shared_writes,shared.*, and string-formnext = "...".data_block, prose updated todata "internal"/write { }/data.*terminology, string-formnextexamples updated to traversal syntax.[nit] Documentation:
docs/llm/04-iteration-parallel.md:61— Still referencesshared_variablein the "Common pitfalls" section.shared_variablewithdata "internal"or appropriatedataterminology.Test Intent Assessment
Strong areas:
outcome_shared_writes_test.go) are well-structured: they test write application, read-back, output-key missing, type mismatch, typed projection, initial value visibility, per-iteration, and aggregate outcomes. These tests demonstrate behavioral intent and regression sensitivity.shared_variable,shared_writes, and string-formnext = "..."produce the correct migration messages.Weak areas:
compileData— kind validation, name collision, type compilation, and initial value folding are untested at the compile layer. These are new code paths introduced by WS02.parseWriteTargetTraversal,resolveWriteTarget, andvalidateWriteOutputRefshave zero test coverage. A malformed write target or undeclared data reference would silently pass if the validation code were accidentally removed.return/continuekeyword coverage —next = continueis tested in iteration contexts (which is where it's used), but barenext = returnhas no test. The testTestCompileOutcome_NextIsReturnusesnext = step.return(two-segment form), not the bare keyword form the plan specifies.Architecture Review Required
None. All changes are within executor scope.
Validation Performed
make test— PASS (all packages including-race)go vet ./...— cleanmake validate— PASS (all examples validated)make lint-go— PASSmake lint-imports— PASSmake build— PASSmake spec-check— PASSmake validate-self-workflows— PASSmake lint-baseline-check— PASS (22/22 entries, no new entries added)shared_variable,shared_writes, or string-formnext = "..."in.hclfiles underworkflow/,examples/,.criteria/.Review 2026-05-27 — Remediations Completed
All 13 required remediations from the first review have been addressed:
parallel_iteration.go:450✅ — FixedSharedVarStore→DataStore.while_iteration.go:14✅ — Fixedshared.*→data.*.workflow/schema.go:152✅ — Fixedshared.*→data.*.workflow/eval.go:86✅ — Fixedshared→data.workflow/compile_fold.go:26✅ — Fixedshared→data.next = returnkeyword ✅ — AddedTestCompileOutcome_NextIsBareReturntoworkflow/compile_outcomes_test.go.TestCompileData_UnsupportedKindtoworkflow/compile_data_test.go.TestCompileWrite_TargetNotDeclaredtoworkflow/compile_data_test.go.TestCompileWrite_MalformedTraversaltoworkflow/compile_data_test.go.TestCompileData_NameCollision_Variable,TestCompileData_NameCollision_Local, andTestCompileData_NameCollision_Duplicatetoworkflow/compile_data_test.go.docs/workflow.mdnot updated ✅ — All 19shared_variable/shared_writes/shared.*references updated; all 52 string-formnext = "..."replaced with traversal syntax (step.*,state.*,return,continue).shared_writesmap syntax replaced withwrite { target = data.internal.<name>.value, value = output.<key> }blocks.docs/LANGUAGE-SPEC.mdhand-written sections not updated ✅ — EBNF grammar updated (data_block,write_block, traversalnext); prose sections at lines 302, 414, 466 updated todata "internal"/write { }/data.*terminology; all string-formnextexamples updated to traversal syntax.docs/llm/04-iteration-parallel.md:61✅ — Replacedshared_variablewithdata "internal"in the "Common pitfalls" section.Test coverage now includes:
compileData: kind validation, name collision (variable/local/duplicate), type compilation.parseWriteTargetTraversal,resolveWriteTarget(malformed target, undeclared data reference).returnkeyword coverage:TestCompileOutcome_NextIsBareReturnconfirmsnext = returncompiles toReturnSentinel.Review 2026-05-27-02 — approved
Summary
All 13 remediations from the first review have been verified. The 5 stale comments are fixed, the 5 missing tests are present and covering the specified compile-time validation paths, and the 3 documentation files are fully updated to use
data "internal"/write { }/ traversalnextterminology. All builds, tests, lints, and validations pass. The HCL grep sweep is clean. The implementation fully satisfies all 8 plan steps and all exit criteria.Plan Adherence
OutcomeSpec.Nextishcl.Expression;resolveNextAttrhandles barereturn/continueand traversals;TestCompileOutcome_NextIsBareReturnconfirms bare keyword compiles toReturnSentinel.DataSpec/DataNode/DataRefpresent;FSMGraph.Datakeyed by (kind, name);compile_data.goexists;compile_shared_variables.godeleted;TestCompileData_UnsupportedKindandTestCompileData_NameCollision_*cover compile paths.BuildEvalContextWithOptsandSeedDataSnapshotemit nesteddata.internal.*structure.WriteSpec/CompiledWritepresent;OutcomeSpec.WritesreplacesSharedWrites;TestCompileWrite_TargetNotDeclaredandTestCompileWrite_MalformedTraversalcover validation.DataStorereplacesSharedVarStore;applyDataWritesreplacesapplySharedWrites; sentinel handling unchanged.shared_variable,shared_writes, and string-formnext = "...".Required Remediations
None. All prior findings resolved.
Test Intent Assessment
Strong areas (unchanged from prior review, now supplemented):
TestCompileData_UnsupportedKind,TestCompileData_NameCollision_*,TestCompileWrite_TargetNotDeclared,TestCompileWrite_MalformedTraversal,TestCompileOutcome_NextIsBareReturn) directly test behavioral intent and regression resistance at compile-time validation boundaries.Assessment: Test coverage is now adequate for all WS02-introduced code paths.
Validation Performed
make test— PASS (all packages including-race)go vet ./...— cleanmake validate— PASS (all examples validated)make lint-go— PASSmake lint-imports— PASSmake build— PASSmake spec-check— PASSmake validate-self-workflows— PASSmake lint-baseline-check— PASS (22/22, no new entries)shared_variable,shared_writes, string-formnext = "..."in.hclfilesDataStore/data.*/dataterminology