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, anddevopsareagent_actionnodes (one bounded agent turn each);deploy_approvalis 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)
- The SDLC flow runs on the Temporal interpreter engine (durable execution), not the legacy coordinator.
- All 5 nodes reach a terminal state — the run completes.
- The live-DAG projection streams every transition (running → awaiting_approval → completed), lighting the console with zero polling.
- 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).
- 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 samellm_usage_eventsrows 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=trueon the agent-orchestrator + context-engine (the master kill-switch, PRD #1783 §5). With itfalse, triggers are rejected and the flow runs on the legacy coordinator — this demo needs the Temporal path.TEMPORAL_WORKER_ENABLED=trueso the interpreter + activity workers poll.- Real agents need provider keys: either a configured vault (
VAULT_PASSPHRASE) for real LLM turns, orWF_FAKE_EXECUTOR=truefor deterministic keyless turns (the demo works with either; keyless is cheaper and reproducible). grpcurlon 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: architect → coder → qa go
in_progress → completed, 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 fails —devopsnever runs, so nothing ships without a human sign-off. This is the WF-21 regression the E2E'sTestWF33_..._GateExpirylocks 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.