Skip to content

✨ feat(flow): Graph.build — plan-time expansion of flow bodies - #184

Merged
roninjin10 merged 4 commits into
mainfrom
flow-graph-build
Aug 13, 2026
Merged

✨ feat(flow): Graph.build — plan-time expansion of flow bodies#184
roninjin10 merged 4 commits into
mainfrom
flow-graph-build

Conversation

@roninjin10

@roninjin10 roninjin10 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

The analysis phase: Graph.build(flow, payload) turns a bodied flow into keyed plan input.

  • Bodies evaluate against their real payload; step results are strict Planned placeholders — computation throws GraphBuildError with the node path (PR1's traps, now exercised end-to-end).
  • Branch expands both arms as static topology with the digested predicate on the branch node's material — the plan shows the whole choice.
  • Inline .call() splices a bodied callee's subgraph (capability intersection); body-less flow calls and activity calls are leaf drafts. Recursive .call() throws recursion_requires_boundary directing to .to()/.child().
  • Effects/capabilities/placement read from the PR3 annotation keys into KeyMaterial + NodeEffects.
  • Output drafts feed Plan.compile/Plan.append directly — round-trip test proves drafts → keyed Plan with correct dependsOn from Ref/Pending inputs, and that editing a map function's source re-keys exactly the downstream cone.

Two OPEN QUESTION (2026-08-11) markers (deliberate): default boundary mode for nodes without declared effects; nested-branch planned-subject binding.

Depends-on: #183

Verification

  • npm run check clean for @smthrs/flow and @smthrs/plan; lint clean for both
  • New Graph.test.ts (20) + extended Node.test.ts (15); full suites: flow 15 files / 141 tests, plan 6 files — both at the enforced 100% coverage (independently re-verified)

Decisions taken (were open questions — review these)

  1. Default effects for a node that declares none: {reads: [], writes: [], boundaryMode: "expected"} (Graph.ts:200). Rationale: a node that declared nothing claims no path either way; "expected" means runtime deviation is surfaced, not fatal. The "hard" alternative would fail any run whose node touches an undeclared path — the right strictness only after authors actually declare effects routinely. Revisit when effect declarations are pervasive.
  2. Nested branch subjects: innermost binding wins (Graph.ts:674). The arm subject is recorded under one Node.branchSubject name, so an arm nested in another branch's arm sees the inner subject — exactly lexical shadowing semantics from ordinary code. Per-branch subject names in the AST were deliberately not added; they buy disambiguation nobody has needed at the cost of AST surface.

…Error

The authoring half of the plan: seven closure-free node variants (Succeed,
All, Map, AndThen, FlowCall, ActivityCall, Branch) with digested deferred
functions in out-of-band tables, the Planned<T> branded placeholder whose
proxy throws GraphBuildError on any computation (toPrimitive, valueOf,
toString, toJSON, application), and the GraphBuildError vocabulary.

Docs: docs/specs/Concepts/Unified Flow Authoring.md
(cherry picked from commit 64624ac)
Activity.make gains a string-first overload: a named, schema-typed atom
(payload/success/error/tier/idempotencyKey) whose implementation attaches
separately via .toLayer(execute) through the existing registration and
activity machinery, and whose .call(payload) records an ActivityCall node
for plans. The inline options form is unchanged.

Docs: docs/specs/Concepts/Unified Flow Authoring.md
(cherry picked from commit b883814)
…otations

Flow.make accepts an optional plan-time body (required once the migration
completes), Flow.prototype.call records a FlowCall node, the trampoline
outcome vocabulary lands as pure data (Flow.done / flow.to / Flow.park over
the existing Waiting reasons), and Capabilities / EffectsDeclaration /
Placement join the annotation keys.

Docs: docs/specs/Concepts/Unified Flow Authoring.md
Docs: docs/specs/Concepts/Trampoline Loops.md
(cherry picked from commit ad7df62)
@roninjin10

Copy link
Copy Markdown
Contributor Author

Review round 1 complete (sol + fable, independent). Findings for this PR were fixed and folded into its commit; the full ledger (15 fixes, 6 resolved-as-designed) lives in the stack's review notes. All 14 branches rebased and re-verified: whole-repo check green, all package suites at 100% coverage, rebuilt tip byte-identical to the verified fix tree.

Evaluates a body against its real payload with strict Planned placeholders
for step results, expands branch arms as static topology, splices inline
flow calls with capability intersection, rejects recursive calls with
recursion_requires_boundary, reads effects/capabilities/placement from the
authoring annotations, and emits NodeDrafts that Plan.compile keys directly.

Docs: docs/specs/Concepts/Unified Flow Authoring.md
(cherry picked from commit f8bf150)
@roninjin10

Copy link
Copy Markdown
Contributor Author

Swap pass complete — review loop closed. Both seats have now reviewed every PR. Swap findings (6: nested-catch subject scoping, foreign-flow wait tokens, two __proto__ cloner recurrences, drafts-field refusal, re-drive test + lineage-id doc) fixed and folded into owning PRs; tail rebuilt tree-identical; whole-repo check + all suites green at 100% coverage.

@roninjin10
roninjin10 changed the base branch from flow-body-values to main August 13, 2026 01:13
@roninjin10
roninjin10 merged commit ce958cc into main Aug 13, 2026
1 check failed
@roninjin10
roninjin10 deleted the flow-graph-build branch August 13, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant