# CAIN-42 Evolution 9: Mandatory Verified Governance Packages

**Date:** 2026-10-04 · **Session:** -2b · **Status:** PRE-PRODUCTION. Implemented, tested and integrated into the
gateway code. **Not deployed** at the time of writing (needs a blue+green restart, which is the owner's call). No
evidence bundle has been published.

## 1. What changed, in one paragraph

Until E8, a governance package was optional: nothing required it. E9 adds **boundary modes per product**:
OBSERVATION (default, a pure pass-through), MONITORING, PROTECTED and HIGH_ASSURANCE. At a PROTECTED or
HIGH_ASSURANCE boundary there is **no contract, no decision and no E8 commit without a valid, active,
registry-certified package whose compiled terms the contract matches exactly**. Packages live in a quorum-certified
registry with a lifecycle, sticky revocation and anti-rollback.

It reaches `decide()` and the E8 commit **without changing either**. The fabric already re-checks each product's
policy digest at three points:
1. `authorize()`
2. `decide()`, which returns POLICY_UNAVAILABLE or POLICY_DRIFT on a mismatch
3. the live execution binding at the E8 commit, which refuses on any digest change

A wrapping adapter folds the package's live registry state into that digest.

## 2. Verified properties carried over (not regressed)

| Property | Evidence in E9 |
|---|---|
| Only RIG grants authority | `test_rig_binding_matrix` (RIG action/scope/capability narrower than package → refused), `test_rig_revocation_and_model_substitution_still_apply` |
| A package cannot grant | the adapter can only turn a digest into UNAVAILABLE; `test_compiler_cannot_exceed_rig` (E8) still passes |
| Contract lifecycle unchanged | packages issue through draft → propose → validate → authorize → consent (`issue`, `create_from_package`) |
| Every use reaches E8 | `test_revocation_between_decision_and_e8_commit_stops_execution`: E8 REFUSED, `binding_changed: [policy_digest]`, nothing executed |
| Optimizer only drops fabric-implied constraints | unchanged; now backed by SMT lemmas P4 (§5) |
| Verification recompiles | unchanged (memoized); warm verify p50 1.12 ms (regression target 1.1 ms) |
| Clean-room verifiers independent | new AST import scan + run in an interpreter that blocks every repository module (§6) |
| E8-F1 fix (safe tightening not reported as expansion) | regression test retained; still passes |

## 3. Components (all real, all tested)

| Component | Where | What it does |
|---|---|---|
| Registry replica | `cain_governance_package_runtime.RegistryReplica` | SQLite store with its own Ed25519 key. Endorses at most one event per (tenant, namespace, seq), only for legal transitions, **after verifying (recompiling) the package itself** |
| Registry client | `GovernancePackageRegistry` | quorum writes (q = ⌈(n+f+1)/2⌉, the `cain_contract_quorum` sizing), quorum reads, anti-entropy, `recover()` |
| Lifecycle | `TRANSITIONS`, `legal()`, `derive()` | PUBLISHED → ACTIVE ⇄ SUSPENDED → SUPERSEDED / REVOKED / EXPIRED; revocation is sticky; anti-rollback |
| Enforcing adapter | `PackageEnforcingAdapter` | folds live package state into the product policy digest; **checks the contract body equals the package's compiled terms** (`body_matches_package`) |
| Runtime | `GovernancePackageRuntime` | boundary modes (`ModeStore`, on disk, read on every check), `issue()`, `create_from_package()`, `explain()` |
| Time machine | `explain_at()` | which package governed a contract at time t, from the certified log only; unknowns are listed, never inferred |
| Lattice + proofs | `cain_governance_lattice` | `compare()` (partial order), 17 z3 lemmas, bounded-exhaustive code conformance, monotonicity engine |
| API | `routers/governance_packages.py` → `/fabric/contracts/packages/*` | compile, verify, publish, activate, suspend, revoke, status, export, create, explain, boundary |
| Fabric hook | `routers/contracts.FABRIC_HOOKS` | every tenant fabric gets enforcing adapters at construction; **a failing hook means the fabric is not cached or used** |
| SDK | `sdk/python/src/cainstudio/governance.py` (`cain.governance`) | compile / publish / activate / suspend / revoke / status / export / explain / create_contract, with local fingerprint and IR-binding checks before consent |
| CLI | `scripts/govc.py` | adds `compare`, `monotonicity`, `proofs`, `registry-verify` |
| Clean-room registry verifier | `scripts/govreg_verify_offline.py` | zero CAIN imports: certificates, chain, lifecycle legality, state at t |

## 4. Package → RIG → contract → E8 chain (what each link checks)

```
package (registry: ACTIVE, not expired, tenant, signer for HIGH_ASSURANCE)
  → contract body == package's lowered terms (subject, action, resource, conditions, pin, uses ≤, ttl ≤, evidence, risk ≤, IR digest)
  → authorize(): live RIG covers the contract (actions, scopes, chain ACTIVE, key, runtime) + policy digest (incl. package state)
  → decide() each use: same policy digest re-derived (package state now) + all E1–E5 guards
  → E8 commit: live binding re-derived (package state now); any change → REFUSED, nothing runs
```

**Attack examples from the prompt, mapped to this system:**

| Attack | Result |
|---|---|
| RIG grants READ-type, package requests EXEC | refused at authorize (AUTHORITY_AMPLIFICATION) |
| RIG grants scope `/srv/app/logs`, package requests `/srv/app` | refused |
| Capability A vs B | refused |
| RIG grants $100, package requests $1,000 | **NOT_APPLICABLE**: no spend dimension exists in the four products (roadmap #13) |

## 5. Formal status of policy tightening (§18–§21)

| Part | Statement | Method | Status |
|---|---|---|---|
| P1 | per operator: semantic ≤ ∧ holds(a, request, t) ⇒ holds(b, request, t) | z3, all values | **FORMALLY_VERIFIED** (string sets, integer maxima, trust order, UTC hour arithmetic on `at_ms`, `cwd_under` via path transitivity) |
| P2 | the z3 encodings equal `G._cond_ok` / `G.path_under` | sampled, 400 cases | PROPERTY_TESTED |
| P3 | the compiler's `cond_leq` code ⇔ semantic ≤ | exhaustive over bounded domains: 96,831 pairs | PROPERTY_VERIFIED (bounded) |
| P4 | optimizer rules and resource order: path_under transitivity (5 case lemmas + prefix-witness lemma), validator bounds, trust bottom, full day, namespace implied | z3, all values | **FORMALLY_VERIFIED** |
| Composition | `ir_leq(a,b)` ⇒ a admits ⊆ b admits | conjunction of sound per-conjunct implications | argued (propositional), not solver-checked |
| Monotonicity engine | source → IR → optimized IR → lowered → contract, each stage machine-checked per package | `monotonicity_report` | RUNTIME_ASSERTED per package |

**Honest net:** the per-operator semantics are proved; the link to the implementation is bounded-exhaustive, not
proved. "FORMALLY_VERIFIED" is not claimed for `ir_leq` as code.

## 6. Clean-room verification

- `scripts/govreg_verify_offline.py` (new) re-derives the membership digest and quorum size, every event hash and
  chain link, every certificate (≥ q distinct valid Ed25519 endorsements), every transition's legality, and the state
  at any time t.
  - It agrees with the runtime's `derive()` at three times, including after expiry.
  - It refuses tampered logs: changed kind, dropped signatures, reordering, truncation.
  - **It refuses an illegal transition even when its certificates are valid.** This was tested with all four replicas
    misbehaving, beyond the f = 1 fault bound.
- Dependency isolation is now tested for all three verifiers:
  - an AST import scan, allowing stdlib and `cryptography` only
  - execution in a fresh interpreter whose import system refuses `cain*` modules, with the repository removed from
    `sys.path`

## 7. Distributed registry: what is and is not true

| Claim | Status |
|---|---|
| Quorum protocol: one endorsement per seq per honest replica, q-of-n certificates, conflicting writers never both commit | TESTED (threads, two independent clients) |
| Stale replica cannot resurrect a revoked package via quorum reads; anti-entropy repairs it | TESTED |
| Minority partition: no reads or writes (UNAVAILABLE) → protected boundaries block | TESTED |
| Client died after collecting endorsements → `recover()` completes the event | TESTED |
| Split endorsement (two events, disjoint halves) → UNCERTAIN, boundaries block, **recover cannot force it** | TESTED. **Liveness gap:** needs an operator action that does not exist yet |
| Equivocating replica alone cannot certify an illegal event | TESTED |
| Restart: revocation survives | TESTED |
| **Multi-host deployment** | **NOT DONE.** The four replicas are four SQLite files on one host: one failure domain. The protocol is real; the fault independence is not |
| Consistency guarantee | protected reads are linearizable with respect to committed events with at most f faulty replicas; monitoring reads are local (eventual). Nothing stronger is claimed |

## 8. Security red team (§45): what was attempted and the outcome

| Attack | Outcome | Test |
|---|---|---|
| package → authority escalation | refused by RIG at authorize | `test_rig_binding_matrix` |
| contract claims a package but carries wider terms (even with the issuing context forged) | refused at authorize (body ≠ package terms) | `test_contract_claiming_a_package_with_wider_terms_is_refused` |
| package → E8 bypass (revoke between decision and commit) | E8 REFUSED, not executed | `test_revocation_between_decision_and_e8_commit_stops_execution` |
| no package at a protected boundary | issuance refused; pre-existing contracts denied | `test_protected_refuses_contracts_without_package`, `test_contract_issued_before_protection_is_denied_after` |
| tenant escape | publish, revoke, create and read all refused or isolated | `test_tenant_isolation` (library and API) |
| revoked restoration via rollback, republish, restart, stale replica | refused in every case | `test_revocation_is_permanent`, `test_restart_preserves_revocation`, `test_stale_replica_*` |
| older-package rollback, including via suspend | refused | `test_anti_rollback_direct_and_via_suspend` |
| expired package | denied | `test_expired_package_denies` |
| HIGH_ASSURANCE with unsigned or untrusted signer | refused | `test_high_assurance_requires_trusted_signer` |
| package tampering (replicas verify themselves) | not committed | `test_replicas_verify_packages_themselves` |
| forged certificates | refused | `test_forged_certificates_are_refused` |
| mode downgrade to keep a revoked package's contracts alive | outstanding contracts are invalidated in both directions | `test_mode_change_invalidates_outstanding_contracts_both_ways` |
| enforcement silently missing after restart | prevented by the fabric-creation hook, which fails closed | `test_failing_fabric_hook_fails_closed` |
| bypass via the direct product routes (E9-F7) | 403 BOUNDARY_PROTECTED; fails closed | `test_direct_product_routes_refuse_at_a_protected_boundary` (5 routes) |
| agent key governs itself | 403 | `test_agent_keys_cannot_govern` |

Findings during the build are in `EVOLUTION_9_FAILURE_CORPUS.json` (E9-F1 … F7). The two most serious:
- **E9-F7 (found by another session's review, not by me):** the direct product routes (`execute-sandboxed`,
  `mint-token`, MemVault `inspect-write`/`search`, SCITT `register-statement`) never read the boundary mode. At a
  PROTECTED boundary a tenant key could still execute with no package, no contract and no E8 commit. It was latent,
  because E9 was never deployed. Fixed: those routes now refuse with `403 BOUNDARY_PROTECTED` at enforcing modes, and
  fail closed if the mode store is unreadable. Tested through the real app.
- **E9-F1:** the first package request on a fresh process deadlocked its worker thread.

**Not attempted:** MCPGate bypass (MCPGate is not integrated, §9), protocol semantic laundering, compiler/verifier
binary substitution at the OS level.

## 9. Integration status

| Surface | Status |
|---|---|
| Contract fabric + E8 | **INTEGRATED** via the policy-digest channel (no change to `decide()` or E8) |
| Gateway API | **IMPLEMENTED, NOT DEPLOYED**: `/fabric/contracts/packages/*`, auth and tenant from `frontier20_gate`, boundary changes operator-only |
| SDK | **IMPLEMENTED, UNRELEASED** (`cain.governance`; not in a published wheel yet) |
| CLI | `scripts/govc.py` (not the `cain` CLI) |
| MCPGate | **NOT_IMPLEMENTED.** MCPGate does not route through contracts today (roadmap #3). `explain()` and `/packages/explain/{id}` are the status surface it would consume |
| CAIN Studio UI | **NOT_IMPLEMENTED** (the API is the surface; no UI was built, so there is no static page pretending) |
| ClawX | **PARTIAL**: `explain()` returns the labels PACKAGE_VALID / REVOKED / EXPIRED / SUSPENDED / REVERIFICATION_REQUIRED / UNKNOWN / DENIED and reason codes without policy contents; no ClawX UI wired |
| Frontier-20 | the four in-window products: **SUPPORTED** at the policy-digest level (openshell, memvault, scitt boundaries; RIG as the authority source). The other 16: **NOT_IMPLEMENTED** (directive) |
| Migration (§27) | **NOT_IMPLEMENTED**: only one compiler/IR version exists; other versions are rejected, never reinterpreted |
| Supply-chain SBOM and attestation (§28–30) | **NOT built here.** Session -ff's E10 (`cain_governance_attestation.py`) hashes the compiler and verifiers as BOM components; not duplicated |
| Runtime, model and tool substitution (§31–33) | model: the existing `runtime_digest` check (MODEL_SUBSTITUTION) applies to package contracts (tested). Host/runtime identity binding: **NOT_IMPLEMENTED** |

## 10. Performance (single host, load average ~42 on 4 vCPU: numbers inflated; `results/BENCHMARK.json`)

| Operation | p50 |
|---|---|
| verify package, warm (regression target 1.1 ms) | 1.12 ms |
| registry quorum read (4 local replicas) | 6.2 ms |
| registry local read | 2.2 ms |
| publish (4 replica verifications) | 20 ms |
| activate | 13 ms |
| memory_write invoke, OBSERVATION | 33 ms |
| memory_write invoke, PROTECTED | 70 ms |

The PROTECTED overhead (about 37 ms) comes from re-checking package state at authorize/decide/E8, each check a
quorum read. A per-invoke status memo would cut it but reintroduces a staleness window, so it is not done.

## 11. IP and reproduction cost (conservative)

**Size:** about 2,300 new lines.

| Component | Lines |
|---|---|
| runtime | 835 |
| lattice | 237 |
| router | 214 |
| registry verifier | 172 |
| SDK | 94 |
| tests | ~830 (73 tests) |

Plus small hooks in `main.py` and `routers/contracts.py`.

**Hard to reproduce:**
- enforcing through an existing digest channel, so that decide **and** the E8 commit re-check package state without a
  second authorization path
- the package↔contract body binding
- sticky revocation with anti-rollback across suspend
- a quorum registry whose replicas verify packages themselves
- the clean-room log verifier that refuses illegal-but-certified transitions
- the proof decomposition for path transitivity

**Effort for a senior engineer with the fabric and E8 in hand:** about 4–6 engineer-weeks.
Replacement-cost delta:
- **Low** $25k
- **Base** $50k
- **High** $90k

**What each replacement scale would need** (not claims):
- **$250k:** multi-host registry deployment with failure-domain evidence, MCPGate enforcing packages on real MCP
  traffic, a released SDK, one design partner on PROTECTED.
- **$500k:** plus migration, an operator resolution path for split endorsements, a published auditor workflow, and
  external reproduction.
- **$1M+:** an ecosystem around the package format (third-party packs, conformance of other runtimes) and
  production evidence over time. The current implementation does not support the $1M+ ranges.

**Patent candidates (weak to moderate; not legal advice):**
- "enforcing externally-registered governance state through a digest that is re-derived at both the decision and
  the commit boundary"
- "quorum registry replicas that independently recompile and verify governance packages before endorsement"

Prior art: Sigstore/TUF (rollback, freeze), Cedar/OPA bundles, PBFT-style certificates. **Trade secret:** the
binding check set and the boundary-mode semantics.

## 12. Acceptance gate (§51)

| Item | Status |
|---|---|
| Implementation | DONE |
| Real E8 integration | DONE (policy-digest channel; E8 refusal tested) |
| RIG binding | DONE |
| MCPGate enforcement | **INCOMPLETE** (not integrated) |
| SDK | DONE (unreleased) |
| API | DONE (not deployed) |
| CLI | DONE (`govc.py`) |
| Distributed validation | **PARTIAL**: in-process replicas; no multi-host |
| Revocation | DONE |
| Expiration | DONE |
| Replay defense | DONE (contract nonces + sticky revocation + anti-rollback; package replay across tenants blocked) |
| Formal/property analysis | DONE as scoped in §5 |
| Clean-room | DONE |
| Tenant isolation | DONE |
| Red team | DONE as listed in §8 |
| Performance | DONE (single host) |
| Evidence | **NOT DONE**: no bundle |
| Public-truth audit | no public claims were changed |

## 13. Next (in-window)

1. Deploy (blue+green restart), then a live smoke test with the uxtest keys: publish → PROTECTED on scitt → create
   → invoke → revoke → deny.
2. MCPGate: refuse tool calls whose contract's package is not PACKAGE_VALID (roadmap #3).
3. An operator path to resolve split endorsements, then a multi-host registry on cain-mr-02.
4. A per-invoke package-state memo bounded by a revocation-propagation SLO, with evidence.
