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.

← All docsView source on GitHub →