Golden-flow provisioning
The "golden flow" is the content the MVP work-routing spine (Quad → Task → team → delegate → gates → cost) runs on, and the target the live demo and the D1 end-to-end runner (#1235) exercise. This runbook covers how to provision it on a dev or prod tenant, what gets created, and the idempotency guarantees.
Issue: #1233 · milestone "MVP — Work-Routing Framework" · ADR-0015 (A2A model), ADR-0016 (demo-fork).
What gets provisioned
v2.4 single-unit model (#1867). Org-model v2.4 (migration 125) dissolved the
pillartype — all units areteam. The provisioner therefore no longer creates a separate pillar; the golden flow is one uniform "Engineering" unit (the active root) that the agents, the workflow, and the governance policies all bind to. Agents are registered with an emptypillar_id(→ NULL), which skips the legacy pillar existence gate (pillarExistsGated, #1853/#1854). This is what makes the full path converge on a v2.4 tenant and keeps the workflow'steam_idand the agents on the SAME unit so they cannot diverge (the #1842/#1850 class).PillarID == TeamIDin the CLI output.
Running the provisioner against a tenant (--org-id <uuid>) creates or reconciles:
-
An Engineering unit (
org_units, a rootteam) carrying the B8 routing metadata (migration 092, #1234):domain = "engineering"capabilities = ["code","review","test","deploy","sdlc"]The deterministic Coordinator matcher (OrgUnitService.RouteTeam) uses these to map a Task's intent tags to this unit. This single unit IS the golden-flow team (post-#1867 there is no separate pillar).
-
Four deployed agents, named exactly to match the SDLC workflow's role labels (this is load-bearing — see "Why the names matter" below):
name persona model clearance budget architectdrafts the design note gpt-4o-mini3 $5/day, $20/mo coderimplements the function gpt-4o-mini3 $5/day, $20/mo qareviews against the acceptance check gpt-4o-mini3 $5/day, $20/mo devopswrites the release note gpt-4o-mini3 $5/day, $20/mo All are deployed SUPERVISEDwith an empty (minimal) toolset. -
The SDLC workflow definition ("SDLC Golden Flow"), registered as an
activeworkflow on the team:architect (agent_action)→ coder (agent_action)→ qa (agent_action)→ deploy_approval (approval_gate, required_clearance=4, high severity)→ on approve: devops (agent_action, terminal)→ on reject: flow fails (no deploy)The definition is validated with the engine's own
SchemaValidatorbefore it is ever written. -
A dogfood target —
FEATURE_REQUEST.md, a tiny "add aClamphelper" spec the architect drafts against. Each step does small-but-real work. MVP performs no external side effects (no GitHub writes); that is a follow-up delivered via the toolset extension. -
Agent-step governance policies — four rows in
org_governance_policies(one per workflowagent_role) so the SDLC agent-step Govern check passes instead of hitting the default-deny (issue #1842):action_type target effect min_clearance agent_actionarchitectallow0agent_actioncoderallow0agent_actionqaallow0agent_actiondevopsallow0These four rows are seeded at both legacy governance layers the engine reads (org and team — see below), so the org ∩ team intersection the agent-step Govern check computes yields
allow. The org rows go toorg_governance_policiesviaUpsertOrgPolicy; the team rows go toorg_unit_governance_policiesviaUpsertTeamPolicy, keyed on the SDLC workflow's storedteam_id. Both ride the same RLS-scoped transaction and upsert on their unique keys. See "Why the governance policies, and why per-role" below.
Why the agent names matter (B6 role resolution)
Role → agent resolution matches a workflow step's agent_role against the agent
name, case-insensitively and exactly, and the name must be unique per
team. The roster names (architect/coder/qa/devops) are therefore the
same strings used as agent_role in the SDLC definition. The provisioner asserts
this invariant up front (Spec.SpecRolesMatchAgents) and again in --verify
(CheckRoleResolution), so any drift between the roster and the definition is
caught before the flow is run.
Why the governance policies, and why per-role (#1842)
An SDLC agent-step Govern hop is checked with action_type="agent_action",
target=<agent_role>, and — because the SDLC steps carry no required_clearance
— Clearance=0. It also carries no MemberID, so the governance engine skips
the 4-layer cascade (engine.go: the cascade is only entered when
action.MemberID != "") and falls to the legacy org-only path, which reads
only org_governance_policies and team_governance_policies (the two-layer
legacy walk). On an unseeded tenant both are empty, so the check hard
default-denies. Seeding these policies is what lets the Govern step pass.
Both legacy layers must be seeded (org AND team). The legacy path evaluates
the org layer and the team layer for any action carrying a team_id
(engine.go §2 + §4), then intersects them — a default-deny at either
layer fails the check. The SDLC action carries the workflow's team_id, so an
org-only seed passes the org gate only to hit a symmetric team-layer
default-deny (denied_by=team — the #1842
f142a8fc live trigger; ruling
4996776329).
So the provisioner seeds each per-role agent_action/<role>/allow/0 row at
both layers:
- org →
org_governance_policiesviaUpsertOrgPolicy. - team →
org_unit_governance_policiesviaUpsertTeamPolicy, keyed on the SDLC workflow's storedteam_id(least-privilege: exactly the team the resolved action carries, not every team).
Which team table (the correctness gate). Migration 070 dropped
team_governance_policies; with read_from_org_units ON, UpsertTeamPolicy /
GetTeamPolicies retarget to org_unit_governance_policies (unit_id == team_id). The deployed orchestrator engine reads that table (the team deny
returned an empty layer, not a 42P01 "relation does not exist"). The
provisioner therefore must write there too — it pins read_from_org_units
ON (the same resolver it hands the agent service) and
ensureTeamGovernancePolicies guards on it, refusing to seed if the flag is
OFF rather than silently writing into the dropped legacy table. The seed's
flag-state must match the orchestrator's READ_FROM_ORG_UNITS.
Per-role, explicitly not target='*'. A single agent_action/*/allow row
would satisfy the check too, but a wildcard allow is a governance rubber-stamp —
it undercuts the governed-release story and, because "whatever ships becomes the
pattern beta inherits," it would cement a wildcard-allow anti-pattern. Per-role
explicit allows still enforce real authorization: a role not in the set falls
through to default-deny, so the choke-point stays demonstrable (you can show a
role the flow denies). The provisioner derives the targets from the workflow's
own agent_role labels, so the seed stays in lock-step with the definition.
min_clearance=0 is deliberate — and temporary. Agent-step Govern is
currently checked at Clearance=0, so any min_clearance>0 policy can never
match. 0 is the value that unblocks the flow today. The follow-up
#1847 (branch-timed
with WF-19) changes govInput to pass the resolved agent's clearance (the
roster is deployed at clearance 3) so min_clearance becomes a meaningful gate;
when it lands, the seeded min_clearance is bumped 0 → 3 at both the org
and team layers together (an asymmetric org=0 / team=3 would deny agents with
clearance <3 at the team layer while org allowed — confusing and demo-breaking).
Until then, the per-role targeting is the meaningful governance and the clearance
gate is hardened next. Do not raise min_clearance above 0 before #1847
lands — it would re-introduce the default-deny.
Running it
The provisioner is a standalone command. It needs DATABASE_URL for the target
environment and the tenant's org_id.
# Provision (default). Idempotent — safe to re-run.
DATABASE_URL=postgres://USER:PASS@HOST:5432/upsquad?sslmode=disable \
go run ./cmd/provision-golden-flow --org-id <tenant-org-uuid>
# Verify the wired result without making any writes.
DATABASE_URL=... go run ./cmd/provision-golden-flow --org-id <uuid> --verify
# Also emit the dogfood target file for the runner to feed as trigger context.
DATABASE_URL=... go run ./cmd/provision-golden-flow --org-id <uuid> \
--dogfood-out ./FEATURE_REQUEST.md
A convenience wrapper that defaults DATABASE_URL/REDIS_URL to the local dev
stack lives at scripts/provision-golden-flow.sh:
scripts/provision-golden-flow.sh <org-id> # provision
scripts/provision-golden-flow.sh <org-id> --verify # verify only
REDIS_URL is optional (defaults to redis://localhost:6379/0). It is only used
to construct the agent service; lifecycle-event emission is disabled during
provisioning, so Redis is never dialed.
--policies-only mode (governance seed, decoupled)
# Seed the two-layer governance policies. --workflow-id pins WHICH workflow's
# team the team-layer seed keys on (the id the trigger runs). On the dev tenant:
DATABASE_URL=... go run ./cmd/provision-golden-flow --org-id <uuid> \
--policies-only --workflow-id a07c8ff5-598c-43ab-ab66-ae35bd6100e1
# Verify only the governance rows (pass the SAME --workflow-id; other checks SKIP).
DATABASE_URL=... go run ./cmd/provision-golden-flow --org-id <uuid> \
--verify --policies-only --workflow-id a07c8ff5-598c-43ab-ab66-ae35bd6100e1
--policies-only upserts just the two-layer governance rows (item 6 of "What
gets provisioned" above — four org rows and four team rows), skipping the
pillar/team/agents/workflow ensure steps entirely. Same single RLS-scoped
transaction, same ON CONFLICT idempotence, same org-scoping as a full
provision — it simply runs one of the five steps.
Which team the team-layer seed keys on (--workflow-id / --team-id). The
SDLC trigger runs an explicit workflow_id, and the run carries that
workflow's stored team_id. The team-layer seed must land on the SAME team, so
--workflow-id <id> pins it (or --team-id <id> to override directly). This is
load-bearing on the dev tenant, which has multiple active workflows named
"SDLC Golden Flow" on different team_ids — a name/oldest heuristic would
seed the wrong team, the canonical trigger (a07c8ff5 → team dd153714) would
still deny, and --verify would false-green. When no explicit id is given and
multiple active same-named workflows have differing team_ids, the command
fails loud listing every (workflow_id → team_id) rather than guess. If the
workflow is absent entirely it also fails loud (never seeds org-only, which would
silently reintroduce the team gate). Dev-tenant invocation:
--workflow-id a07c8ff5-598c-43ab-ab66-ae35bd6100e1.
When to use it. On an org-model v2.4-migrated tenant the full provision
path is currently blocked: migration 125 dissolves the pillar type
(unit_type='pillar' → 'team'), and the provisioner's pillar model has not yet
converged with the v2.4 uniform-team graph — the full run fails at
ensureAgents (RegisterAgent passes the adopted root's id as a non-empty
pillar_id, and pillarExistsGated rejects a retyped team root:
pillar not found). That layer is irrelevant to what the SDLC Govern check
needs — it reads only org_governance_policies — so --policies-only closes the
#1842 policy half
regardless of the drift. Tracking:
#1866 (this mode) and
#1867 (full-path
convergence — retiring the provisioner's pillar model against the v2.4 graph).
Why it's sufficient and safe. The eight rows (org + team) are all the legacy
two-layer Govern path reads; the SDLC workflow (a07c8ff5) already exists under
the dev org with a stored team_id, so runtime role→agent resolution does not
depend on re-running the provision path. --verify --policies-only reports the
three org-unit/agent/definition checks as SKIP (not FAIL) and gates only on
the governance rows (both layers).
Prerequisites
- Migration 092 (org-unit routing metadata) and 014 (workflows) must be applied to the target DB. If a dev DB is behind, see dev-migrate dirty-version recovery.
- The target
org_idmust already exist inorganizations(a tenant must have been created first).
Full-path convergence on a drifted v2.4 tenant (--workflow-id + --repoint-workflow-team, #1867)
On a tenant where a prior workflow already runs on a different unit than the
one the agents occupy, full provision alone would leave the trigger's workflow
pointing at the wrong team and role→agent resolution would fail
(no deployed agent matches role … in team … — the #1842 run 835f66b5 shape:
the agents are on the active root, the canonical workflow is on a soft-deleted
child). To converge, name the canonical workflow and authorize the re-point:
# DEV ONLY. Re-points the named workflow's team_id onto the golden-flow unit
# (where the agents live), then seeds governance on that unit in the SAME run.
ENVIRONMENT=development go run ./cmd/provision-golden-flow \
--org-id 00000000-0000-0000-0000-000000000001 \
--workflow-id a07c8ff5-598c-43ab-ab66-ae35bd6100e1 \
--repoint-workflow-team
--workflow-idnames the canonical workflow the SDLC trigger runs.--repoint-workflow-teamauthorizes the single-rowworkflows.team_idmutation (without it, a team mismatch is a loud error — never a silent re-point). It is dev-only: refused whenENVIRONMENTisproduction/prod/beta; the same operation on a prod/beta tenant is founder-gated (#1867 §4A / FOLLOW-ON 2).
Governance replay guard — ordering is load-bearing. The re-point and the
governance seed happen in the same provision run: ensureWorkflow re-points
first, then ensureGovernancePolicies seeds on the golden-flow unit T (it keys
on T, never the workflow's stale team_id), so Govern reads a seeded team
layer on T post-repoint (no denied_by=team replay). The equivalent
decomposition is: (1) re-point, then (2) re-run
--policies-only --workflow-id <id> (which now reads the re-pointed team_id
and seeds on T). The orphaned governance rows on the old (tombstoned) team are
inert — no active workflow references it, so no action carries that
team_id. After convergence, run --verify (all four checks OK) and confirm the
SDLC trigger reaches agent execution.
--verify checks
--verify is read-only (the transaction is rolled back) and asserts:
- Team routing — the deterministic matcher routes intent tags
["engineering","code"]to the Engineering team. - Role resolution — every workflow
agent_roleresolves to exactly one active agent whose name matches it (unique per team — B6 semantics). - Definition validity — the registered SDLC definition validates in the
engine's
SchemaValidator. - Agent-step governance policies (both layers) — every workflow
agent_rolehas an exact (non-wildcard)agent_action/<role>allow/min_clearance=0policy at both the org layer (org_governance_policies) and the team layer (org_unit_governance_policies, keyed on the SDLC workflow'steam_id). A missing, denied, wildcard-only, or non-zero-clearance policy at either layer fails this check — a green verdict means both legacy Govern layers are seeded (verify-symmetry, ruling 4996776329), so a re-triggered SDLC run cannot rediscover a gate the verify claimed was closed.
It exits non-zero if any check fails, so it is usable as a CI/CD gate.
With --policies-only, --verify runs only check 4; checks 1–3 print as
SKIP: … (policies-only mode) and the exit code gates solely on the governance
rows.
Idempotency
Re-running provisioning against the same tenant creates no duplicates:
- the pillar and team are matched by name (case-insensitive) and reused;
- team routing metadata is re-applied every run (heals drift);
- agents are matched by name (case-insensitive, unique per team) and reused; on a re-run their persona and budget are reconciled to the Spec;
- the workflow is matched by name on the team and skipped if present (the definition is immutable);
- the four agent-step governance policies upsert on
UNIQUE(org_id, action_type, target)(ON CONFLICT DO UPDATE), so a re-run converges the effect/clearance in place — same rows, no duplicates.
All work for a single provisioning run happens inside one RLS-scoped transaction
and is committed atomically; --verify rolls back.
This is why seeding lives in the committed provisioner path rather than ad-hoc
SQL: the dev reconciler resets /opt on a schedule, but the seeded DB rows
survive it, and re-running scripts/provision-golden-flow.sh <org-id> is the
single documented, idempotent way to (re-)apply them.
Notes / follow-ups
- No external side effects in MVP. The
devopsstep records a release note; GitHub writes / real deploys are a follow-up via the agent toolset extension. - The workflow row is written with a direct, validated
INSERTrather than viaworkflow.Store.CreateWorkflow, which scanscreated_atstraight into a*timestamppb.Timestampand only works under pgxmock (no protobuf-timestamp pgx codec is registered). Flagged for a separate fix on the workflow store.