Skip to main content

WF-33 — SDLC golden-flow demo on the Temporal engine (founder script)

The W5 demo endpoint and the MVP exit proof: watch the 5-node SDLC golden flow run end to end on the Temporal interpreter engine — streaming through the live DAG, parking on the high-severity deploy gate for a human approval, and rolling the real per-step cost up into the run and task totals.

Issue: #1943 (WF-33) · tracker #1786 (W5, MVP exit) · PRD #1783 · ADR-0023 (Temporal-NOW) · LLD #1798.

The flow (internal/goldenflow/assets/sdlc_workflow.json, 5 nodes):

architect → coder → qa → deploy_approval (approval_gate, clearance 4) → devops

architect, coder, qa, and devops are agent_action nodes (one bounded agent turn each); deploy_approval is a high-severity approval gate that a human with clearance ≥ 4 must approve before the deploy step runs. (The tracker mentions a "13-node" flow — that is a future demo enhancement, not the MVP. The MVP golden flow is these 5 nodes.)

This script is derived from the automated MVP exit proof test/integration/goldenflow/temporal_e2e_test.go (WF-33). Everything it asserts, you can watch happen live.


What this demo proves (the MVP exit invariants)

  1. The SDLC flow runs on the Temporal interpreter engine (durable execution), not the legacy coordinator.
  2. All 5 nodes reach a terminal state — the run completes.
  3. The live-DAG projection streams every transition (running → awaiting_approval → completed), lighting the console with zero polling.
  4. The deploy gate parks on a durable timer and resumes on a human approval decision routed through the real approval service (and, in the reject/expiry case, voids + fails — no deploy).
  5. The Σ cost invariant holds on the Temporal engine (the #1127 chain): Σ workflow_actions.cost_usd == workflow_runs.cost_usd == tasks.cost_usd, every figure tracing to the same llm_usage_events rows the agent turns burned (proven live at ~$0.003 for the four gpt-4o-mini turns).

0. Prerequisites on beta-dev

  • The Temporal substrate is up and the app namespace exists (see docs/runbooks/temporal-ops.md §1, §3).
  • TEMPORAL_ENABLED=true on the agent-orchestrator + context-engine (the master kill-switch, PRD #1783 §5). With it false, triggers are rejected and the flow runs on the legacy coordinator — this demo needs the Temporal path.
  • TEMPORAL_WORKER_ENABLED=true so the interpreter + activity workers poll.
  • Real agents need provider keys: either a configured vault (VAULT_PASSPHRASE) for real LLM turns, or WF_FAKE_EXECUTOR=true for deterministic keyless turns (the demo works with either; keyless is cheaper and reproducible).
  • grpcurl on your machine, pointed at the context-engine gRPC endpoint. Export the tenant auth the interceptor expects (org + a clearance-4 member for the approval); on beta-dev this is your Clerk session translated to the tenant context.

Set once for the session:

export ENGINE=beta-dev.upsquad.ai:443 # context-engine gRPC (adjust to your reachability)
export ORG=<your-org-uuid>

1. Provision the golden flow (idempotent)

If the tenant already has the golden flow (Engineering unit + architect/coder/qa/ devops agents + the SDLC workflow + governance policies), skip this. Otherwise provision it — this is the same minimal seed the WF-33 E2E uses:

# From the agent-orchestrator container (has DATABASE_URL), or point DATABASE_URL
# at beta-dev. See docs/runbooks/golden-flow-provisioning.md for the full walkthrough.
DATABASE_URL=<beta-dev-dsn> \
go run ./cmd/provision-golden-flow --org-id "$ORG"
# Prints: team=<uuid> workflow=<uuid> (v2.4 single unit) + the four agent ids +
# the org/team governance allow policies.

Capture the workflow id it prints — that is the id you trigger:

export WORKFLOW_ID=<workflow-uuid-from-the-provisioner>

2. Open the live DAG stream (watch it in real time)

In a separate terminal, start streaming the run status before you trigger, so you see every transition as it lands. StreamRunStatus emits a NodeStatusUpdate per node transition; StreamRunLogs emits the narration tail. (In the console, this is the live-DAG panel — the same data, rendered.)

You will have the run_id after step 3; open the stream then, or open the workflow-level status stream now and filter. The console does this automatically — for a CLI demo, run this the instant you have the run id from step 3:

# StreamRunStatus takes only run_id; the org is resolved from the auth interceptor.
grpcurl -d "{\"run_id\":\"$RUN_ID\"}" \
"$ENGINE" upsquad.workflow.v1.WorkflowService/StreamRunStatus

You will see the nodes light up in order: architectcoderqa go in_progresscompleted, then deploy_approval goes awaiting_approval and the run parks.


3. Trigger the SDLC flow on the Temporal path

grpcurl -d "{
\"workflow_id\": \"$WORKFLOW_ID\",
\"context\": { \"feature_request\": \"Add a Go helper returning the max of two ints, with a doc comment and a table test.\" }
}" "$ENGINE" upsquad.workflow.v1.WorkflowService/TriggerWorkflow
# → { "run_id": "<uuid>", "status": "running" }
export RUN_ID=<run-uuid-from-the-response>

Under the hood this is TriggerWorkflow → TemporalTrigger.Trigger → StartWorkflow (WF-15): a workflow_runs row is committed, a canonical Task is anchored to it, and the interpreter workflow starts on the wf-runs queue. In the Temporal Web UI (docs/runbooks/temporal-ops.md §2) you can watch the execution run:<org>:<run_id> walk its activities: BootRun, then per agent node Govern → Audit → ExecuteAgentStep → CompleteStepAndAccrue → Project.

Now open the stream from step 2 with this RUN_ID and watch architect → coder → qa complete and the flow park on deploy_approval (awaiting_approval).


4. Approve the deploy gate (the human-in-the-loop moment)

The gate is high-severity (clearance 4). Find the pending approval and approve it as a clearance-4 operator — this fires the DecisionPublisher, which SignalWorkflows the parked run and resumes it into devops.

# List the pending approval for the run (the approvals panel in the console).
grpcurl -d "{\"org_id\":\"$ORG\",\"status_filter\":\"pending\"}" \
"$ENGINE" upsquad.governance.v1.ApprovalService/ListApprovals
export APPROVAL_ID=<the-deploy_approval-row-id>

# Approve it (clearance-4 operator).
grpcurl -d "{
\"org_id\": \"$ORG\",
\"approval_id\": \"$APPROVAL_ID\",
\"decision\": \"DECISION_APPROVED\",
\"reason\": \"ship it\"
}" "$ENGINE" upsquad.governance.v1.ApprovalService/RecordDecision

