CC-1 report — Audit pack B: spending admission hard gates (WO-01..07, WO-47)
Date: 2026-09-09 · Branch: audit/cc-1-spending-gates-90e88i · Base: main @ 26bec9c6b424ca4b86b35621602f7a06764e5afe (merge of #1012)
Lane: ENG + SEC · Source: docs/audits/AUDIT_EXECUTION_PROMPTS_2026-09-08.md §CC-1, docs/audits/wo/WO-0{1..7}.md, WO-47.md
Claim: claim: CC-1 audit pack B spending admission hard gates (ledger, 2026-09-09; commit 33672f7)
PR: https://github.com/MIZOKI-3-5/MIZOKICloudRun/pull/1016 — MERGED by the owner 2026-09-09T21:42:51Z as merge commit 4f4c064 (see Post-merge state at the end; the body below is the pre-merge report, unchanged).
Every finding in this pack was treated as a hypothesis and re-run on current main
before anything was changed (Step 0). All eight reproduced on 26bec9c. Each fix
makes a refusal path explicit, coded and tested; nothing widens access. The four
services' suites (tests/governance, which is the six governance services' own suite),
tests/remediation, tests/connectors and the shared content gates are green on the
branch head.
Non-independence disclosure (rule 01): the same session wrote every fix and every test in this report. The Step-0 reproductions were run before the fixes and their first failing outputs are quoted verbatim below; the acceptance runs are the same session's measurements, not an independent verifier's.
Summary table
| WO | Status | Repro test (first run FAILED on 26bec9c) | Fix, one line |
|---|---|---|---|
| WO-01 | fixed | test_wo01_failed_incremental_profit_alone_is_ineligible_not_del_91_7 |
HARD check taxonomy; any HARD failure → BLOCKED before DEL |
| WO-02 | fixed | (companion; acceptance tests only) | explicit eligibility_class=exploration with approved envelope + cap + logged probability; not exempt from HARD |
| WO-03 | fixed | test_wo03_foreign_tenant_passport_with_invalid_seal_cannot_authorize |
passport resolved as immutable, sealed, tenant/path/domain/validity-bound record; binding inside the signed authorization |
| WO-04 | fixed | test_wo04_proposer_boolean_alone_no_longer_proves_experiment_evidence |
registry proof of holdout (tenant, registered_at < first exposure, salt_version); proposer boolean ignored |
| WO-05 | fixed | test_wo05_service_principal_with_a_human_body_actor_is_403 |
approver identity + role from the verified principal only |
| WO-06 | fixed | test_wo06_flag_true_with_no_proof_is_refused |
Stage 4 requires a stored RollbackProof for the exact tenant/account/action class |
| WO-07 | fixed | test_wo07_two_concurrent_redemptions_yield_one_authorization |
atomic claim → persist → complete; deterministic authorization id; verbatim replay |
| WO-47 | fixed | test_wo47_tenant_b_caller_cannot_read_tenant_a_decision |
tenant-scoped by-id reads answer 404 (never 403) on DCP, chain and passport routes |
Commits, one per WO (WO-01 and WO-02 share one commit because the exploration class
lives in the same evaluate() branch the hard gate rewrites):
785b737 WO-01/WO-02: hard-gate failures are terminal; bounded exploration class
2903834 WO-03: bind evidence passports to the decision
745b7b1 WO-04: holdout registration is proved, not asserted
03a6880 WO-05: approver identity from authentication only
3f3f1bd WO-06: rollback proof artifact before promotion
ba500ef WO-07: single-use approval redemption under concurrency
3ab2eab WO-47: tenant-scope DCP get_decision, decision chain and passport reads
WO-01 — Hard-gate failures are terminal
Status: fixed.
Step 0. tests/governance/test_wo01_02_hard_gates.py::test_wo01_failed_incremental_profit_alone_is_ineligible_not_del_91_7
issues a real media passport through the validation orchestrator with the audit's $40
revenue → $80 media-cost shape (incremental_profit FAILS, the other five checks pass,
pass_rate == 5/6) and proposes it with evidence_completeness=1.0,
verification_weight=1.0. First run on 26bec9c:
E assert 'eligible' == 'blocked'
with del_score == 91.7 and a signed authorization in the response — the audit's
[R1][R2] counterexample, verbatim.
Fix.
- contracts/mizoki_contracts/validators.py: the ONE check taxonomy — CHECK_CLASSES
(economic / integrity / consent / policy = HARD_CLASSES; rank otherwise),
check_class(name), hard_failures(checks). An unnamed check that failed is HARD
(class unknown) — fail closed. Media: incremental_profit=economic,
consent_coverage=consent, causal_refutation+incrementality_evidence=integrity,
mmm_agreement+creative_fatigue=rank. Finance and CRE batteries classified too.
- ValidationCheck.check_class (additive, None on passports sealed before it); the
validation orchestrator stamps it on every issued check, reports the taxonomy on
/health, and audits hard_failures per passport.
- Policy engine: EvalRequest.checks (per-check verdicts). evaluate() computes the
hard failures FIRST (after the treasury/supply vetoes, before materiality, the floor,
and the DEL) and returns BLOCKED with reason codes HARD_CHECK_FAILED:<class>:<name>,
hard_failures=[…], del_score=0.0 ("DEL not computed"). The class is re-derived by
NAME — a caller's check_class label is never trusted. Not a weight tweak.
- DCP forwards the passport's checks to the engine and stores hard_failures and the
class on the decision record; a hard-blocked decision is not redeemable (409).
Files: contracts/mizoki_contracts/validators.py, contracts/mizoki_contracts/decision_objects.py,
services/service-validation-orchestrator/main.py, services/service-policy-engine/main.py,
services/service-decision-control-plane/main.py, tests/governance/test_wo01_02_hard_gates.py,
tests/remediation/test_end_to_end.py (ACT-991 scenario re-cut to rank-only weakness; DEL 41 kept).
Acceptance.
- (a) test_wo01_each_hard_check_failing_alone_is_ineligible[…] — each HARD media check
failing alone, all others 100% → blocked, no approval route, its code in reasons.
- (b) test_wo01_property_no_rank_vector_flips_a_hard_failure — seeded property sweep:
boundary grid × 150 random vectors of (pass_rate, evidence_completeness,
verification_weight), random rank pass/fail, random value/reversibility; a HARD failure
never flips. (No hypothesis dependency: adding one to the governance image is a
.github/** change; the sweep is deterministic under seed 0xB01.)
- (c) test_wo01_fully_passing_battery_is_still_eligible (DEL 96.5, signed) plus the
whole pre-existing policy-engine / DCP / meter suites unchanged in outcome.
- Also: relabelling a HARD check as rank changes nothing; unknown failed checks are
HARD; rank-only failures still route on DEL; the validator emits {name, class, passed}.
Owner decision surfaced — F4 calibration lane. services/growth-scheduler/main.py
_seal_passport seals the F4 passport with media_cost and geo counts only, so its
honest media battery fails consent_coverage (0/0), incremental_profit (−cap),
incrementality_evidence and causal_refutation — all HARD. Under the 2026-08-24 ruling
that passport routed to the human queue (calibration floor 0.0, approval_only); under
WO-01 it is BLOCKED. This is pinned on purpose by
test_wo01_f4_shaped_calibration_passport_is_now_blocked_by_hard_checks so the change is
a decision at review, never a surprise at deploy. Resolutions, either of which is a
separate change: (i) the lane supplies evidence that passes the HARD checks (consent on
the geo panel is measurable; the economic check needs a stated basis), or (ii) a
DECLARED, bounded, test-pinned exemption on the calibration domain (which is already
actuator/action-bound) — never a hidden one. Nothing in this pack deploys; the live F4
pilot is unaffected until a dispatch carries this change.
WO-02 — Bounded exploration class
Status: fixed (companion to WO-01; no separate Step 0 — the audit's exploration path WAS the averaging WO-01 removes).
Fix. eligibility_class ∈ {standard, exploration} on EvalRequest and
ProposeRequest. Exploration requires exploration_envelope.envelope_id naming an
APPROVED, tenant-bound record in exploration_envelopes (cap read from the STORE, never
the request), estimated_value ≤ cap_usd, and a logged assignment_probability ∈ (0,1].
It relaxes only the DEL bar, still obeys the value ceiling and reversibility, and is
exempt from no HARD check. Refusal codes: EXPLORATION_ENVELOPE_MISSING / NOT_FOUND /
NOT_APPROVED / FOREIGN_TENANT / CAP_INVALID, EXPLORATION_OVER_CAP,
EXPLORATION_ASSIGNMENT_PROBABILITY_INVALID. POST /api/v1/exploration-envelopes
registers an envelope (tenant-resolved, attributed, immutable). The class, envelope and
probability are stored on the decision record (decision_requests) and audited.
Acceptance (tests/governance/test_wo01_02_hard_gates.py): no envelope → refused;
unknown / foreign / over-cap envelope → refused; no or invalid probability → refused;
HARD failure under exploration → refused; valid exploration → eligible with the envelope
echoed and the probability logged; the domain ceiling still applies; class stored on
the decision record; unknown class → 422.
WO-03 — Bind evidence passports to the decision
Status: fixed.
Step 0. tests/governance/test_wo03_passport_binding.py::test_wo03_foreign_tenant_passport_with_invalid_seal_cannot_authorize:
passport issued for gov-other-tenant, body altered, immutable_hash set to an invalid
seal, actuator registry staged to Stage 4. First run on 26bec9c: HTTP 200 with
"stage":"stage-4-bounded-autonomy" and a signature — the [R3] shape.
Fix. DCP resolve_bound_passport() runs before any decision document exists and
refuses with 422 + code: PASSPORT_FOREIGN_TENANT (tenant ≠ resolved caller tenant),
PASSPORT_SEAL_INVALID (no well-formed seal), PASSPORT_ALTERED (re-derived seal ≠
stored seal — computed over the STORED dict so pre-existing passports still verify),
PASSPORT_STALE (outside PASSPORT_MAX_AGE_SECONDS, default 24h, skew 120s),
PASSPORT_ACTION_MISMATCH (path ≠ chosen_path_id, or domain not permitted —
DOMAIN_PASSPORT_BINDINGS = {"calibration": ("media","calibration")} is the one declared
cross-domain binding, pinned). model_version and measurement_window (horizon) are
required (PASSPORT_BINDING_INCOMPLETE). The bound fields — passport id + seal,
passport tenant, path, domain, model version, horizon, issued/valid-until and
decision_fingerprint = sha256(tenant|decision|actuator|action|bounds) — ride INSIDE the
signed ActionAuthorization as the additive evidence_binding field, so the runner's
HMAC covers them; verify_evidence_binding() re-verifies a grant against the ledger.
Files: services/service-decision-control-plane/main.py, contracts/mizoki_contracts/decision_objects.py
(ActionAuthorization.evidence_binding), tests/governance/test_wo03_passport_binding.py,
tests/governance/conftest.py (propose() binds model version/horizon and follows the
passport's path), tests/remediation/* helpers (binding fields), src/shared/mizoki_governance/client.py
(passthrough kwargs), tests/governance/test_decision_control_plane.py (advisory-only case
now rides a CRE passport), tests/governance/test_passport_chain.py (honest-absent
model_version superseded: refused by code).
Acceptance: one test per code (foreign tenant, seal invalid, altered, stale, path mismatch, domain mismatch, binding incomplete); valid bound passport → authorized with all ten binding fields inside the signed body, signature verifies at the runner, re-verification OK; tampering EACH bound field after signing → signature fails and the runner refuses 403 (parametrized over seven fields); a passport rewritten after issue is caught by re-verification; the approved path carries the same binding.
Consequences reported, not changed here: proposers that do not declare
model_version + measurement_window are now refused at propose with
PASSPORT_BINDING_INCOMPLETE: services/growth-scheduler/main.py (F4 sends the window
but no model version), src/cells/cell37/market_cell/main.py, ops/remediation/live_proof.py,
and every mizoki_governance.client.propose caller (the client gained the kwargs; callers
must pass them). Authorizations signed before the evidence_binding field existed no
longer verify at the runner (fail closed; re-propose) — a ≤1h TTL window at deploy.
WO-04 — Holdout registration is proved, not asserted
Status: fixed.
Step 0. tests/governance/test_wo04_holdout_registry.py::test_wo04_proposer_boolean_alone_no_longer_proves_experiment_evidence:
has_experiment_evidence=True, no registered holdout. On 26bec9c: eligible with a
signed authorization (the boolean alone sufficed).
Fix. contracts/mizoki_contracts/holdouts.py::resolve_holdout_registration(store, tenant_id, holdout_id, first_exposure_at)
— the ONE resolver over holdout_registrations (pilot_report.HOLDOUT_COLLECTION):
sufficient only when the record exists for THIS tenant, is well formed
(registered_at, salt_version), and registered_at < first_exposure_at strictly —
the registry's measured first_exposure_at beats the proposer's claim, which beats now.
Codes: HOLDOUT_ID_MISSING / UNREGISTERED / FOREIGN_TENANT / RECORD_INVALID /
REGISTERED_AFTER_EXPOSURE. DCP: ProposeRequest.holdout_id (or bounds.holdout_id) and
first_exposure_at; the proposer boolean is IGNORED (recorded for audit only); the
verdict drives the engine's has_experiment_evidence and is stored on the decision
record; a proven holdout is stamped onto the bound action so the runner re-proves it.
POST /api/v1/holdouts/register (server-stamped registered_at, immutable, cannot be
backdated — extra="forbid"). Action runner require_proven_holdout() runs before the
single-use dispatch claim for intent-driven actions or any holdout id: 403 by code,
nothing sent, authorization unconsumed; resolve_holdout_registration is re-exported
from main.py for the adapters.
Files: contracts/mizoki_contracts/holdouts.py (new), services/service-decision-control-plane/main.py,
services/service-action-runner/main.py, tests/governance/test_wo04_holdout_registry.py,
tests/governance/conftest.py (register_holdout; propose() rides one by default),
remediation helpers, tests that meant "no experiment evidence" now pass holdout_id=None.
Acceptance: resolver — unregistered, post-exposure (request-claimed and
registry-measured), other-tenant, malformed → insufficient; registered → passes with the
record. DCP — the three refusals → experiment-required with the code stored; registered
→ eligible with bounds.holdout_id on the signed grant; registration route stamps and is
immutable; backdating → 422. Runner — the three refusals → 403 HOLDOUT_*, authorization
unconsumed; registered → dispatches; intent-driven without id → HOLDOUT_ID_MISSING.
Gaps reported: (1) services/service-action-runner/execution_adapters/base.py::check_holdout
(CC-2) still checks presence only; the interface it should call is
main.resolve_holdout_registration / main.require_proven_holdout. (2) No producer in
this repo writes holdout_registrations today (Cell 36 registration is external;
pilot_report only reads it). Until registrations land — via the new DCP route or the
collection — every media candidate is honestly experiment-required. That is the
fail-closed posture CONSTITUTION II.10 asks for, and it matches the platform's own
"first real registered holdout is the open gate" state.
WO-05 — Approver identity from authentication only
Status: fixed.
Step 0. tests/governance/test_wo05_approver_principal.py::test_wo05_service_principal_with_a_human_body_actor_is_403:
service principal S sends actor="some human". On 26bec9c: 200, approved_by="some human".
Fix. service-approval-routing: resolve_principal (dependency over
verify_caller) classifies the verified caller. Service accounts / local-dev are
SERVICE principals and can never satisfy a HUMAN approval (403 APPROVER_NOT_HUMAN); a
human's roles come only from the approver registry (MIZOKI_APPROVER_ROLES env seed +
approver_roles store collection), and the request's route is the role required
(403 APPROVER_ROLE_MISSING). A registered identity broker (MIZOKI_APPROVER_BROKERS
— the command-center BFF service account, which already authenticated the human
session) may relay the human id in x-mizoki-principal; any other caller's header is
ignored and a broker cannot relay a service id. Body actor is optional and must equal
the principal (400 APPROVER_ACTOR_MISMATCH). The stored approval carries the
principal's verified id, kind, roles and broker; D6 self-approval is still refused.
Files: services/service-approval-routing/main.py, tests/governance/test_wo05_approver_principal.py,
tests/governance/conftest.py (DEFAULT_HUMAN, as_principal, human, service),
tests/governance/test_approval_routing.py, tests/governance/test_treasury_gate.py,
tests/remediation/conftest.py (install_human_principal) + three remediation files.
Acceptance: service + body-human → 403; human wrong role → 403; human right role →
200 with approved_by = verified id (not the body); actor mismatch → 400; body actor
never the stored identity; denial under the same rules; classification of SA /
local-dev / direct human OIDC / broker relay; broker relay over HTTP uses the header, not
the body; the suite's own transport identity (local-dev) cannot approve without the
override; DCP redeems only the verified approver.
Operator actions (deployment config, not done here — the pack forbids Cloud Run
changes): seed MIZOKI_APPROVER_ROLES, register the BFF SA in
MIZOKI_APPROVER_BROKERS, and have the BFF send x-mizoki-principal (its
lib/bff/approval-mutations.ts already derives the actor from the session; CX-2 owns
app/api/**). Until then no approval can be granted — fail closed, and honest on
/health (approver_roles_seeded, identity_brokers). Stronger form not done (needs a
Track O secret): verifying the Supabase session JWT directly on approval-routing.
WO-06 — Rollback proof artifact before promotion
Status: fixed.
Step 0. tests/governance/test_wo06_rollback_proof.py::test_wo06_flag_true_with_no_proof_is_refused:
register + /actuators/demonstrate (flag flipped on request) then promote. On
26bec9c: 200, Stage 4 granted with no drill anywhere.
Fix. RollbackProof {drill_id, tenant_id, account, action_class, actuator,
executed_at, outcome, evidence_ref} — POST /api/v1/rollback-proofs stores it
immutably (409 on re-record; outcome vocabulary; executed_at in the past;
evidence_ref and actuator required). Shape follows ops/remediation/live_proof.py's
drill record (read only, unchanged). Promotion to Stage 4 names its scope (tenant_id,
account, action_class — 422 otherwise) and requires a stored SUCCESSFUL proof for
exactly that scope drilled through this actuator (403 ROLLBACK_PROOF_MISSING); the
proof is recorded on the actuator (stage4_proof) and audited; walking back clears it.
Registration's rollback_demonstrated and /demonstrate are advisory only. Dispatch: a
Stage-4 row with no recorded proof is held (ROLLBACK_PROOF_MISSING); an authorization
whose tenant / action class the proof does not cover is refused
(ROLLBACK_PROOF_SCOPE_MISMATCH) — before the single-use claim, nothing sent.
Files: services/service-action-runner/main.py, tests/governance/test_wo06_rollback_proof.py,
tests/governance/test_action_runner.py, tests/governance/test_fail_closed.py,
tests/governance/conftest.py (earn_stage4), tests/remediation/conftest.py (earn_stage4),
tests/remediation/test_end_to_end.py, test_services_hardening.py, test_execution_adapters.py
(promotion helpers switched to proofs).
Acceptance: flag=True + no proof → refused; proof for a different action class →
refused; different tenant / account → refused; failed drill → refused; proof naming
another actuator → refused; missing scope → 422; future / evidence-less / duplicate proof
→ 422 / 409; matching proof → promotion allowed and recorded; /demonstrate earns
nothing; walk-back needs no proof; dispatch bound to the proof's tenant and action
class; legacy Stage-4 row without a proof never dispatches; proof shape pinned.
Reported: ops/remediation/live_proof.py step 7a earns Stage 4 through
/demonstrate and will now get 403; the drill needs to POST /api/v1/rollback-proofs
after its rollback step. Not modified (read-only for this pack).
WO-07 — Single-use approval under concurrency
Status: fixed.
Step 0. tests/governance/test_wo07_single_use_redemption.py::test_wo07_two_concurrent_redemptions_yield_one_authorization
with a barrier store handing each thread an independent snapshot (as Firestore does —
the memory store aliases its dicts, which masked the race on the first attempt). On
26bec9c: two 200s, {'AUT-205680917638464c', 'AUT-c7cd8f1d38a248a5'} — two distinct
signed authorizations for one approval.
Fix. DCP authorize_approved is three atomic phases over Store.transact_update
(a real Firestore transaction in deployment; the store lock in memory): (1) CLAIM the
approval — still approved by this approver; COMPLETE → replay the stored authorization
verbatim; CLAIMED (concurrent redeemer / crash after claim) → complete under the same
id; pre-WO-07 "id set, no redemption state" → 409. (2) PERSIST
action_authorizations/{id} first-writer-wins; every caller receives the persisted
document (identical signature / issued_at / expires_at). (3) COMPLETE the approval.
authorization_id = "AUT-" + sha256(approval_id|decision_fingerprint|tenant)[:16]. The
treasury reservation keyed by that id is idempotent on retry.
Files: services/service-decision-control-plane/main.py, tests/governance/test_wo07_single_use_redemption.py,
tests/governance/test_decision_control_plane.py, tests/governance/test_decision_meter.py,
tests/remediation/test_end_to_end.py (a replay is now the same authorization, not 409).
Acceptance: 50-way concurrent redemption → exactly one id, 49 identical replays, one persisted authorization, one complete redemption; deterministic id; crash after claim / after persist / after complete → retry converges on the same id (state inspected at each boundary); contention (transaction aborted BEFORE the write) at each of the three write boundaries → retry converges (fault-injection store); legacy marker → 409; wrong approver → 403 even after redemption.
Reported: ops/remediation/live_proof.py step 5e expects a replay to be 409; it is now
200 with the identical authorization. Not modified.
WO-47 — DCP get_decision returns any tenant's DecisionProof
Status: fixed.
Step 0. tests/governance/test_wo47_decision_tenant_scope.py::test_wo47_tenant_b_caller_cannot_read_tenant_a_decision
(caller→tenant registry armed with A and B; caller B requests A's id). On 26bec9c: 200
with the full document; /decision/{id}/chain 200; /passport/{id} 403 (confirms the
id exists).
Fix. DCP tenant_scoped_read(): resolve the caller against the DOCUMENT's tenant;
foreign, unmapped-strict, or missing all answer 404 {"detail": "not found"}; an explicit
?tenant_id must match the document. services/service-audit-replay/main.py (owned by
no audit prompt — touched only for WO-47's three named acceptance routes):
/decision/{id}/chain, /passport/{id} and /passport/{id}/verify convert the tenant
refusal to the same 404.
Acceptance: tenant B → 404 with only a detail field on all three routes; tenant A
positive path unchanged on all three; missing and foreign indistinguishable; explicit
tenant query must match; strict-mode unmapped caller → 404.
Deferred / gaps (all reported above, gathered here)
- F4 calibration lane is BLOCKED by WO-01's hard checks (owner decision at review; pinned by test). Options (i)/(ii) above.
- Proposers without
model_version+measurement_windoware refused (WO-03): growth-scheduler, cell37 market cell, live_proof, SDK callers. - No writer of
holdout_registrationsin-repo (WO-04); media candidates stayexperiment-requireduntil registrations are written (DCP route provided). execution_adapters/base.py::check_holdoutstill presence-only (CC-2); interface exposed frommain.py.- Approval-routing deployment config (WO-05): roles seed, broker registration, BFF header — operator/CX-2 actions; Supabase JWT verification would need a Track O secret.
ops/remediation/live_proof.pysteps 5e and 7a encode the old 409-replay and demonstrate-earns-Stage-4 behaviours (WO-06/07); read-only for this pack.- Contracts touched (owned by no prompt):
validators.py(taxonomy),decision_objects.py(two additive optional fields),holdouts.py(new resolver). Both signing sides dump the same model, so signatures stay consistent once both deploy; in-flight authorizations (≤1h) signed withoutevidence_bindingare refused. src/shared/mizoki_governance/client.pygained four passthrough kwargs; that one file is what fans the Deploy Router out to seven non-governance workflows (below).
Gates run on the branch head
/tmp/venv/bin/python -m pytest tests/governance -c tests/governance/pytest.ini # exit 0 (six governance services' suite, incl. 100+ new WO tests)
/tmp/venv/bin/python -m pytest tests/remediation --ignore=tests/remediation/test_token_minters.py -o addopts="" # 298 passed (token_minters needs google-auth; env only)
/tmp/venv/bin/python -m pytest tests/connectors tests/test_edge_inference_hardening.py tests/test_canonical_events_ddl.py tests/test_marketsignal_closure_register.py -o addopts="" # 464 passed, 32 skipped
bash .github/scripts/content_gates.sh # exit 0 (138 passed) — the same script ci.yaml and auto-merge run
python3 scripts/skill_sync.py --audit # canon audit — 0 findings across 15 skills
flake8 <changed files> --select=E9,F63,F7,F82 # 0 (ci.yaml's selection); pyflakes F-class clean on the new test files
python3 scripts/gate_leak_scan.py --check # 0 new / 0 grown / 0 stale
rule-03 V1–V3 greps over the diff # no hits
python3 .github/scripts/deploy_router.py --base origin/main --head HEAD # 10 workflows would dispatch on merge (see below)
python3 scripts/claude_memory.py check --strict # structurally valid
scripts/mizoki_canon.py --check is a no-op and is not cited. ruff/black are not
configured as blocking gates in this repo (black --check … || true in ci.yaml).
Merge is a deploy decision (rule 04). The Deploy Router matches 10 workflows on this
diff: deploy-service-action-runner, deploy-service-canonical-ingestion,
deploy-service-marketing-connectors (from the three contracts/mizoki_contracts/*
files) and deploy-boss-agent-core, deploy-cell2, deploy-cell3, deploy-coding-moa,
deploy-gemini-kg-pipeline, deploy-moa-controller, deploy-moe-router (from
src/shared/mizoki_governance/client.py). The five governance services themselves ship
through the dispatch-only deploy-governance-services.yml, which this merge does NOT
fire. Nothing in this pack was deployed, no Cloud Run config, secret or live dataset was
touched, and nothing ran against production BigQuery/Firestore.
Exact commands that prove the pack
python3 -m venv /tmp/venv && /tmp/venv/bin/pip install -e contracts pytest pyyaml httpx reportlab==5.0.1 Flask==3.1.3
/tmp/venv/bin/python -m pytest tests/governance/test_wo01_02_hard_gates.py tests/governance/test_wo03_passport_binding.py tests/governance/test_wo04_holdout_registry.py tests/governance/test_wo05_approver_principal.py tests/governance/test_wo06_rollback_proof.py tests/governance/test_wo07_single_use_redemption.py tests/governance/test_wo47_decision_tenant_scope.py -c tests/governance/pytest.ini -q
/tmp/venv/bin/python -m pytest tests/governance -c tests/governance/pytest.ini -q
/tmp/venv/bin/python -m pytest tests/remediation -q -o addopts="" --ignore=tests/remediation/test_token_minters.py
bash .github/scripts/content_gates.sh
Base SHA: 26bec9c6b424ca4b86b35621602f7a06764e5afe. PR: https://github.com/MIZOKI-3-5/MIZOKICloudRun/pull/1016 — head e6d57d8 at PR open (the PR page carries the current head). Branch head at report time: see
the PR. Merge gate: APPROVED: [MERGE], typed by the owner in-session — never by an agent.
Post-merge state (measured 2026-09-10, same session)
Read from the GitHub API after the merge; every row is a measurement with its id.
| Fact | Measured value |
|---|---|
| Merge | PR #1016 marked ready for review and merged by mediaintelligence at 2026-09-09T21:42:51Z; merge commit 4f4c064ea1f275074c2c7681ac92e8a962127ea1; branch head 307991f is its ancestor (git merge-base --is-ancestor); the three spot-checked new files resolve by blob on origin/main. |
| Review at merge | Codex review started 21:42:38Z (draft-marked-ready trigger) and reported Completed at 21:53:07Z, after the merge; zero review threads and zero review comments on the PR. No Claude Approvals check runs on this repo. Human review: none recorded (rule 02 — a merged PR is not evidence a review happened). |
Push-triggered deploys (10, all success) |
deploy-service-action-runner run 34408428073 · deploy-service-canonical-ingestion 34408428069 · deploy-service-marketing-connectors 34408428072 · deploy-boss-agent-core 34408428138 · deploy-cell2 34408428225 · deploy-cell3 34408428032 · deploy-coding-moa 34408428118 · deploy-gemini-kg-pipeline 34408428078 · deploy-moa-controller 34408428112 · deploy-moe-router 34408428023 — exactly the ten the Deploy Router predicted; every one head_sha=4f4c064, event push, created 21:42:53Z. |
| Governance services | Not dispatched. deploy-governance-services.yml (matrix: policy-engine, audit-replay, decision-control-plane) last ran 33136551314 on 7b566f0 (2026-08-28). Its serving revisions therefore predate this pack. |
| No CI deploy path at all (rule 04) | service-validation-orchestrator and service-approval-routing appear in no deploy workflow (grep -rl over .github/workflows/ → only URL lookups in deploy-growth-scheduler.yml); their registry rows say deploy: ops/remediation/deploy_all.sh, revisions 00009-74c / 00007-vgn, live-verified 2026-07-27. WO-05 (principal-only approver) can only reach production through that manual script. WO-01 does not depend on the orchestrator being redeployed — the policy engine derives check_class by name itself (validators.hard_failures). |
Honest state per WO, in the required proof language:
| WO | main |
Fleet |
|---|---|---|
| WO-01/02 hard gates + exploration | implemented | deployed-unverified nowhere — policy-engine needs a deploy-governance-services dispatch |
| WO-03 passport binding, WO-07 atomic redemption, WO-47 tenant-scoped reads | implemented | DCP + audit-replay: same dispatch. Action-runner side of WO-03/WO-04/WO-06 (evidence_binding verification, holdout re-proof, RollbackProof) is in the 34408428073 image → deployed-unverified. |
| WO-04 holdout registry proof | implemented | runner leg deployed-unverified; DCP leg awaits dispatch; no writer of holdout_registrations exists anywhere |
| WO-05 principal-only approver | implemented | service-approval-routing has no CI path; manual deploy_all.sh only; fail-closed until MIZOKI_APPROVER_ROLES / MIZOKI_APPROVER_BROKERS are set on the service |
| WO-06 RollbackProof before Stage 4 | implemented | runner leg deployed-unverified (34408428073); no proof has been recorded for any tenant, so Stage 4 is refused fleet-wide once the runner serves this image |
Runner leg measured serving 2026-09-14 (control plane, read-only): service-action-runner Ready=True, latestReady == latestCreated == service-action-runner-00044-pr4 at 100% traffic, created 2026-09-13T22:10Z by deploy run 34785900647 from fe8beadcb (a descendant of 4f4c064), so the runner-side rows above are now serving, not only deployed-unverified; the DCP/policy-engine legs are unchanged (not dispatched).
Compatibility consequence now live on the runner: ActionAuthorization records signed before evidence_binding existed no longer verify at service-action-runner (TTL ≤ 1h, fail closed). Any authorization minted by the still-old DCP revision will be refused by the new runner until the DCP is dispatched — that is the intended fail-closed direction, and it is a full stop on Stage-4 dispatch, not a degradation.
Owner actions that remain open (unchanged from the pre-merge list; nothing here was decided by the merge): F4 calibration lane resolution; dispatch deploy-governance-services (policy-engine, DCP, audit-replay) and run deploy_all.sh for approval-routing / validation-orchestrator, in that order or together; seed MIZOKI_APPROVER_ROLES / MIZOKI_APPROVER_BROKERS; a writer of holdout_registrations; RollbackProof drills per tenant/account/action class; CC-2's execution_adapters/base.py::check_holdout.
Recorded on the ledger (CLAUDE.md inbox), the session board, and the cross-repo shared memory (shared-memory/records/platform/, hazard/, session/) the same day.