Growth Control Runbook — operate the Completion v1.1 surfaces
Status: written with the build (2026-08-19), branch
claude/growth-control-completion-v1-1-w7vg1c; everything here is
[IN BUILD] until merged, then merged ≠ deployed ≠ live-verified —
this runbook is the GATE-2 path from one state to the next. Plans of record:
docs/MIZOKI_SIGNAL_GROWTH_CONTROL_UNIFIED_SYSTEM_r2.0.md + whitepaper
r3.5.1/r3.5.2. Status authority once merged: docs/CANON_STATUS.md
(generated; python scripts/gen_canon_status.py --check) and
GET /api/v1/status/canon on service-audit-replay.
Prime rule: every surface below ships flag-OFF / config-absent =
not_configured, honestly reported. Nothing in this runbook flips an
activation or Preview label, enables a writeback, or registers a real
holdout/geo — those are separate owner acts with their own evidence.
Tenant self-serve config (owner ruling 2026-08-23, landed 2026-08-27):
every TENANT value — F5 treasury declarations, F2 discount/blend +
order-economics pointer, F4 geos/cap/bounds + observations pointer, F3
inventory policy, the supply-veto threshold/scope/freshness — is entered by
the tenant on the authenticated onboarding surface and read from their vault
(economics_declared, via mizoki_contracts.tenant_lane_config; armed by
TENANT_LANE_VAULT on the consuming service). The repo config/*.yaml
files are SHAPE-ONLY templates that assert nothing, and the lanes no longer
consult them for tenant values. Sections below describing "mount a filled
copy" workflows are retired where marked.
1. Deploy paths (rule 04: a change that spans services deploys asymmetrically)
| Surface | Path in repo | Deploy mechanism |
|---|---|---|
| Governance contracts (treasury, passport, jobs, pilot, canon-status) | contracts/mizoki_contracts/** |
action-runner auto-deploys on merge (deploy-service-action-runner.yml watches contracts/**); policy-engine / audit-replay / DCP are dispatch-only via deploy-governance-services.yml (or operator ops/remediation/deploy_all.sh) — they do NOT redeploy on merge |
| Intent cells 33/34/35 (IEv2 retention) | src/cells/cell33..35/** |
dispatch-only: deploy-intent-platform.yml (input cells: cell33 \| cell34 \| cell35 \| all) — a human dispatches |
| Cell 37 (F3) | src/cells/cell37/** |
dispatch-only (deploy-cell37.yml) |
| net-yield (Workstream C) | services/net-yield/** |
dispatch-only (deploy-net-yield.yml); registry row deployed-dispatch |
| Command Center UI | miz-oki-command-center-ui/** |
auto-deploys on merge (deploy-ui.yml) |
| virtuoso-models-service (skillpack v3.2 data) | services/virtuoso-models-service/** |
auto-deploys on merge (deploy-virtuoso-models.yml — watches the service dir only; src/shared/virtuoso_models/** alone does not trigger it, but this branch touches both) |
| Shared growth_control / creative_aesthetic libraries | src/shared/growth_control/**, src/shared/creative_aesthetic/** |
library code — live only when a consuming service above redeploys |
Consequence: after merge, the policy-engine and audit-replay revisions keep
serving the old code until an operator runs ops/remediation/deploy_all.sh.
That is the intended GATE-2 step, not drift — but never claim a route below
is live until the serving revision has it (probe, don't infer).
2. F5 — treasury constraints on the policy engine
- Source (ruling 2026-08-23): the tenant vault. The tenant enters
liquidity floor, dated cash position, base cap, and covenant cap on the
onboarding F5 card; policy-engine resolves them per tenant at evaluate
time (
resolve_tenant_treasury, parsed through the SAME treasury contract). Arming =TENANT_LANE_VAULT=trueon service-policy-engine (+secretmanager.versions.accessonmizoki-connector-*for its runtime SA). Vault unarmed / tenant unconfigured ⇒/healthsays"treasury": "not_configured", evaluate() runs with no treasury gate — fail-closed means claims-nothing, not blocks-everything.config/treasury_constraints.yamlis a shape-only template; the file mount +TREASURY_CONSTRAINTS_PATHworkflow is RETIRED (no path lets a repo value apply to a tenant). The tightening_curve is engine capability with no tenant intake today — floor/caps enforce; tightening claims nothing until a governed intake exists for it. - Apply without a redeploy:
POST /api/v1/reload(authed viaverify_caller) reloads policy, the supply-veto share-source config, and the vault handle. - Behavior when configured: the treasury check runs FIRST among hard
blocks in
evaluate(). A breach returnsstatus=BLOCKED+constraint_vetonaming exactly one oftreasury_liquidity_floor_breached/treasury_tightened_cap_exceeded/treasury_covenant_cap_exceeded, and DCP records atreasury_breach_reviewsitem (TBR-…,needs_human_review) + audit eventtreasury.breach.routed. - Review queue:
GET /api/v1/treasury/breaches(audit-replay). A veto is not an approvals-queue entry and cannot be redeemed byauthorize_approved(onlyAPPROVAL_REQUIREDeligibility redeems — 409 otherwise). Resolution = fix the position/config or drop the proposal. - Stale position: a position older than its declared freshness window tightens
the cap (floor breach ⇒ multiplier 0). Feed updates are v1-manual (edit the
mounted file,
/reload); the v2 live feed is an honestNotImplementedTreasuryFeedstub — do not wire anything to it.
2b. Supply-chain stockout veto v2 (policy engine — measured)
- v2 (ruling 2026-08-23 §3): measured data is never typed. The share is
read LIVE from
mizoki_unified_data.f3_recommendations(kindstockout_share_report, written by cell37's F3 evaluation); the tenant declares only the threshold, governed domains, and freshness bound on the onboarding supply card. The v1 declared-config path is REMOVED —parse_supply_veto_configrejects any config carrying atenantskey, so a declared share cannot re-enter through review. - Arming:
SUPPLY_VETO_CONFIG_PATH=/app/service/supply_veto.yaml(the shipped v2 share-source config in the policy-engine image) +TENANT_LANE_VAULT=true— a reviewed env change ondeploy-governance-services.yml. Every layer stays fail-closed: tenant without threshold+domains ⇒not_configured; absent/stale/unreadable share ⇒ non-veto with the state NAMED (share_absent/share_stale/share_feed_unavailable); treasury still outranks; strictness-only holds (pinned both ways intests/governance/test_supply_veto.py). - Breach routing unchanged:
SBR-…rows insupply_breach_reviews, surfaced byGET /api/v1/supply/breaches— never approvable.
3. ValidationPassport packages (audit-replay)
- Assemble:
POST /api/v1/passport/{decision_id}/assemble(tenant-checked); read:GET /api/v1/passport/{decision_id}. The package is a READ-MODEL over the eight decision objects — 18 fields (SPEC_FIELDS, incl. proposer-declared, honest-absentmodel_version),absent_fieldshonesty manifest,sha256seal (verify_package_seal). Every persisted version carries a per-tenantchainblock (passport-chain-v1, embedded before signing) mirrored to the append-onlypassport_chain_linksstore; chain-walk verify:GET /api/v1/passport-chain/{tenant}/verify(named breaks;links: 0= "no chain yet", never "verified"). Store:passport_packages(ledger retention — never in any erasure cascade; inputs are person-data-free by construction). - Demo fixtures: SIG-042 (J-02) and Google Ads drop (J-04) under tenant
demo-fixtures, illustrative-labeled. Load viacontracts/mizoki_contracts/passport_fixtures.py. Fixtures never leave internal surfaces. - Surfacing is internal Command Center only (O-5). No external dashboard.
4. Decision Jobs + canon status (audit-replay)
GET /api/v1/jobs— J-01…J-06 fromcontracts/mizoki_contracts/decision_jobs.py(config/decision_jobs.yamlis generated FROM the code; parity-tested — edit the code, regenerate the yaml, never the reverse).GET /api/v1/status/canon— the B.6 declared-status table with code-truth probes. Doc twin:docs/CANON_STATUS.md; regenerate withpython scripts/gen_canon_status.py(venv with pydantic;--checkin CI fails on drift). Probe ≠ declaration is a same-day defect.
5. 90-day pilot (audit-replay)
POST /api/v1/pilot/{tenant}/start→ observe (day 1) → validate (day 31) → recommend (day 61).POST …/checklistrecords evidence-backed items (evidence required — the call refuses without it);POST …/advanceis double-gated (window AND checklist; refusals name the missing item);GET …/reportemitsskeleton=true, verified_numbers=[]until real measured numbers exist — a pilot report with numbers you did not measure is a defect, not a deliverable. Playbook (owner-facing):docs/pilot/PLAYBOOK.md(r2.0 §4.2 verbatim). Gate-3 artifact grants nothing — autonomy stays a separate owner act.
6. IEv2 retention sweeps (cells 33/34/35 — dispatch-only deploys)
All sweeps: flag INTENT_RETENTION_SWEEP (default False, ast-pinned);
flag-off ⇒ route answers 503 retention_sweep_disabled. All routes are
verify_caller-gated. Undatable timestamps KEEP data. Idempotent — safe to
re-run. Suggested cadence once enabled: Cloud Scheduler → authed POST,
daily; watch each cell's /health for the new fields.
- cell33 (I-01/I-02):
POST /v1/internal/retention/sweeppurges behavioral payloads for ended sessions and expired active-session rows (INTENT_SESSION_TTL_MINUTES, default 30). Streaming-buffer rows are skipped-and-counted (skipped_streaming_buffer), swept by a later run. - cell34 (I-04):
POST /v1/internal/retention/sweepdeletes shadow scores pastINTENT_SHADOW_TTL_HOURS(default 24; ≤0 refused at boot). - cell35 (I-03/I-04):
POST /v1/internal/retention/sweepretires latent bridges whose STAMPEDexpires_tspassed;POST /v1/internal/creative/retirecascades a creative retirement (graph edges + node + durable docs +creative_vectorscleanup seam — seam reportsunattempted_no_clienthonestly where the graph-only image carries no BigQuery client). - I-05 (passport feed) is ledger-retention by design — nothing to sweep.
7. F3 — observe-only inventory sync (cell 37)
- Flag
F3_INVENTORY_SYNC(default off) on cell37. Enabled, it writes recommendations tof3_recommendations(BigQuery DDLbigquery/schemas/f3_recommendations.sql) — and does nothing else. - HARD invariant (AST-tested both directions): actuator
"f3-recommendation-only"is registered in NO adapter registry — the two-key rule makes dispatch structurally impossible. If anyone asks to "just wire it", the answer is a new owner-gated workstream, not a config. - Triggers (ruling 2026-08-23): the tenant's onboarding F3 inventory card,
resolved from their vault when
TENANT_LANE_VAULTis armed on cell37 (market_cell/tenant_vault.py; same parser, per tenant, on demand).config/f3_triggers.yamlis a shape-only template; with the vault armed, file tenant values are never consulted. - v2 supply-veto feed: each evaluation also writes a tenant-level
stockout_share_reportrow (measured share + counts; evidence carries no timestamp so unchanged snapshots re-land asduplicateand the store'slast_seencarries freshness). The DEL supply veto reads it live (mizoki_contracts.supply_share_feed); the report is a measurement, never a decision-queue submission.
8. F4 — micro-geo calibration (library; approval-gated by nature)
- Config (ruling 2026-08-23): the tenant's onboarding F4 card — geos, cap,
bounds, cycle length, exclusions, and the
observations_tablepointer — resolved from their vault by growth-scheduler (growth_control/tenant_vault.py, same parser, ranges re-enforced at read time; the bridge STRIPS anyautonomykey, so the engine defaulteligible: falsealways governs). Arming =TENANT_LANE_VAULT=true+F4_CALIBRATION=trueon growth-scheduler — absent either ⇒not_configuredper tenant.config/f4_geo_candidates.yamlis a shape-only template (the 2026-08-24 mycocoons values were retired under the ruling; mycocoons re-enters them on the form).F4_CALIBRATION_DEFAULT = Falseis the pinned source literal (canon probe). - EVERY reservation proposal carries
requires_approval=Trueand actuator"f4-geo-reservation"(registered nowhere — structurally recommend-only). Proposals reach DCP only through an injected submit seam; the L2 approval flow decides. Two clean calibration cycles are the eligibility THRESHOLD only: the autonomy flip is an owner config edit that must carryapproved_by+approved_on+ ≥2clean_cycle_refs(parse-rejected otherwise), and even then proposals still route through the decision queue under caps. Reserving real geos is out of scope of this build — doing it is an owner act following this section plus a registered measurement-rails design (matched_geo). - A null
spend_cap_usd_per_cycleREFUSES proposals (spend_cap_not_declared) — a missing cap is never unbounded.
9. F1 — creative element effects (library)
- Flag
F1_ELEMENT_EFFECTS(default off;F1_ELEMENT_EFFECTS_DEFAULT = Falsepinned). Configconfig/f1_creative.yaml: thresholds are raise-only;pilot_validated: truewithoutpilot_evidence+pilot_validated_by+pilot_validated_onis rejected at parse. - Every estimate is
provisional=truein this build, and the store seam additionally REFUSESprovisional=Falserows outright — flipping the label requires the owner config change AND a deliberate seam change (double-lock). Element effects are REASON/PLAN inputs only; any creative action keeps the human-approval invariant. - DDL:
bigquery/schemas/f1_element_effects.sql(+ additivecreative_vectorselement columns). Person-data-free by construction; the writer seam rejects identity-shaped keys and values.
10. F2 — LTV treatment regimes (library)
- Config (ruling 2026-08-23): the tenant's onboarding LTV card — quarterly
discount factor, blend weight, and the
order_economics_tablepointer — resolved from their vault by growth-scheduler (same parser; the one-way quarter floor holds at read time).config/f2_ltv.yamlis a shape-only template;F2_LTV_CONFIG_PATHis no longer read by the service. - HARD guard:
MIN_OBSERVED_QUARTERS = 2(pinned literal; tenant config can only raise it). Fewer closed observed quarters ⇒data_insufficientwith no value fields — never an extrapolated finding. Partial quarters never enter findings; probabilistic identities are excluded and counted. - Ledger horizon columns (
outcome_horizon_days/_basis) are additive; historic rows stay NULL — never backfill a guess.
11. What stays OFF, and how to prove it
MEASUREMENT_WRITEBACK and NET_YIELD_WRITEBACK remain OFF — each has
a fail-if-flipped source-literal test. Verify any suspicion with the tests,
not memory:
VENV=<venv with pydantic 2.13.4/fastapi/pyyaml>
$VENV/bin/python -m pytest tests/governance -c tests/governance/pytest.ini
$VENV/bin/python -m pytest services/net-yield -q -p no:cacheprovider --override-ini="addopts="
$VENV/bin/python -m pytest tests/market_signal -q -p no:cacheprovider --override-ini="addopts="
python scripts/gen_canon_status.py --check
Shadow-deploy order for GATE 2 (each step: deploy → probe the serving
revision → only then the next): action-runner (auto on merge) →
ops/remediation/deploy_all.sh (policy-engine, audit-replay, DCP) → probe
/health treasury field + one passport fixture assemble on demo-fixtures →
dispatch deploy-intent-platform.yml (cells 33/34/35; probe 503 on sweep
routes with flag off) → dispatch deploy-cell37.yml (probe F3
not_configured) → dispatch deploy-net-yield.yml (probe dry-run) →
UI (auto). Smokes stay read-only: no flag flips, no real tenants.