Back in the status stream: deploy_approval flips to approved, devops runs and completed, and the run completes. All 5 nodes are terminal.

Rejection / expiry variant (the guardrail). Instead of approving, deny the gate (DECISION_DENIED) or simply let it time out. Because the deploy gate has no reject branch, the approval is voided and the run failsdevops never runs, so nothing ships without a human sign-off. This is the WF-21 regression the E2E's TestWF33_..._GateExpiry locks in.


5. Show the Σ cost invariant (the money proof)

Every agent turn recorded its real usage; the interpreter rolled it up. On the Temporal engine, the per-node action costs, the run total, and the task total all equal the same llm_usage_events sum:

-- Against the beta-dev app DB, scoped to your run.
SET app.org_id = '<org>';

-- Per-node action costs (workflow_actions), the run total (workflow_runs), and
-- the raw usage the turns burned — the three should reconcile.
SELECT 'Σ actions' AS k, SUM(cost_usd) FROM workflow_actions WHERE run_id = '<run>'
UNION ALL
SELECT 'run total', cost_usd FROM workflow_runs WHERE id = '<run>'
UNION ALL
SELECT 'usage sum', SUM(cost_usd) FROM llm_usage_events WHERE workflow_run_id = '<run>';

-- The task rollup (tasks.cost_usd = SUM over linked runs) equals the run total.
SELECT t.cost_usd
FROM tasks t
JOIN coordinator_tasks ct ON ct.id = t.id
WHERE ct.workflow_run_id = '<run>';

All four figures match — the #1127 chain closes on the Temporal engine. For a keyless (WF_FAKE_EXECUTOR) run the per-turn figures are the deterministic canned cost; for a real-vault run they are the actual gpt-4o-mini spend (~$0.003 across the four turns, the figure this was first proven at live).


Appendix — running the automated proof locally

The exact invariants above are asserted by the WF-33 E2E against a throwaway Temporal dev-server and a scratch DB (never live infra):

# A scratch DB (name must end in _test / contain _scratch|_itest — the #1906 guard)
# migrated through the latest migration, Redis, and the Temporal CLI on PATH.
DATABASE_URL=postgres://upsquad:upsquad@localhost:5432/upsquad_test?sslmode=disable \
REDIS_URL=redis://localhost:6379/0 \
go test -tags temporal_integration -count=1 -timeout 5m -v \
-run 'TestWF33' ./test/integration/goldenflow/...

TestWF33_SDLCGoldenFlow_Temporal_HappyPath (~5s) proves the happy path + Σ cost; TestWF33_SDLCGoldenFlow_Temporal_GateExpiry (~63s — a real 1-minute gate timer) proves the void+fail guardrail. In CI they run in golden-flow-e2e.yml when a temporal-substrate change lands.