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: 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 — devops 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.