Skip to main content

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 pillar type — all units are team. 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 empty pillar_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's team_id and the agents on the SAME unit so they cannot diverge (the #1842/#1850 class). PillarID == TeamID in the CLI output.

Running the provisioner against a tenant (--org-id <uuid>) creates or reconciles:

  1. An Engineering unit (org_units, a root team) 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).
  2. Four deployed agents, named exactly to match the SDLC workflow's role labels (this is load-bearing — see "Why the names matter" below):

    namepersonamodelclearancebudget
    architectdrafts the design notegpt-4o-mini3$5/day, $20/mo
    coderimplements the functiongpt-4o-mini3$5/day, $20/mo
    qareviews against the acceptance checkgpt-4o-mini3$5/day, $20/mo
    devopswrites the release notegpt-4o-mini3$5/day, $20/mo
    All are deployed SUPERVISED with an empty (minimal) toolset.
  3. The SDLC workflow definition ("SDLC Golden Flow"), registered as an active workflow 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 SchemaValidator before it is ever written.

  4. A dogfood targetFEATURE_REQUEST.md, a tiny "add a Clamp helper" 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.

  5. Agent-step governance policies — four rows in org_governance_policies (one per workflow agent_role) so the SDLC agent-step Govern check passes instead of hitting the default-deny (issue #1842):

    action_typetargeteffectmin_clearance
    agent_actionarchitectallow0
    agent_actioncoderallow0
    agent_actionqaallow0
    agent_actiondevopsallow0

    These 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 to org_governance_policies via UpsertOrgPolicy; the team rows go to org_unit_governance_policies via UpsertTeamPolicy, keyed on the SDLC workflow's stored team_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_clearanceClearance=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:

  • orgorg_governance_policies via UpsertOrgPolicy.
  • teamorg_unit_governance_policies via UpsertTeamPolicy, keyed on the SDLC workflow's stored team_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_id must already exist in organizations (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-id names the canonical workflow the SDLC trigger runs.
  • --repoint-workflow-team authorizes the single-row workflows.team_id mutation (without it, a team mismatch is a loud error — never a silent re-point). It is dev-only: refused when ENVIRONMENT is production/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:

  1. Team routing — the deterministic matcher routes intent tags ["engineering","code"] to the Engineering team.
  2. Role resolution — every workflow agent_role resolves to exactly one active agent whose name matches it (unique per team — B6 semantics).
  3. Definition validity — the registered SDLC definition validates in the engine's SchemaValidator.
  4. Agent-step governance policies (both layers) — every workflow agent_role has an exact (non-wildcard) agent_action/<role> allow/min_clearance=0 policy at both the org layer (org_governance_policies) and the team layer (org_unit_governance_policies, keyed on the SDLC workflow's team_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 devops step 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 INSERT rather than via workflow.Store.CreateWorkflow, which scans created_at straight into a *timestamppb.Timestamp and only works under pgxmock (no protobuf-timestamp pgx codec is registered). Flagged for a separate fix on the workflow store.