Skip to content

Workflow definition graph: static DAG with live run overlay#13

Merged
thedumbtechguy merged 21 commits into
mainfrom
feat/workflow-definition-dag
Jul 1, 2026
Merged

Workflow definition graph: static DAG with live run overlay#13
thedumbtechguy merged 21 commits into
mainfrom
feat/workflow-definition-dag

Conversation

@thedumbtechguy

Copy link
Copy Markdown
Contributor

Summary

Adds a per-run definition graph to the ChronoForge dashboard: a static DAG of the durable steps a workflow will run — parsed from perform with Prism, never executed, never touching the DB — with the run's live status overlaid on each node.

Core (chrono_forge)

  • ChronoForge::Definition / Node / Edge — rendering-agnostic graph value objects (to_h → JSON-safe).
  • ChronoForge::DefinitionAnalyzer — Prism static analysis of a workflow's own perform into a conservative conditional DAG: durably_execute / wait / wait_until / continue_if / branch / merge_branches / durably_repeat, with if/unless/case → guarded edges, early return → dashed terminal exit, same-class helper tracing, and loops/computed names → a dynamic node + warning rather than a confident-but-wrong graph.

Dashboard (chrono_forge-dashboard)

  • DefinitionOverlay paints each node with the run's status from execution_logs (done / in-progress / failed / not-reached, plus fan-out and repeat aggregates and unmapped steps).
  • CytoscapeGraph emits structured elements; the page renders them client-side with Cytoscape + dagre (vendored, ~670 KB) — pan/zoom, tap a node/edge to inspect its step name / guard. No text-DSL, so labels/guards need no escaping.
  • New GET workflows/:id/definition page, linked from the workflow detail page; degrades to a warning (never a 500) for unknown/unanalyzable classes.
  • Demo KitchenSinkWorkflow given a real analyzable perform so the preview renders a mixed-status graph; screenshot + README docs added.

Built TDD across a 9-task plan; then hardened against the real Achieve ScheduledPaymentRecurrenceWorkflow, which surfaced several defects the fixtures missed (all fixed with regression tests): wait's name is the 2nd positional; merge status must come from its own log; unquoted guard labels; and — critically — analyzing the workflow's own perform rather than the prepended ChronoForge::Executor#perform.

Test Plan

🤖 Generated with Claude Code

Prism-based DefinitionAnalyzer in core produces a rendering-agnostic conditional
DAG; dashboard overlays a run's execution_logs and renders via Mermaid on a new
per-run definition page. Reference: durable_flow DefinitionAnalyzer.

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq
9 TDD tasks: Definition value objects + Prism dep; analyzer (linear, conditionals,
fan-out/merge, repeat/helper-tracing/loop-warnings); dashboard overlay, Mermaid
renderer, and per-run definition page.

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq
Model if/unless/case around durable calls as :conditional edges carrying a
guard label; thread multiple exit ids so the post-conditional step is reachable
from both the body exit and the skip path. continue_if's false path emits a
:terminal edge to a virtual halt sink (mirrors the virtual start id).

Also fixes latent perform-resolution bug: bind to the DefNode on the method's
source line so multiple workflow classes in one file no longer collide.

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq
…d-positional); merge status from own log; drop dead controller branch

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq
… of Mermaid

- Structured elements (JSON) consumed directly — no text-DSL escaping (kills the
  guard-label bug class); pan/zoom + tap-to-inspect; ~670KB vs 3.5MB vendored.
- Fix: analyze the workflow's OWN perform, not the prepended ChronoForge::Executor
  #perform (every real workflow was being analyzed as the executor).
