Skip to content

Add validated flow helper proof of concept#1518

Open
abishekgiri wants to merge 10 commits into
iii-hq:mainfrom
abishekgiri:feature/validated-flow-helper-poc
Open

Add validated flow helper proof of concept#1518
abishekgiri wants to merge 10 commits into
iii-hq:mainfrom
abishekgiri:feature/validated-flow-helper-poc

Conversation

@abishekgiri
Copy link
Copy Markdown
Contributor

@abishekgiri abishekgiri commented Apr 20, 2026

What

  • add a trace-backed helper for validating real flow paths from in-memory spans
  • add an end-to-end proof of concept for:
    • HTTP trigger
    • iii::durable::publish
    • queue trigger
    • state::set
    • state trigger
  • add a second end-to-end proof of concept for:
    • HTTP trigger
    • stream::set
    • stream trigger
  • generate renderable flow graph assets only after those runtime paths are actually observed
  • serialize access to shared in-memory tracing storage so validated-flow tests remain stable as the helper expands

Why

  • this follows the direction in #1468 to validate flows from real runtime behavior instead of relying on hand-maintained metadata
  • it proves the helper can derive assets from more than one real connection type while staying within the dev/testing-tool boundary
  • it keeps the scope intentionally narrow so the model can be reviewed before broader engine or console work

Notes

  • this is still a proof of concept, not a full flows system
  • scope is limited to two concrete paths using built-in workers:
    • HTTP -> queue -> state
    • HTTP -> stream trigger
  • no console integration or public engine flow API is included yet
  • validation used:
    • cargo fmt --all
    • cargo test -p iii --test validated_flow_asset_e2e -- --nocapture

  • I am licensing the entirety of this PR under Apache 2 and have all necessary rights to the code I am contributing.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 20, 2026

@abishekgiri is attempting to deploy a commit to the motia Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a public test helper module that installs in-memory OpenTelemetry tracing, builds deterministic flow graph assets from captured spans, and provides path specs for HTTP→queue→state and HTTP→stream flows; also adds end-to-end Tokio tests that exercise both flows and assert exact serialized graph assets.

Changes

Cohort / File(s) Summary
Flow Test Helpers
engine/tests/common/flow_helpers.rs, engine/tests/common/mod.rs
New public test helper module and mod export. Installs OTEL tracing with a memory exporter (ensure_flow_test_tracing() / FlowTracingGuard), exposes serializable graph types (FlowGraphNode, FlowGraphEdge, ValidatedFlowGraphAsset) and two path specs: HttpQueueStatePathSpec (queue→state) and HttpStreamPathSpec (stream). Each spec provides async wait_for_validated_asset() which polls captured spans, validates event JSON, traverses spans by parent IDs and exact span names/attributes, and constructs the expected node/edge graph.
Flow Asset E2E Tests
engine/tests/validated_flow_asset_e2e.rs
New Tokio serial e2e tests for both queue-state and stream flows. Each test starts real workers (HTTP + durable queue + state or HTTP + stream), registers functions/triggers, performs an HTTP request, awaits observed events, then builds path specs and asserts exact serialized graph JSON matches expected nodes/edges. Tests rely on tracing bootstrap and memory span capture.

Sequence Diagram

