OPT-GOV caller wiring — growth-scheduler built DARK + ONNX exporter
Branch: claude/opt-gov-phase5-caller-wiring (+ infra/growth-scheduler-deploy for the protected-path workflow) · Session: 7pjhmg · Date: 2026-08-22
Authority: owner directive "I want all the lanes live" + in-session ruling "Build it all, dark" (tenant scope: mycocoons first). Standing approved:commit/merge/deploy tokens. Every lane flag remains false; nothing here arms anything.
What "live" required, and what this build supplies
The four OPT-GOV library lanes shipped with injected seams and no production caller — by spec. This build supplies the caller while preserving every posture: services/growth-scheduler, an IAM-locked FastAPI service (verify_caller + tenant resolution on every route) that is dark by construction — observable and side-effect-free until the owner arms values:
| Route | Lane | Dark behavior (all pinned by tests) |
|---|---|---|
POST /api/v1/f4/schedule |
geo-SCM scheduler | Config from config/f4_geo_candidates.yaml (mycocoons nulls ⇒ the engine's own refused/not_configured); observations from the declared F4_OBSERVATIONS_BQ_TABLE (unset ⇒ named 422, never a default); cell36 holdout registration through the real client (CELL36_URL; the library's holdout-before-submit order holds); DCP submission via DCP_URL. F4_CALIBRATION dark ⇒ the library's flag_dark record — no holdout registered, nothing submitted (zero-side-effect pinned). |
POST /api/v1/f4/mmm-propose |
MMM priors | Caller declares the starting prior; posterior built only from stored CLEAN cycles; zero clean cycles ⇒ honest 409 unearned_posterior. MMM_PRIOR_WRITEBACK dark ⇒ shadow, payload withheld. |
POST /api/v1/f2/blend |
LTV blend | Curves from the declared F2_ORDER_ECONOMICS_BQ_TABLE (unset ⇒ named 422); candidate regimes stated by the caller (business hypotheses are PLAN inputs — never invented); data_insufficient passes through unsoftened; LTV_BID_WEIGHTING dark ⇒ shadow record, blended_bid_value withheld. |
POST /api/v1/edge/distill |
edge distillation | Parent scoring batch supplied by the caller (no scoring source fabricated); caller-declared fidelity floor; refusals persist as evidence without the tree; provenance vs the caller-declared current registry parent; EDGE_INFERENCE_SERVE dark ⇒ shadow offer, registration request withheld. |
Durable persistence: DurableCalibrationStore implements the F4 store seam over the governed mizoki_contracts.STORE, with the seam's own person-data-free guard imported (one guard, two homes impossible). New collections f4_mmm_proposals, f2_bid_weight_records, edge_distillation_records are posture-registered (ledger-retention, guard-enforced).
Cell36 seam, measured contract: the client maps to the shipped POST /v1/causal/holdouts:assign {tenant_id, units:[{unit_kind: "geo", unit_id}]} shape and reads assignments[]; unset URL / non-200 / empty assignments return registered: False with the reason named — surfacing as the scheduler's holdout_not_registered refusal, never a 500.
ONNX exporter (scripts/export_distillate_onnx.py): the operator step every registration offer names. Deliberately split — tree_to_onnx_spec is pure stdlib (TreeEnsembleRegressor lists, BRANCH_LEQ mirroring predict's goes-left rule) and pinned against distiller.predict on a probe grid without the onnx dependency; serialization imports onnx lazily (absent ⇒ named refusal, nothing written; present ⇒ checker-validated bytes + printed sha256 for the signing step the boss registry requires). Real round-trip tested with onnx installed.
Deploy path (.github/workflows/deploy-growth-scheduler.yml, dispatch-only, rides a protected-path review PR — the registry row says PENDING and status: built until the first successful run, per rule 04): gate = the new suites; build from repo root (image bundles shared libs + contracts + config templates); --no-allow-unauthenticated; env-replace semantics with the SELF_URL fold-in (the PR #770 lesson) and pinned config-path envs; probe = authenticated-as-allowed-caller /health reporting the dark posture plus the dark 422 observations_source_not_configured itself — the assertion only the correctly-deployed, dark service satisfies (401/403 = auth broken; 200 = something armed). Cloud Scheduler cadence stays an operator wiring (terraform is protected).
What now separates each lane from LIVE (the owner's part, unchanged)
- F4: real
geos+spend_cap_usd_per_cycle+perturbation_pct_boundsfor mycocoons inconfig/f4_geo_candidates.yaml; pointF4_OBSERVATIONS_BQ_TABLEat a view withtenant_id, geo, observed_on, value; setCELL36_URL/DCP_URL; flipF4_CALIBRATION=true— all via the workflow env list (PR #730 pattern). - F2: real
ltv_blend_weight;F2_ORDER_ECONOMICS_BQ_TABLE(the shippedorder_economicsDDL — 0 live rows today, so findings staydata_insufficientuntil ≥ 2 closed quarters exist);LTV_BID_WEIGHTING=true. - MMM: clean cycles must exist first (F4 live is the prerequisite); then
MMM_PRIOR_WRITEBACK=true. - Edge: run the exporter on a distillate, sign the bytes, register via the boss lane;
EDGE_INFERENCE_SERVE=true. - Supply veto (no caller needed — policy-engine already serves it): real values in
config/supply_veto.yaml+ image mount +SUPPLY_VETO_CONFIG_PATH+/reload.
Gates (pre-push, fresh venv)
growth-scheduler + exporter suites 25 + 6 (1 designed complementary skip) · full governance suite green (see commit line) · audit 0/14 · both parity checkers OK · canon-status fresh · V1–V3 0 in-diff · registry row honest (built, iam-intended, aspirational deploy path named as such) · no lane flag created or flipped.