CX-2_CODEX.txt

You are working in the MIZOKI-3-5/MIZOKICloudRun repository (MIZ OKI 3.5, a governed
decision-intelligence platform: 39 Cloud Run cells, BigQuery, Firestore, Next.js command-center UI).
Read in this order before touching code: CLAUDE.md, CONSTITUTION.md (Article VI governs governance
surfaces), docs/audits/AUDIT_WORK_ORDERS_2026-09-08.md, docs/audits/wo/<your WO files>, and
docs/audits/AUDIT_2026-09-06_RECONCILIATION.md if it exists (CX-1 writes it).

Ground rules
- Audit findings are HYPOTHESES pinned at commit fc8b03f9. main has moved. Step 0 of every WO is:
  reproduce the counterexample on current main with a failing test. If it does not reproduce,
  record "not reproduced on <sha>" in your report and move on — do not fix what is not broken.
- Fail closed. Every fix must make a refusal path explicit and tested. Never widen access to make a test pass.
- No new architecture. Reuse the existing modules named in the WO. If a WO says "reuse X", reuse X.
- Tests are the deliverable. Each WO lists acceptance tests; write them first, watch them fail, then fix.
  Name them test_wo<nn>_<what>. Keep the audit's synthetic counterexample numbers ($40 refund → $80,
  $70+$70 vs $100 cap, DEL 91.7, etc.) as fixtures so the regression is recognizable.
- Branch: audit/<PROMPT-ID>-<slug>. NEVER use a claude/* branch (they auto-merge to main in seconds).
  Commit per WO with message "WO-nn: <title>". Open ONE PR for the prompt when done. Do not merge.
- Protected paths (.github/**, deployment/terraform/**, deployment/cloudbuild*, CODEOWNERS): review PR only.
- Never write a typed gate token in full anywhere (docs, commits, PR bodies, reports): the gate-leak
  ratchet (tests/test_gate_leak_scan.py) turns main red. Refer to it only as APPROVED: [MERGE].
- Do not deploy, do not change Cloud Run config, do not touch secrets, do not run anything against
  production BigQuery/Firestore, do not spend money on any provider. Local + test fixtures only.
- Coordination: before starting, run
    python scripts/claude_memory.py record --title "<PROMPT-ID> claim" --summary "<WOs> on branch <name>" --tags coordination
  if the script exists; if not, add a line to docs/audits/COORDINATION.md.
- Stop and report (do not guess) if: a fix needs a new secret, a provider account, an IAM change,
  a schema migration on a live dataset, or a change to a file owned by another prompt (see the
  ownership table in docs/audits/AUDIT_EXECUTION_PROMPTS_2026-09-08.md).

Final report — write docs/audits/reports/<PROMPT-ID>_REPORT_<date>.md with, per WO:
  status (fixed | not reproduced | blocked), repro test name + first failing run, fix summary,
  files changed, acceptance tests + pass evidence, anything deferred and why. End with the PR URL,
  the exact test command(s) that prove the pack, and the commit SHA the PR is based on.

PROMPT-ID: CX-2. Branch: audit/cx-2-ui-authz-product-truth.
Work orders: WO-15 (P0), WO-16 (P0), WO-22 (P2), WO-23 (P2), WO-33 (P3). Lane: SEC + ENG + CUST.
You own: miz-oki-command-center-ui/app/api/** EXCEPT app/api/bff/lanes/** (CX-1), plus the website
pilot intake, which CX-1 resolved to `# MIZ OKI 3.5/mizoki_runtime/pilot_requests.py` and `# MIZ OKI 3.5/app.py`
(in this repo; see docs/audits/AUDIT_2026-09-06_RECONCILIATION.md on branch audit/cx-1-current-truth / PR #1015). TypeScript/Next.js/Vitest.
Reference implementation for role + actor checks: app/api/bff/actions/authorize/route.ts and its
route.test.ts — copy its pattern, do not invent a new one.

WO-15 Role-check the onboarding mutation handlers
  Files: app/api/bff/tenant-economics/save/route.ts; the tenant cost save route; the connector
         credential save route under app/api/bff/connectors/ (find it: grep "credential" in
         app/api/bff/connectors). Also the backend economics/cost routes these call — find them in
         the BFF's fetch targets and note them; if they are Python services, write the required
         backend change as a patch file under docs/audits/patches/ and report it (backend is CC/CX-3 territory).
  Step 0: Vitest: construct a viewer identity for tenant T, stub the adapters, call all three save
          handlers; assert 200 and that the adapter was invoked. Failing test.
  Fix: apply the authorize route's role + actor + tenant check to all three; role must be admin (or
       the role the authorize route uses for mutations); actor must be the verified principal.
  Accept: viewer → 403 on all three and NO adapter call; admin → 200; wrong-tenant admin → 403.

WO-16 Route → required-role inventory enforced in CI
  Fix: a script (scripts/ui_route_roles.ts or .py) that walks app/api/**/route.ts, detects mutating
       handlers (POST/PUT/PATCH/DELETE), and requires each to export or annotate `requiredRole`
       (pick the mechanism the authorize route already uses, or add a small `withRole()` wrapper);
       emit a table to docs/audits/UI_ROUTE_ROLES.md. A Vitest test fails if any mutating route lacks it.
  Accept: test green on your branch; the table lists every mutating route with a non-null role.

WO-22 Quarantine the legacy omnichannel allocator
  Files: app/api/omnichannel/allocation/route.ts.
  Step 0: call with no input; assert it currently returns a budget ($68,000) and projected revenue
          ($281,835.51); call with channel data missing clicks; assert a numeric conversion rate is
          returned (267,000%).
  Fix: missing live evidence → 422 {error: "evidence_unavailable", missing: [...]}; any simulated
       output must carry {mode: "simulation"} and must never be returned from the live path; guard
       every denominator (null, never a number); assert allocated + unallocated === budget or throw.
  Accept: zero-input → 422; missing clicks → rate null; sum invariant test; simulation mode explicit.

WO-23 No path from the legacy allocator into DCP
  Fix: a static test (Vitest or a small script in CI) asserting that no file outside
       app/api/omnichannel/** imports the allocator's output type or calls its route, and that DCP
       proposal intake never accepts an object with the allocator's shape. Add a deprecation banner
       comment and a `X-MIZOKI-Legacy: allocation` response header.
  Accept: CI test present and green; grep evidence in the report.

WO-33 Wire the site pilot request callback
  Files: per CX-1's reconciliation table. If the code is in another repo, write the fix there on a
         branch of the same name and report both PRs; if it truly does not exist, report "not in
         scope" with evidence.
  Fix: connect the request construction to the pilot intake route; on success persist a record;
       on failure surface the error to the user (no silent drop).
  Accept: submit test request → intake record exists; failure path shows an error.

Gates: npx vitest run; npx tsc --noEmit; next build if it runs in CI.
PR title: "Audit pack C/F (UI) — mutation role checks, allocator quarantine, pilot callback (WO-15,16,22,23,33)".
← All docsView source on GitHub →