@thedumbtechguy thedumbtechguy merged commit 98cd9b5 into main Jul 1, 2026
2 checks passed
thedumbtechguy added a commit that referenced this pull request Jul 2, 2026
…un overlay (#13)

* docs: design spec for static workflow definition DAG (future timeline)

Prism-based DefinitionAnalyzer in core produces a rendering-agnostic conditional
DAG; dashboard overlays a run's execution_logs and renders via Mermaid on a new
per-run definition page. Reference: durable_flow DefinitionAnalyzer.

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* docs: implementation plan for workflow definition DAG

9 TDD tasks: Definition value objects + Prism dep; analyzer (linear, conditionals,
fan-out/merge, repeat/helper-tracing/loop-warnings); dashboard overlay, Mermaid
renderer, and per-run definition page.

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(definition): graph value objects + prism dependency

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(analyzer): linear durable-step extraction via Prism

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(analyzer): guarded conditional edges and continue_if terminal

Model if/unless/case around durable calls as :conditional edges carrying a
guard label; thread multiple exit ids so the post-conditional step is reachable
from both the body exit and the skip path. continue_if's false path emits a
:terminal edge to a virtual halt sink (mirrors the virtual start id).

Also fixes latent perform-resolution bug: bind to the DefNode on the method's
source line so multiple workflow classes in one file no longer collide.

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(analyzer): branch fan-out nodes and merge join edges

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(analyzer): repeat node, same-class helper tracing, loop warnings

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(dashboard): overlay execution_logs onto the definition graph

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(dashboard): render statused definition graph to Mermaid

* fix(analyzer): correct unless/nested/elsif guards, class-scoped helper tracing, dynamic-merge join, guard sanitization

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* feat(dashboard): per-run workflow definition DAG page with Mermaid

* chore: lint + silence -w warnings; document definition graph page

* fix(analyzer): resolve step names per real DSL arg positions (wait 2nd-positional); merge status from own log; drop dead controller branch

Claude-Session: https://claude.ai/code/session_01DPLkBrXrXdjXdpoDyfdyqq

* fix(dashboard): quote Mermaid edge labels so guards with () and < render

* feat(analyzer): model early returns as terminal edges; bind dynamic nodes by prefix (no double-report)

* feat(dashboard): render definition graph with Cytoscape+dagre instead of Mermaid

- Structured elements (JSON) consumed directly — no text-DSL escaping (kills the
  guard-label bug class); pan/zoom + tap-to-inspect; ~670KB vs 3.5MB vendored.
- Fix: analyze the workflow's OWN perform, not the prepended ChronoForge::Executor
  #perform (every real workflow was being analyzed as the executor).

* style(dashboard): polish definition graph — aligned cards, legend, taxi routing, spacing

* docs: drop stale Mermaid mention in guard-truncation comment

* docs: document definition graph in READMEs + screenshot; give demo KitchenSinkWorkflow a real analyzable perform

* style: lint-clean the definition test files (scope deliberate-syntax fixtures)

* docs: add real-world scheduled-payment definition graph screenshot
thedumbtechguy added a commit that referenced this pull request Jul 2, 2026
…UX polish

Follow-up fixes and polish for the definition graph (#13).

Analyzer (lib/chrono_forge):
- follow durable calls through assignments, &&/||, and case/in so a step one
  expression deep is no longer silently dropped
- treat merge_branches with a non-literal branch name as a dynamic node instead
  of a merge with a nil step_name that never binds
- warn when a perform has no durable steps (empty/unanalyzable) so the page shows
  a readable note rather than a bare unmapped node

Overlay:
- a dynamic prefix node no longer rebinds a log a static node already owns
- count repetitions by exact string prefix, not SQL LIKE (an underscore in a step
  name was treated as a wildcard and over-counted)
- map pending logs to :active (the enum is only pending/completed/failed)

Rendering:
- HTML-escape labels, step names, and guards before writing them into the
  tap-to-inspect detail panel (DOM XSS)
- surface the repeat count (xN) and fan-out child tallies the overlay computes

Dashboard:
- the definition page opts out of the auto-refresh region swap (which wiped the
  Cytoscape canvas) and hides the now-inert refresh control

Docs/fixtures:
- README updates for the above
- give OrderProcessingWorkflow a real branch/merge body and add a
  ScheduledPaymentRecurrenceWorkflow demo fixture with distinct per-branch step
  names; regenerate the scheduled-payment screenshot so one branch reads as taken
  and the others dimmed
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