MIZOKI audit → work-order backlog
Source: "MIZOKI: repository audit and profit roadmap", code-backed update of 6 Sep 2026, pinned at MIZOKI-3-5/MIZOKICloudRun@fc8b03f9.
Prepared: 8 Sep 2026 for Boss. Status: recommendations, not authorization. No code, config, deploy, or spend changed.
Path check: file paths below were confirmed present in the local checkout at 15a6ba29b (7 Sep, docs(github-virtuoso): connector_setup v1.3 …, 5 uncommitted local changes), which is ahead of the audited commit. Only one defect was re-confirmed on that HEAD: services/service-action-runner/execution_adapters/google_ads.py:55 still defaults to v21 (WO-24). Every other ticket carries the audit's finding as its hypothesis; the first step of each is to reproduce on current main.
How to read a ticket
WO-nn · [Package] · Severity · Lane then Finding → Files → Fix → Acceptance → Depends on. Severity: P0 = spending-admission or tenant-boundary blocker; P1 = economics/causal credibility blocker for any customer claim; P2 = product truth, provider compatibility, CI; P3 = enhancement or evidence task. Lanes match the report's handoffs: ENG, SEC, MEAS, CUST, OPS, OWNER.
Package order
A Truth & inventory (WO-00, WO-31, WO-30) days 1–7
B Spending admission hard gates (WO-01..07) ┐ coordinated
C Tenant boundaries & durable execution (WO-15..21) ┘ days 8–30
D Economics correctness (WO-08, WO-09)
E Causal/statistical consolidation (WO-10..14, WO-29)
F Product truth & provider compatibility (WO-22..25, WO-33)
G Customer loop proof (WO-40..43) — gated on B, C, D passing
H Release gate (WO-26..28, WO-32) — recurring, every release
Rule from the report: A → B/C together → D/E/F → G when evidence permits → H on every change. Nothing in G starts until every P0 in B and C has a green negative test in CI.
Package A — Establish current truth
WO-00 · [A] · P0 · ENG — Pin audit commit and diff to current main
Finding: Audit pinned fc8b03f9; local main is already at 15a6ba29b. Every finding must be re-anchored before work is assigned.
Files: repo root; OPEN_ITEMS.md; docs/BUILD_DEBT.md.
Fix: git diff fc8b03f9..main --stat over the files named in WO-01..33; mark each finding unchanged / moved / already fixed. Reconcile against OPEN_ITEMS.md so no duplicate remediation tickets exist (report §Findings 4).
Acceptance: a table in docs/audits/AUDIT_2026-09-06_RECONCILIATION.md with one row per R-reference: path at fc8b03f9, path at main, status, ticket. All 33 rows filled.
Depends on: none.
WO-31 · [A] · P2 · OWNER — Reconcile open register with audit
Finding: OPEN_ITEMS.md already lists pilot, MMM and activation gaps [R31]; some older defect lists are obsolete (#804 closed via #809/#810).
Files: OPEN_ITEMS.md, docs/BUILD_DEBT.md.
Fix: merge WO-01..33 into the existing register; close items superseded by Sep 2 state; do not create a competing status doc.
Acceptance: one register, every WO linked, no duplicate of an already-closed item.
Depends on: WO-00.
WO-30 · [A] · P2 · ENG — Verify onboarding lane-readiness evidence join
Finding: Lane readiness is derived from an evidence join whose inputs were not verified [R30].
Files: miz-oki-command-center-ui/app/api/bff/lanes/status/route.ts (+ .test.ts); onboarding page.
Fix: trace each readiness flag to its evidence source; readiness must not report ready from a default or missing record.
Acceptance: test: tenant with no economics record → lane status not_ready, never ready.
Depends on: WO-00.
Package B — Spending admission: hard failures
WO-01 · [B] · P0 · ENG — Make hard-gate failures terminal in policy eligibility
Finding: Six-check validator with a failed incremental-profit check still returned ELIGIBLE at DEL 91.7 because eligibility uses an aggregate pass-rate floor [R1][R2].
Files: services/service-policy-engine/main.py (pass_rate / DEL); services/service-validation-orchestrator/main.py (incremental_profit check); services/service-decision-control-plane/main.py.
Fix: partition checks into hard (economic, integrity, consent, policy) and rank. Any hard failure → INELIGIBLE before DEL is computed. Exploratory experiments get their own bounded eligibility class, never a hidden exception.
Acceptance: negative tests: each hard check failing alone, with all others passing at 100%, → INELIGIBLE. Property test: no combination of rank scores can flip a hard failure.
Depends on: WO-00.
WO-02 · [B] · P0 · ENG — Reject unbounded exploration
Finding: Companion to WO-01: exploration must be explicit and capped, not a side effect of averaging [R1].
Files: services/service-policy-engine/main.py.
Fix: eligibility_class ∈ {standard, exploration}; exploration requires an approved envelope ID, cap, and logged assignment probability.
Acceptance: exploration candidate without envelope → refused; with envelope over cap → refused.
Depends on: WO-01.
WO-03 · [B] · P0 · SEC — Bind evidence passports to the decision
Finding: DCP consumed a fetched passport's aggregate scores without checking tenant/domain/path binding or seal; a foreign, invalidly sealed passport yielded a signed Stage-4 authorization [R3].
Files: services/service-decision-control-plane/main.py, decision_meter.py.
Fix: resolve passports as immutable records; verify seal; bind tenant, action fingerprint, model version, horizon, validity window into the signed authorization payload.
Acceptance: tests: foreign-tenant passport, stale passport, altered-body passport, wrong-action passport → each refused with a distinct reason code. Authorization signature covers the binding fields.
Depends on: WO-00.
WO-04 · [B] · P0 · MEAS — Prove holdout registration, not a boolean
Finding: Experiment sufficiency depends on a proposer boolean; a nonblank holdout ID at the adapter does not prove pre-exposure registration [R4].
Files: services/service-action-runner/execution_adapters/base.py, meta_ads.py (holdout checks); DCP.
Fix: adapter resolves holdout ID against the experiment registry; requires registered_at < first_exposure_at and a matching tenant.
Acceptance: unregistered ID, post-exposure registration, other-tenant registration → refused.
Depends on: WO-03.
WO-05 · [B] · P0 · SEC — Derive approver identity from authentication
Finding: Approval service compares a request-body actor string with the requesting service; the same service passes by inventing a human name [R5].
Files: services/service-approval-routing/main.py; principal auth module.
Fix: approver identity and role come only from the verified principal; body-supplied actor fields are ignored or must match the principal. Service allowlisting ≠ human approval.
Acceptance: HTTP test with a service principal and a body actor of a different human → 403. Test with a verified human principal lacking the role → 403.
Depends on: WO-00.
WO-06 · [B] · P0 · SEC — Verify rollback proof before promotion
Finding: Actuator registration can assert rollback_demonstrated=True; promotion trusts it without a drill [R6].
Files: services/service-action-runner/main.py; tests/governance/test_action_runner.py, test_decision_control_plane.py; ops/remediation/live_proof.py.
Fix: promotion requires a stored proof artifact (drill ID, tenant, account, action class, timestamp, outcome) per tenant/account/action; registration flags are advisory only.
Acceptance: promotion with flag=True and no artifact → refused; with artifact for a different action class → refused.
Depends on: WO-05.
WO-07 · [B] · P0 · ENG — Single-use approval under concurrency
Finding: Concurrent redemption of one approval issued two distinct authorization IDs [R7].
Files: services/service-decision-control-plane/main.py (approval redemption); Firestore/DB layer.
Fix: atomic claim on the decision/approval; deterministic authorization ID derived from (approval_id, decision_fingerprint); retries return the same stored result.
Acceptance: 50-way concurrent redemption test → exactly one authorization ID; crash-after-write retry → same ID; contention at every write boundary covered.
Depends on: WO-03.
Package C — Tenant boundaries and durable execution
WO-15 · [C] · P0 · SEC — Role-check the onboarding mutation handlers
Finding: Onboarding page requires admin, but economics / cost / connector-credential save handlers check tenant identity only; viewer identity returned 200 and invoked all three saves [R15][R16].
Files: miz-oki-command-center-ui/app/api/bff/tenant-economics/save/route.ts; connector credential save route under app/api/bff/connectors/; reference pattern in app/api/bff/actions/authorize/route.ts.
Fix: apply the canonical role + actor check from the authorize route to all three mutations; backend economics/cost routes verify end-user role, not just service + tenant.
Acceptance: Vitest: viewer → 403 on all three, no adapter call; admin → 200. Backend route test with valid service token but viewer role → 403.
Depends on: WO-00.
WO-16 · [C] · P0 · SEC — Inventory the end-user role model
Finding: Companion to WO-15: role checks exist only on some routes.
Files: miz-oki-command-center-ui/app/api/**/route.ts.
Fix: generate a route → required-role table; every mutating route must have a non-null entry.
Acceptance: CI test fails if a mutating route has no role annotation.
Depends on: WO-15.
WO-17 · [C] · P0 · ENG — Fail closed on empty tenant registry
Finding: Shared tenant resolver permits arbitrary tenant input when its registry is empty; a registry read failure produces that state [R17].
Files: tenant resolver (DCP decision_meter.py; policy engine main.py; shared resolver in src//common/ — locate in WO-00).
Fix: strict mode refuses unknown tenants; distinguish unavailable from intentionally_empty; last-good cache only with TTL and documented policy. Enforce stored-tenant ownership on DCP reads and runner execute/rollback/outcome writes.
Acceptance: registry read raises → resolution refused; empty registry in strict mode → refused; cross-tenant read of a stored decision → 404/403.
Depends on: WO-00.
WO-18 · [C] · P0 · ENG — One versioned constraint state from Decide to settlement
Finding: Policy reads treasury from tenant vaults; DCP/Act loads an optional global file at startup, so a per-proposal treasury pass does not establish an aggregate reservation [R18].
Files: services/service-decision-control-plane/main.py, decision_meter.py; services/service-action-runner/main.py; services/service-policy-engine/main.py, pacing_veto.py.
Fix: single per-tenant versioned constraint resolver used at admission, reservation, execution, settlement; currency, horizon, freshness bound to the action.
Acceptance: test: policy-level treasury pass with no DCP reservation → execution refused; stale constraint version → refused.
Depends on: WO-17.
WO-19 · [C] · P0 · ENG — Persist atomic exposure reservations and freezes
Finding: Portfolio exposure and reconciliation freezes are process-local dicts; two $70 checks against a $100 cap both passed → $140 exposure [R19].
Files: services/service-action-runner/execution_adapters/portfolio.py, base.py.
Fix: move reservations/freezes to a transactional store (Firestore transaction or equivalent); check-and-reserve is one atomic op keyed by tenant/account.
Acceptance: two workers, two processes, $70+$70 vs $100 cap → exactly one passes; restart mid-reservation → reservation survives.
Depends on: WO-18.
WO-20 · [C] · P0 · ENG — Cover the whole mutation-and-verification interval
Finding: An ambiguous exception inside adapter.execute is raised before the freeze handler's try-block [R20].
Files: services/service-action-runner/execution_adapters/base.py, main.py.
Fix: wrap dispatch → provider call → read-back in one guarded span; unknown outcome → freeze + reconcile before any retry; state machine proposed→validated→authorized→dispatched→confirmed/uncertain/failed→compensated/closed.
Acceptance: fault-injection tests: provider success + client timeout, crash after provider success, duplicate delivery → no duplicate spend effect, freeze recorded.
Depends on: WO-19.
WO-21 · [C] · P0 · SEC — Authenticate the Boss service perimeter
Finding: Production entry re-exports the Boss app; chat and direct-action handlers have no auth dependency and the deploy config requests public access [R21].
Files: miz-oki-adk-agents/boss/app.py, miz-oki-adk-agents/app/main.py; deploy workflows carrying allow-unauthenticated/allUsers (see .github/workflows/deploy-governance-services.yml and others found in WO-00).
Fix: explicit public/private route inventory; auth dependency on chat and direct-action; deploy config no longer requests public invoker unless the route inventory says so.
Acceptance: anonymous request to direct-action → 401 in test client; route inventory committed; IAM invoker policy verified by OPS (read-only check).
Depends on: WO-00.
Package D — Economics correctness
WO-08 · [D] · P1 · ENG — Tenant-key every net-yield aggregation
Finding: Return-rate SQL groups by SKU without tenant_id; order-rate stage joins by order_id alone; final tenant MERGE cannot undo pooled inputs [R8]. Finding is from generated-SQL inspection, not BigQuery execution.
Files: services/net-yield/compute.py, bq.py, test_compute.py.
Fix: carry tenant_id through every CTE, join, group-by, and maturity window.
Acceptance: two-tenant invariance test against a real BigQuery test dataset: replacing tenant B's rows changes nothing in tenant A's output. Generated SQL snapshot test asserts tenant_id in every GROUP BY / JOIN ON.
Depends on: WO-00.
WO-09 · [D] · P1 · ENG — Replay-safe refunds, fees and changed orders
Finding: $40 refund doubled to $80 under concurrency/retry; refund before order lost; two flat fees overwrote each other; changed orders kept stale COGS [R9].
Files: services/net-yield/returns_adjustment.py, bq.py, cost_config.py; tests test_order_economics.py, test_bq.py.
Fix: durable unique event ledger keyed by provider event ID; atomic transitions or deterministic materialization; unmatched refunds retained and re-matched; versioned recompute on order change; fees keyed by (tenant, fee_type).
Acceptance: against a real test DB: duplicate refund delivery → one entry; refund-before-order → matched later; crash-and-retry → idempotent; two fee types → both kept; order change → COGS recomputed.
Depends on: WO-08.
Package E — Causal and statistical consolidation
WO-10 · [E] · P1 · MEAS — Replace individual caused/anticipated labels with experiment-level estimands
Finding: Causal credit labels first-N conversions anticipated, rest caused; swapping two timestamps moved summed caused value $1,000 → $10 [R10].
Files: services/measurement-rails/causal_credit.py, main.py, test_causal_credit.py.
Fix: compute incremental revenue/profit at the assignment-unit level with intervals; any per-purchase allocation is labeled convention, never causal.
Acceptance: permutation test: reordering purchase timestamps within a cell does not change the estimand; output schema carries estimand, ci_low, ci_high, n_units.
Depends on: WO-00.
WO-11 · [E] · P1 · MEAS — Stop feeding the point sum into Meridian calibration
Finding: MMM export consumes the caused-value sum as a calibration point estimate [R11].
Files: services/service-media-incrementality/main.py; services/service-validation-orchestrator/main.py (meridian export).
Fix: export experiment-level effect + interval from WO-10; refuse export when the estimand is a convention.
Acceptance: export test: convention-labeled input → export refused; experiment input → prior with SD populated.
Depends on: WO-10.
WO-12 · [E] · P1 · MEAS — Cell 26: honest evaluation split and interval
Finding: Policy evaluated on data including its training rows; interval from dispersion of predicted individual effects omits fitting uncertainty [R12].
Files: cell 26 module (resolve via config/actual_urls.py in WO-00); reuse seeded-bootstrap / grouped cross-fitting from services/lift-engine.
Fix: grouped cross-fit evaluation; bootstrap over refits; report exposure counts.
Acceptance: leakage test: evaluation rows disjoint from training rows; interval widens when n shrinks.
Depends on: WO-10.
WO-13 · [E] · P1 · MEAS — Cell 27: refresh intervals on posterior update
Finding: Posterior parameters update but promotion still reads the original stored intervals, so no winner is ever selected [R13]. Files: cell 27 module (resolve in WO-00). Fix: recompute and persist intervals on every update; promotion reads the current version. Acceptance: lifecycle test: after N updates with a clear winner, promotion selects it. Depends on: WO-12.
WO-14 · [E] · P2 · MEAS — Provenance on F2 retention multipliers
Finding: Multipliers are useful scenario inputs but lack assumption-vs-effect provenance [R14].
Files: src/shared/growth_control/f2_ltv/dtr.py; tests/governance/test_f2_ltv.py; services/net-yield/test_returns_adjusted_f2_bridge.py.
Fix: each multiplier carries provenance ∈ {assumption, baseline, measured_effect}; proposals and bid values may consume only measured_effect.
Acceptance: test: assumption-tagged multiplier in a bid-value path → refused.
Depends on: WO-00.
WO-29 · [E] · P3 · MEAS — Dosage estimator: held-out real-data test
Finding: Synthetic smoke recovered 3.0274 vs known 3.0 and refused constant dose; that is algorithmic, not marketing, evidence [R29].
Files: services/lift-engine/continuous_dosage.py, src/core/continuous_dosage.py, tests/test_continuous_dosage.py.
Fix: add nonlinear synthetic cases (saturation, carryover); then a bounded prospective real-data test under WO-41.
Acceptance: saturation fixture recovered within tolerance; support-range refusal test.
Depends on: WO-41.
Package F — Product truth and provider compatibility
WO-22 · [F] · P2 · ENG — Quarantine the legacy omnichannel allocator
Finding: Routable allocation API returns hardcoded defaults on missing data/exception: $68,000 budget, $281,835.51 projected revenue from zero input; channel budgets summed to $53,828.12; 267,000% conversion rate [R22][R23].
Files: miz-oki-command-center-ui/app/api/omnichannel/allocation/route.ts.
Fix: return unavailable on missing live evidence; simulations explicitly labeled; assert allocated + unallocated = budget; guard zero denominators.
Acceptance: zero-input → 422 evidence_unavailable; sum invariant test; missing clicks → rate null, not a number.
Depends on: WO-00.
WO-23 · [F] · P2 · ENG — Assert no path from legacy allocator into DCP execution
Finding: No connection to canonical DCP execution was demonstrated, but not disproven [R23]. Files: as WO-22; DCP intake. Fix: grep-based CI guard that the legacy route's output type is never accepted by DCP proposal intake. Acceptance: CI test present and green. Depends on: WO-22.
WO-24 · [F] · P2 · ENG — Google Ads adapter: upgrade off sunset v21
Finding (re-confirmed on local main 15a6ba29b): google_ads.py:55 defaults GOOGLE_ADS_API_VERSION to v21; v21 sunset 5 Aug 2026; no version override in the runner workflow; adapters checked disabled. Dormant activation blocker [R24][W16][W17].
Files: services/service-action-runner/execution_adapters/google_ads.py; runner deploy workflow env.
Fix: default to a currently supported version (release notes list v25.1, 19 Aug 2026 — confirm latest supported at implementation time); test every mutation and read-back contract against the new version.
Acceptance: validate-only mutation + read-back succeeds in a test account; CI asserts default version is not in Google's sunset list.
Depends on: WO-00.
WO-25 · [F] · P2 · ENG — Data Manager connector: fix request contract
Finding: Existing connector omits productDestinationId, never consumes conversion_action, sends conversionValue as an object instead of number + sibling currency, and omits encoding for hashed userData. Independently confirmed against REST schema [R25][W18][W19]. Do not build a second connector.
Files: services/service-data-manager-connector/main.py; miz-oki-adk-agents/kg-canonical-ingest/mappers/google_ads.py.
Fix: conform to Event / IngestEvents / Destination schema; multi-action destination routing; validate-only reporting surfaced.
Acceptance: schema-validation unit tests; validate-only ingest accepted in a test account; final ingestion diagnostics captured.
Depends on: WO-00.
WO-33 · [F] · P3 · CUST — Wire the site pilot request callback
Finding: Site pilot request construction exists with an unwired callback [R33]. Files: website pilot intake (locate in WO-00 — not found under repo root; may live in the website repos). Fix: wire callback to the pilot intake route; confirm delivery. Acceptance: submit test request → record appears in intake store. Depends on: WO-00.
Package H — Release gate (recurring)
WO-26 · [H] · P2 · ENG — Protect the exact commit that ships
Finding: main unprotected (rulesets API 403, plan-related); auto-merge reruns gates on merge result but the push-retry path can rebase after the check [R0][R26]. Several MCP/spec/origin suites absent from CI wiring.
Files: .github/workflows/auto-merge-ai-branches.yml, auto-merge-grothendieck.yml, auto-merge-mcclintock.yml, auto-sync-main.yml, ci.yaml.
Fix: retry path re-runs gates on the rebased SHA or fails; required checks at repo level (branch protection if rulesets unavailable on plan); map every required suite to a CI job.
Acceptance: simulated rebase-after-check → merge blocked; required-checks list committed and enforced.
Depends on: WO-00.
WO-27 · [H] · P2 · ENG — Extend dependency locking by service
Finding: 419 unbounded declarations in 52 of 135 requirements manifests; pin ratchet covers only Firestore and google-api-core [R27]. Not a vulnerability scan.
Files: tests/test_client_library_pin_ratchet.py; per-service requirements*.txt.
Fix: lockfiles per deployed service; ratchet extended to all deployed services; advisory scan added as a separate job.
Acceptance: ratchet test covers every service in the deploy allowlist (tests/governance/test_deploy_allowlist_completeness.py pattern).
Depends on: WO-00.
WO-28 · [H] · P3 · OPS — Authenticated customer-journey smoke at each deploy
Finding: GitHub records green Boss/UI deploys at the pinned commit; no fresh authenticated journey exercised [R28].
Files: .github/workflows/ci.yaml, deploy workflows.
Fix: post-deploy job runs an authenticated tenant journey against the named revision.
Acceptance: job reports revision name + journey pass/fail; not just build success.
Depends on: WO-21.
WO-32 · [H] · P2 · SEC — Certification evaluator enforcement scope
Finding: Certification evaluator exists; its enforcement scope is narrower than the promotion path [R32].
Files: services/service-action-runner/execution_adapters/portfolio.py; services/service-decision-control-plane/decision_meter.py.
Fix: promotion calls the evaluator per tenant/account/action; no bypass path.
Acceptance: promotion without a certification record → refused.
Depends on: WO-06.
Package G — Customer loop proof (gated)
WO-40 · [G] · P1 · CUST — Select one external design partner
Fix: run the existing partner selection process; obtain authenticated onboarding inputs (economics, connectors, vault) through the product, not chat. Acceptance: tenant record with real economics and at least one live read-only connector. Depends on: WO-15, WO-17.
WO-41 · [G] · P1 · MEAS — Preregister one powered experiment
Fix: declare tenant, unit, treatment, comparator, primary outcome, horizon, return maturity, MDE, power, alpha, exposure limits, stopping rule before exposure; register salt/holdout in the registry (WO-04). Acceptance: registration record timestamped before first exposure; independent MEAS sign-off. Depends on: WO-04, WO-10, WO-40.
WO-42 · [G] · P1 · ENG — One complete decision-to-outcome trace
Fix: produce the compact decision record (§7): tenant/account/action, evidence versions, baseline, distributions, approved spend/horizon, constraint snapshot, registration, approver, authorization + reservation IDs, provider pre/post state, rollback state, mature outcome. Acceptance: one real decision with every field populated from system records, no manual fill. Depends on: all P0 in B and C; WO-41.
WO-43 · [G] · P1 · OWNER — Readout and promotion decision
Fix: report incremental contribution after acquisition with uncertainty, iCAC, payback, reconciled coverage, duplicate/unknown-action counts; decide next investment by conservative benefit / total cost ≥ 10 hurdle. Acceptance: readout may say "insufficient evidence"; no public claim without evidence-class enforcement. Depends on: WO-42.
Addendum 2026-09-08 — tickets from the source-verified review v1.1 (docs/reports/MIZOKI_ACQUISITION_ECONOMICS_REVIEW_v1.1_2026-09-06.md, main 635318712)
These six were found by reading current source and OPEN_ITEMS.md after the audit was pinned; none duplicates WO-00..43. Package letters place them in the existing order; WO-44 and WO-46 gate work the other prompts assume is possible.
WO-44 · [H] · P0 · OWNER — Org transfer: rule A/B and restore the homepage deploy lane
Finding (measured 2026-09-04, PR #948 body; docs/runbooks/ORG_MIGRATION_HOMEPAGE_LANE_REPAIR_2026-09-04.md): MIZOKICloudRun moved from the mediaintelligence account into org MIZOKI-3-5 on 2026-09-04. deploy-homepage.yml has failed since 2026-09-03T00:44Z and nobody noticed (dispatch-only). Five guards fail: (1) repo-identity literal — fixed by #948; (2) ACTOR_ID != OWNER_ID structurally unsatisfiable in an org; (3) WIF pool attributeCondition and (4) homepage-prod-deployer@ principalSet are GCP-side; (5) ref_protected is FALSE and CANNOT be set — rulesets/branch protection on a private repo in a Free org return 403. The same plan limit blocks WO-26 (protect the exact commit that ships / V4-17) and environment required_reviewers.
Files: .github/workflows/deploy-homepage.yml, .github/workflows/fix-homepage-deploy.yml; tests/governance/test_homepage_deploy_guard.py; GCP WIF pool + SA binding (owner gcloud, runbook §5, new binding before old).
Fix: Owner ruling A — recommended A1: explicit actor-id allowlist {163805125} (already pinned by #963/037233b05); A2 environment required_reviewers added once the plan allows. Owner ruling B — recommended B1: upgrade org to GitHub Team (restores rulesets, ref_protected, unblocks WO-26/V4-17, enables A2). B2 (make repo public) is irreversible — not recommended. B3 (drop guard 5) asserts a protection the platform no longer enforces — refuse. Then run the two GCP commands and dispatch one homepage deploy.
Acceptance: rulings A and B recorded in OPEN_ITEMS.md; gh api repos/MIZOKI-3-5/MIZOKICloudRun/branches/main -q .protected returns true; one owner-dispatched deploy-homepage.yml run passes all five guards and live-verifies on mizoki3.com. Until then homepage production deploys are honestly BLOCKED — never patched green.
Depends on: none. Blocks: WO-26.
Owning prompt: owner (rulings, billing, gcloud); CX-4 for any workflow edit.
WO-45 · [F] · P1 · ENG — Retire the v22 uploadClickConversions rail; Data Manager rail is the pilot path
Finding (confirmed on main 635318712): services/measurement-rails/offline_conversions.py:30 pins GOOGLE_ADS_API_VERSION = "v22" and builds customers/{id}:uploadClickConversions payloads (line 124). Two clocks run against it: since 2026-06-15 the Ads API refuses NEW adopters of offline conversion imports (official post 2026-05-15; CUSTOMER_NOT_ALLOWLISTED_FOR_THIS_FEATURE; only developer tokens with Dec 2025–May 2026 import history keep access) — a first design partner has no such history under our token; and v22 sunsets ~2026-10-07 (secondary source; confirm on the official sunset page). This is a SECOND sunset-pinned Google site beside WO-24's google_ads.py v21. services/service-data-manager-connector already speaks datamanager.googleapis.com/v1/events:ingest (WO-25 fixes its contract).
Files: services/measurement-rails/offline_conversions.py, services/measurement-rails/test_rails_offline.py, services/measurement-rails/flags.py; connector-health panel adapter in miz-oki-command-center-ui/lib/bff/adapters/.
Fix: keep the provider-neutral front half (gclid→gbraid→wbraid precedence, 90-day window, before-click rejection — unit-tested) and route the send through the Data Manager connector; the legacy uploadClickConversions builder becomes compatibility-only behind an explicit LEGACY_ADS_API_OFFLINE=true flag that refuses when the pinned version is past its recorded sunset date or the tenant has no recorded pre-2026-06-15 import. Add Data Manager developer-token/allowlist eligibility to connector health.
Acceptance: test: default path never emits an uploadClickConversions payload; test: legacy path refuses past sunset and without allowlist evidence; validate-only events:ingest accepted in a test account (shared with WO-25); CI asserts no GOOGLE_ADS_API_VERSION literal in the tree is on Google's sunset list (extend WO-24's check to all sites).
Depends on: WO-00, WO-25.
Owning prompt: CC-4 (services/measurement-rails/**); the sunset-list CI assertion lands with CX-3/WO-24.
WO-46 · [A] · P0 · OPS — Re-authorize the GitHub App for org MIZOKI-3-5 and repoint every stale repo reference
Finding: The 2026-08-18 account-level GitHub App grant was made on mediaintelligence; an org installation is a separate authorization. Cloud/fleet Claude sessions, the Sep 4 review and the create_issues batch all fail on the repo (404 / token refuses). Memory ledger 4b3208ff0 notes the token lacks admin:org. #953 fixed in-repo URLs, but prompt-board files on Drive, scheduled triggers and any connector config that name mediaintelligence/MIZOKICloudRun are still stale.
Files: GitHub org settings (install github.com/apps/claude on MIZOKI-3-5, grant the repo); Drive prompt-board folder 1942_8C4Dsj6hmARrBogNsy7lVZP-sKkx; scheduled-task prompts; docs/audits/wo/create_issues.sh (REPO default already correct).
Fix: install/authorize the App on the org; start a NEW cloud session afterwards (credentials are scoped at session start); grep the board and triggers for mediaintelligence/MIZOKICloudRun and repoint; then run create_issues.sh --dry-run from a credentialed shell.
Acceptance: a fresh cloud session can git ls-remote the org repo; create_issues.sh --dry-run lists all WO-nn; zero stale references on the board.
Depends on: none. Blocks: every CC/CX prompt that runs in the cloud, and the issue batch.
Owning prompt: owner/operator (WO-40..43 class); CX-1 records the result in the reconciliation table.
WO-47 · [B] · P0 · SEC — Tenant-check DCP GET /api/v1/decision/{id}
Finding (confirmed on main 635318712; OPEN_ITEMS S3-4): services/service-decision-control-plane/main.py:504-508 get_decision returns any DecisionProof by id to any allow-listed caller — STORE.get("decision_proofs", decision_id) with no resolve_tenant call, while the sibling list_decisions (line 526) and decisions_summary (549) do resolve tenant against the caller. mcp-server checks the stored tenant_id itself, but the upstream route must too; passport assembly (S3-3) reads through this route.
Files: services/service-decision-control-plane/main.py; its tests.
Fix: resolve the caller's tenant and refuse (404, never 403 that confirms existence) when doc["tenant_id"] does not match; same rule on /decision/{id}/chain and the passport GET. Pairs with WO-03 (passport binding) and WO-05 (approver identity).
Acceptance: negative test over HTTP: tenant B caller requesting tenant A's decision id gets 404 and no body fields; positive test unchanged; the same test against /chain and /passport/{id}.
Depends on: WO-00.
Owning prompt: CC-1 (services/service-decision-control-plane/**).
WO-48 · [H] · P1 · ENG — Run the orphaned test suites in CI
Finding (OPEN_ITEMS W3-CI-1, measured): no workflow runs tests/mcp, tests/spec, tests/gtm, packages/truthgate/tests, tests/shared/test_origin_{schema,strata,classifier}.py, tests/shared/test_agent_share_threshold.py, tests/test_origin_shadow_ddl.py on PR or merge (ci.yaml:140 names one file, not the directory). Their tenant-isolation, read-only-manifest, consent and flag-pin gates are enforced only when someone runs them locally. Separately, scripts/mizoki_canon.py has no CLI — --check is silently ignored (library only); the canon gate is scripts/skill_sync.py --audit.
Files: .github/workflows/ci.yaml (protected path → review PR); tests/governance/ (new pin test).
Fix: one step beside the governance-gates job: pytest tests/mcp tests/spec tests/gtm packages/truthgate/tests tests/shared tests/test_origin_shadow_ddl.py -q -p no:cacheprovider -o addopts=""; plus a governance test asserting no workflow/Makefile/doc invokes mizoki_canon.py --check and that skill_sync.py --audit is the named canon gate.
Acceptance: the step is green on a PR; a seeded failure in tests/mcp reddens CI; the canon-invocation pin passes.
Depends on: WO-00. Same review PR as WO-26/27 where practical.
Owning prompt: CX-4 (.github/workflows/**).
WO-49 · [A] · P0 · OWNER — Execute the PII history purge across ALL copies, not the repo alone
Finding (OPEN_ITEMS V4-4 + memory ledger a1ec34ccc, 9454163f4, 2026-09-05): docs/misc/mycocoons_customers.csv (real PII, Option A decided 2026-09-02) — serving exposure closed and live-verified 2026-09-02T19:48Z, but the history purge is NOT executed; scope is seven repository-history paths across all refs (the original five on main plus two side-branch-only paths, corrected in docs/reports/WS0_PII_SCANNER_REPORT_2026-09-01.md §10.2). The 5 Sep audit adds: the purged CSV is still tracked in the MIZ clone, and the export exists in at least three places, one in a local folder that looks like Google Drive but is not.
Files: repo history (seven paths across all refs in docs/reports/WS0_PII_SCANNER_REPORT_2026-09-01.md §10.2); the MIZ clone; the local pseudo-Drive folder; any Drive mirror.
Fix: owner-run maintenance window after the audit lanes land (history rewrite invalidates every open audit/* branch — sequence it); force-push under ruling; every clone re-cloned; delete the non-repo copies; record closure evidence (git log --all -- <path> empty for each repo-history path; file hashes absent from the MIZ clone, local pseudo-Drive folder, and any Drive mirror) without copying any customer row into a report.
Acceptance: closure evidence in OPEN_ITEMS.md V4-4 for all copies; scanner report re-run clean.
Depends on: none (but sequence AFTER the audit/* merges). Owning prompt: owner (WO-40..43 class).
| Required proof | Ticket |
|---|---|
| Homepage lane deploys under a real protection | WO-44 |
| No sunset-pinned Google site in the tree | WO-24, WO-45 |
| Cloud sessions reach the org repo | WO-46 |
| Cross-tenant decision read refused | WO-47 |
| Orphaned suites gate CI | WO-48 |
| PII absent from every copy | WO-49 |
Acceptance pack (report §9) → ticket map
| Required proof | Ticket |
|---|---|
| Negative hard-gate tests | WO-01, WO-02 |
| Foreign / stale / altered passport refusal | WO-03 |
| Real role + tenant authorization over HTTP | WO-05, WO-15, WO-16, WO-21 |
| Concurrent single-approval redemption | WO-07 |
| Two-tenant BigQuery economics invariance | WO-08 |
| Refund replay / crash / order tests | WO-09 |
| Restart-safe exposure / freeze | WO-19, WO-20 |
| Provider validate-only + read-back / recovery | WO-24, WO-25 |
| Independent effect evaluation | WO-10..13 |
| Complete observed customer decision-to-outcome trace | WO-42 |
Push to GitHub Issues (on your go — not run)
# from repo root, gh authenticated to MIZOKI-3-5
gh label create P0 --color B60205 -f; gh label create P1 --color D93F0B -f
gh label create P2 --color FBCA04 -f; gh label create P3 --color 0E8A16 -f
for l in pkg:A pkg:B pkg:C pkg:D pkg:E pkg:F pkg:G pkg:H lane:ENG lane:SEC lane:MEAS lane:CUST lane:OPS lane:OWNER audit-2026-09-06; do gh label create "$l" -f; done
# then one `gh issue create --title "WO-01 …" --label P0,pkg:B,lane:ENG,audit-2026-09-06 --body-file wo/WO-01.md` per ticket
Split this file into wo/WO-nn.md bodies with a 20-line script, or say the word and I'll generate the per-ticket files and the gh batch.