sequenceDiagram
    actor Client
    participant HTTP as HTTP Worker
    participant Queue as Queue Worker
    participant State as State Worker
    participant Tracing as OTEL Tracer
    participant Observer as State/Stream Observer

    Client->>HTTP: POST /path {payload}
    HTTP->>Tracing: create "METHOD /path" span
    HTTP->>Queue: publish/enqueue (topic or stream write)
    Queue->>Tracing: record producer/enqueue span
    Queue->>Queue: poll & consume (durable) / trigger stream handlers
    Queue->>Tracing: record consumer/trigger span
    Queue->>State: state::set(scope,key,value) / write stream item
    State->>Tracing: record state write / stream set span
    State->>Observer: invoke state/stream observer
    Observer->>Tracing: record observer span
    Tracing->>Tracing: store spans in memory exporter for verification
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐰 I hopped through spans with twitchy nose and cheer,
HTTP nudged Queue, then State listened near.
I stitched nodes, edges — a graph so neat,
Traces lined up, each step complete.
A rabbit's hop made the flow appear.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a validated flow helper proof of concept with concrete examples.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description follows the required template structure with clear What, Why, and Notes sections, includes complete justification and implementation details, and the Apache 2 licensing checkbox is checked.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/tests/common/flow_helpers.rs`:
- Around line 233-237: The current code creates a detached InMemorySpanExporter
when get_span_storage() returns None, which hides tracing setup failures and
causes wait_for_validated_asset to poll an empty store; instead, fail fast:
replace the fallback InMemorySpanExporter::new(…) branch with an explicit panic
or test failure (e.g., panic!("missing span storage: tracing subscriber not
installed for tests")) so the test immediately reports the misconfigured tracing
subscriber; locate the check using get_span_storage() in flow_helpers.rs and
update it to abort rather than instantiate InMemorySpanExporter::new, and ensure
any tests that rely on wait_for_validated_asset are adjusted to set up the
subscriber properly.

In `@engine/tests/validated_flow_asset_e2e.rs`:
- Around line 50-58: The readiness helper wait_for_route currently silently
returns if the route never becomes reachable after 3 seconds; change
wait_for_route to return a Result<(), anyhow::Error> (or TestError) instead of
unit, await client.get(url).send().await inside the loop as before, and on
timeout return Err(anyhow::anyhow!("timeout waiting for route {}", url)) (or use
context/bail) so the test fails immediately with a clear message; update callers
to .await? and propagate the error or unwrap in the test.
- Around line 22-27: The test currently reserves then drops an ephemeral port in
reserve_local_port(), which is a TOCTOU; instead have the worker bind the port
itself (use port 0) or pass an already-bound listener into the worker to avoid
releasing the socket before HttpWorker::initialize is called. Change the test to
stop calling reserve_local_port(), either (a) call HttpWorker::initialize so it
binds to "127.0.0.1:0" and then query the worker for its bound address/port, or
(b) create and keep a StdTcpListener bound to "127.0.0.1:0" and pass that
listener/socket into HttpWorker::initialize (or add an overload that accepts a
listener) so the socket remains owned until the worker takes it. Ensure
references to reserve_local_port are removed and update test assertions to read
the real bound port from the worker or listener.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1b18f5a0-face-421a-9c71-1a3cd6a5817e

📥 Commits

Reviewing files that changed from the base of the PR and between 2023491 and cafd6a6.

📒 Files selected for processing (3)
  • engine/tests/common/flow_helpers.rs
  • engine/tests/common/mod.rs
  • engine/tests/validated_flow_asset_e2e.rs

Comment thread engine/tests/common/flow_helpers.rs
Comment thread engine/tests/validated_flow_asset_e2e.rs
Comment thread engine/tests/validated_flow_asset_e2e.rs
@abishekgiri
Copy link
Copy Markdown
Contributor Author

Following the plan from #1468, the next step after this POC is to keep the scope narrow and extend the validated-helper pattern to a small number of additional connection types while continuing to derive flow assets from real runtime behavior.

I do not plan to broaden this into console integration or a public engine flows API yet. The goal is to prove the helper shape across a few real execution paths first, then build outward from there once the validated model feels solid.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
engine/tests/common/flow_helpers.rs (1)

60-76: Optional: simplify the timeout/error-capture loop.

The Err(err) if Instant::now() < deadline arm plus the shadowing let last_error = last_error.unwrap_or(err); on line 71 works, but the control flow is a bit hard to follow and the successful-path last_error is written but never read. A tokio::time::timeout wrapper around a simple retry loop reads more linearly and preserves the most recent error automatically.

Sketch
-        let deadline = Instant::now() + timeout;
-        let mut last_error: Option<anyhow::Error> = None;
-
-        loop {
-            match self.try_build_asset(state_event) {
-                Ok(asset) => return Ok(asset),
-                Err(err) if Instant::now() < deadline => {
-                    last_error = Some(err);
-                    sleep(Duration::from_millis(25)).await;
-                }
-                Err(err) => {
-                    let last_error = last_error.unwrap_or(err);
-                    return Err(last_error.context("timed out waiting for validated flow asset"));
-                }
-            }
-        }
+        let deadline = Instant::now() + timeout;
+        let mut last_error = anyhow!("validated flow asset was never observed");
+        while Instant::now() < deadline {
+            match self.try_build_asset(state_event) {
+                Ok(asset) => return Ok(asset),
+                Err(err) => {
+                    last_error = err;
+                    sleep(Duration::from_millis(25)).await;
+                }
+            }
+        }
+        Err(last_error.context("timed out waiting for validated flow asset"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/tests/common/flow_helpers.rs` around lines 60 - 76, The retry loop
around try_build_asset uses manual Instant checks and last_error handling;
replace it with tokio::time::timeout(timeout, async { ... }) to simplify control
flow: inside the timeout future loop, repeatedly call
self.try_build_asset(state_event).await, returning Ok(asset) on success and
otherwise sleep(Duration::from_millis(25)).await and retry; if the timeout
elapses, propagate the most recent error with .context("timed out waiting for
validated flow asset") so you no longer need manual Instant::now/last_error
bookkeeping or the shadowed let last_error variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/tests/common/flow_helpers.rs`:
- Around line 212-238: ensure_flow_test_tracing uses a process-global span
storage (get_span_storage) and currently clears it unguarded, which will race
when multiple flow tests run in the same test binary; fix by serializing access
inside ensure_flow_test_tracing: introduce a shared global sync primitive (e.g.,
a static Mutex or tokio::sync::Mutex) acquired at the start of
ensure_flow_test_tracing before initializing the tracer or calling
storage.clear(), and released after the test-specific setup/teardown so only one
test manipulates the global span storage at a time; alternatively, scope spans
to a test by tagging root spans with a per-test id and filtering
storage.get_spans()/try_build_asset by that id instead—apply the chosen approach
to ensure_flow_test_tracing, FLOW_TRACING_INIT, get_span_storage, storage.clear,
and the places that call storage.get_spans()/try_build_asset.

---

Nitpick comments:
In `@engine/tests/common/flow_helpers.rs`:
- Around line 60-76: The retry loop around try_build_asset uses manual Instant
checks and last_error handling; replace it with tokio::time::timeout(timeout,
async { ... }) to simplify control flow: inside the timeout future loop,
repeatedly call self.try_build_asset(state_event).await, returning Ok(asset) on
success and otherwise sleep(Duration::from_millis(25)).await and retry; if the
timeout elapses, propagate the most recent error with .context("timed out
waiting for validated flow asset") so you no longer need manual
Instant::now/last_error bookkeeping or the shadowed let last_error variable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 54d6aa35-2b8b-48f6-a222-5f5bbce4f8ab

📥 Commits

Reviewing files that changed from the base of the PR and between 1d58812 and 898dda0.

📒 Files selected for processing (2)
  • engine/tests/common/flow_helpers.rs
  • engine/tests/validated_flow_asset_e2e.rs
✅ Files skipped from review due to trivial changes (1)
  • engine/tests/validated_flow_asset_e2e.rs

Comment thread engine/tests/common/flow_helpers.rs Outdated
@anthonyiscoding
Copy link
Copy Markdown
Contributor

@abishekgiri Thanks for opening this PR! Can you address the one remaining comment from coderabbit?

@sergiofilhowz Take a look at this one and let us know what you think about it. I know we're all onboard for flows but I haven't reviewed this code myself.

@abishekgiri
Copy link
Copy Markdown
Contributor Author

abishekgiri commented Apr 21, 2026

@anthonyiscoding Addressed the remaining CodeRabbit comment in the latest push.

The flow tracing helper now serializes access to the shared in-memory span storage so future validated-flow tests do not race on storage.clear() or span reads as the helper expands. Focused validation still passes with:

  • cargo fmt --all
  • cargo test -p iii --test validated_flow_asset_e2e -- --nocapture

@abishekgiri
Copy link
Copy Markdown
Contributor Author

Expanded the validated-flow proof of concept with the next #1468 slice in the latest push.

What changed:

  • kept the original validated HTTP -> durable publish -> queue trigger -> state::set -> state trigger path
  • added a second validated HTTP -> stream::set -> stream trigger path
  • extended the helper so both paths generate deterministic graph assets from real runtime traces and observed events
  • serialized access to the shared in-memory span storage so future validated-flow tests do not interfere with each other

Validated with:

  • cargo fmt --all
  • cargo test -p iii --test validated_flow_asset_e2e -- --nocapture

@rohitg00
Copy link
Copy Markdown
Contributor

@abishekgiri hey, how's it going? Is this PR up for review for @sergiofilhowz and @andersonleal

@abishekgiri
Copy link
Copy Markdown
Contributor Author

@rohitg00 Hey, going well, thanks for checking in.

Yes, this is ready for review from the code/design side. This PR came out of the direction discussed in #1468, where we moved away from hand-maintained flow metadata as the source of truth and toward validating flow paths from actual runtime behavior.

This first PR is intentionally a proof of concept. It adds a trace-backed test helper that waits for real in-memory spans from an executed iii path, validates that the expected runtime path actually happened, and only then generates a renderable flow graph asset.

Current scope in this PR:

  • HTTP -> iii::durable::publish -> queue trigger -> state::set -> state trigger
  • HTTP -> stream::set -> stream trigger
  • generated graph asset with nodes and edges from the validated runtime path
  • no public engine API changes
  • no console integration yet
  • no new primitive or workflow DSL

The follow-up work is already split out separately: #1572 adds the pub/sub path and the reusable declaration model for Phase 1.

Most code checks are passing on this PR. I noticed the license-agreement check is still red, so I can take a look at that separately, but from the implementation side this is ready for review by @sergiofilhowz and @andersonleal.

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.

3 participants