{
  "date": "2026-10-01",
  "phases": [
    {
      "extends_moat": "governance invariant #30 (Zero-Mock / Zero-Stub); public truth layer",
      "fix": "no placeholder key or signature can be produced; cryptography is a hard dependency and its absence fails closed",
      "name": "Zero-Stub & Truth Lock",
      "phase": "1",
      "shipped": [
        "fake cryptographic placeholders removed -> fail closed (cain_trifecta_monopoly.py x3, cain/enclave.py)",
        "hardcoded QuorumSeal HMAC key -> env key or ephemeral per-process (cain_quorumseal.py x2)",
        "clawx homepage retained log corrected: shadow->enforce, 16->67 claims, FAILED soak no longer 'Running now'",
        "tests/test_zero_stub_guard.py added"
      ]
    },
    {
      "extends_moat": "Moat 2 (security-context continuity)",
      "fix": "a policy expression has no path to arbitrary code execution; no cross-origin credentialed call primitive",
      "name": "Security Hardening",
      "phase": "2",
      "shipped": [
        "SafeExpressionEvaluator: source eval-of-compiled-code replaced by a real AST interpreter (cain_control_loop.py x2)",
        "wildcard-origin CORS with allow_credentials=True disabled in 36 services",
        "unauthenticated /test-cain-private route removed (main.py x2)",
        "guard extended to fail on source eval and unsafe CORS repo-wide"
      ]
    },
    {
      "extends_moat": "Moat 1/4 via the built governance data plane",
      "fix": "the hosted consensus stage can drop from p50 ~2269 ms to ~0.83 ms for granted tenants; the claim now matches the measurement",
      "name": "Performance & Truth-of-Path",
      "phase": "3",
      "shipped": [
        "governance data plane deployed live (CAIN_AUTH_SNAPSHOT=1; published_snapshots>=1, circuit_open false)",
        "operator tool scripts/cain42_dataplane/grant.py to grant the fast path (admin-gated)",
        "<15ms / sub-millisecond / 'Enforced in Silicon' claims corrected to the measured hosted figure",
        "RAM tmpfs /tmp exhaustion reclaimed; public health recovered to 200 with no 502s"
      ]
    },
    {
      "extends_moat": "Moat 2 + Moat 4",
      "fix": "an UNKNOWN artifact never verifies; a changed artifact cannot inherit its old approval; composed dangerous capability pairs are refused",
      "name": "Artifact Supply-Chain Governance",
      "phase": "4",
      "shipped": [
        "cain_artifact_attestation.py: publisher-signed manifests, rug-pull/version/capability/tool change detection",
        "CompositionRiskEngine: order-independent composition risk naming the dangerous path",
        "tests/test_artifact_attestation.py"
      ]
    },
    {
      "extends_moat": "Moat 1 + Moat 4",
      "fix": "retrieve no longer serves quarantined memory; the dead bulk-insertion poisoning check is replaced by a live gate",
      "name": "Governed Memory at Write Time",
      "phase": "5",
      "shipped": [
        "cain_memory_governance.py: storage-time policy gate over the governed memory store",
        "trust-aware retrieval (quarantine/expiry/trust/poisoning), independent promotion, purge-keeps-evidence, cross-tenant isolation",
        "tests/test_memory_governance.py"
      ]
    },
    {
      "extends_moat": "Moat 2 (security-context continuity)",
      "fix": "CAIN now sits in front of the tool call in every major agent framework, and the site chatbot answers with long, detailed, grounded explanations instead of a few lines",
      "name": "Framework Integrations + Advanced Long-Form Chatbot",
      "phase": "22",
      "shipped": [
        "cainstudio.integrations.*: ten framework adapters using each SDK's native pre-execution hook (langgraph, openai_agents, crewai, llamaindex, pydantic_ai, autogen, google_adk, claude_agent_sdk, mcp, universal)",
        "one contract: a refusal means the tool body NEVER runs; on_block tell_agent returns the reason, raise propagates; available() reports importable adapters; per-framework pyproject extras",
        "verified against real frameworks: sdk/python/tests/test_integrations.py 11 passed (LangChain, OpenAI Agents, MCP, Google ADK, universal, Claude Agent SDK) + 44 client tests",
        "chatbot: stronger model, max_tokens 4000, effort high, TOP_K 10, passages 6000, 45s budget, prompt rewritten for 350-900+ word detailed answers; local cap 900/1500 -> 6000/9000",
        "chatbot bug fixed: _classify_intent generic-opener-vs-specific ordering (13/13 tests, was 11/13)",
        "launcher fades when not clicked within 60s (was 90s) and the ctw-fading CSS that was missing now exists"
      ]
    },
    {
      "extends_moat": "Moat 2",
      "fix": "the public enforcement and status surfaces are bounded per IP and globally instead of unbounded",
      "name": "Public Enforcement Surfaces Rate-Limited (frontier + fabric status)",
      "phase": "21",
      "shipped": [
        "/frontier/* (incl. the enforcement decision point /frontier/authorize) accepted unlimited unauthenticated requests (60/60 -> 200); now a router-level guard at 240/min per IP + global backstop",
        "/fabric/status (which probes live dependencies on every call) now guarded the same way",
        "verified live: 300-request bursts -> {200:240, 429:60} on both; single requests unaffected",
        "tests/test_frontier_and_status_limits.py"
      ]
    },
    {
      "extends_moat": "Moat 2 + evidence integrity",
      "fix": "the changelog's Phase 3/9/10 claim that those endpoints were live was false; it is now true, and a test prevents another dead router",
      "name": "Dead Routers Mounted + False 'Mounted Live' Claim Corrected",
      "phase": "20",
      "shipped": [
        "routers/frontier_phase3.py (7 endpoints) and routers/break_glass.py (6 operator endpoints) were never imported by main.py -> every path 404'd while the changelog said 'mounted live'; both are now mounted and verified live",
        "GET /fabric/frontier/status now 200; POST /fabric/frontier/a2a/authority returns granted:true; POST /api/v1/break-glass/grants (no key) is 403 operator-gated, not 404",
        "frontier API rate-limited as a public surface: 300-request burst -> {200:240, 429:60}",
        "tests/test_frontier_router_mounted.py incl. a guard that fails if any routers/*.py APIRouter is not imported by main.py"
      ]
    },
    {
      "extends_moat": "Moat 2",
      "fix": "the rate limiter is safe under a flood and can no longer turn into an open door when its store fails",
      "name": "Limiter Contention Fix (no 500s) + Fail-Closed Store",
      "phase": "19",
      "shipped": [
        "ratelimit._get_conn sets a busy timeout (default 5s, configurable) with WAL + synchronous=NORMAL so concurrent writers queue instead of raising 'database is locked'",
        "check_and_count/_checked fail CLOSED on any sqlite3.Error (a broken limiter store denies, never opens)",
        "same 800-request flood: before {200:612,500:24,TimeoutError:164} -> after {200:600,429:200}",
        "tests/test_ratelimit_contention.py; corrected a false positive in the Phase 17 SQL guard (PRAGMA busy_timeout=number)"
      ]
    },
    {
      "extends_moat": "Moat 2",
      "fix": "public unauthenticated surfaces (proven unlimited: 30/30 hammering returned 200) are now bounded per IP and globally",
      "name": "Abuse-Resistance for Public Surfaces",
      "phase": "18",
      "shipped": [
        "ratelimit.check_and_count adds a per-IP GLOBAL backstop across all buckets (a client can no longer stay under each endpoint limit while sweeping many)",
        "main.public_rate_limit guard + a router-level dependency on the public proof API (600/min per IP, 1200/min global)",
        "verified live: 700-request burst from one IP -> 599x200, 92x429; ordinary use unaffected",
        "tests/test_public_rate_limiting.py"
      ]
    },
    {
      "extends_moat": "Moat 4 (storage-layer provenance integrity)",
      "fix": "the audit's SQL concern is now a checkable invariant, and the reviewed surface is ground-truthed rather than asserted",
      "name": "SQL Identifier Safety (last open code-level audit deduction)",
      "phase": "17",
      "shipped": [
        "cain_sql_identifier_safety.py: strict identifier validation, a validated Identifiers allow-list, and set_clause/insert_columns helpers that build parameterized SQL by construction",
        "tests/test_sql_identifier_safety.py enumerates every dynamic-SQL module with a justification and fails on a new unreviewed site or a request-derived interpolation",
        "review of the live sites: runtime_engine {field} validated against allowed_fields; cain_private updates built from internal literals; cain_schema table_name validated against CAIN_SCHEMA_TABLES",
        "three false positives in the detector itself were fixed before the list was set to ground truth"
      ]
    },
    {
      "extends_moat": "Moat 4",
      "fix": "tamper-evidence is demonstrated against real stored decisions, not only synthetic ones, and anyone can re-check it offline",
      "name": "Real-Store Decision-Record Integrity Proof",
      "phase": "16",
      "shipped": [
        "scripts/cain42_public/build_record_integrity_proof.py opens the live evidence store READ-ONLY, samples real decision rows, re-derives each digest, checks the Ed25519 record signature and flips a real stage verdict to show detection; mirrored with a zero-dependency verifier",
        "verified on real data: 12/12 records re-derived intact, tamper detected, 1663 decisions in store (420 signed)",
        "fixed three generator defects: sparse-dict KeyError in the tamper vector, a parent-key mismatch, and non-identical mirrors",
        "tests/test_record_integrity_proof.py"
      ]
    },
    {
      "extends_moat": "Moat 4",
      "fix": "a signed, point-in-time, offline-checkable statement of the live enforcement surface, honest about the dirty tree and about what it cannot prove",
      "name": "Signed Governance Status Receipt (clean-room verifiable)",
      "phase": "15",
      "shipped": [
        "scripts/cain42_public/build_governance_receipt.py collects live /fabric/status, /frontier/status, tool schemas, git commit and 3-site health, signs the whole receipt (Ed25519 over sha256(canonical JSON)), and mirrors receipt.json + zero-dependency verify_receipt.py + SHA256SUMS + index.html to both roots",
        "verify_receipt.py recomputes the digest, re-checks the signature and confirms enforcing-list/chain consistency offline; both mirrors VERIFIED",
        "captured at 2026-10-01T05:12:38Z: commit 3d8435f, dirty, mode enforce, 14 stages / 11 enforcing, 1 tool schema, all 3 sites 200",
        "tests/test_governance_receipt.py"
      ]
    },
    {
      "extends_moat": "Moat 4",
      "fix": "anyone can verify, without an account, that the egress/toolargs verdicts are bound into each decision's signed evidence and that editing one is detectable",
      "name": "Public No-Key Stage-Proof Bundle (clean-room verifiable)",
      "phase": "14",
      "shipped": [
        "scripts/cain42_public/build_stage_proof_bundle.py emits stage-proof-2026-10-01 to both mirrors: stage_proof.json, zero-dependency verify_stage_proof.py, SHA256SUMS, index.html, countersigned",
        "three records (allowed, egress_denied, toolargs_denied) with distinct digests and a tamper vector; the verifier recomputes digests, confirms distinctness, detects the tamper and re-checks the Ed25519 signature",
        "fixed a real defect the verifier caught: sort_keys reordered stage keys after hashing; stages are now canonicalized before hashing",
        "tests/test_stage_proof_bundle.py"
      ]
    },
    {
      "extends_moat": "Moat 4",
      "fix": "the runtime restriction stages are cryptographically bound to each decision's evidence, and tool-argument enforcement is operator-controllable and demonstrably live",
      "name": "Restriction Stages As Signed Evidence + Live Tool-Schema Enforcement",
      "phase": "13",
      "shipped": [
        "proof + tests that the live egress/toolargs verdicts are inside the Ed25519-signed decision digest, and that rewriting a recorded egress deny into allow is detected",
        "operator schema management GET/PUT /fabric/toolargs/schemas (admin-gated, atomic file write, registry hot-reload); a registered tool enforces, an unregistered tool stays observe",
        "live scenario POST /fabric/try?scenario=malformed-tool-args returns BLOCKED with blocked_by ['toolargs']",
        "tests/test_restriction_stages_are_signed.py, tests/test_tool_schema_admin.py"
      ]
    },
    {
      "extends_moat": "Moat 4",
      "fix": "a wedged decision log now heals on process start instead of denying every call until an operator intervenes; hallucinated/malformed tool arguments are gated live",
      "name": "Automatic Log Self-Heal + Tool-Call Validity Live Stage",
      "phase": "12",
      "shipped": [
        "automatic decision-log recovery at gate startup (runtime.get_gate): a non-empty log that fails verification is archived (bad line named, intact prefix + quarantined original preserved) and a fresh chain starts; missing/empty is a fresh chain",
        "tool-call argument validity wired as the live 'toolargs' Fabric stage (decision chain is now 14 stages; /fabric/status reports registered_tools)",
        "default observe for unregistered tools; a registered tool's args must match its schema and grounded values must be present in the CONTEXT the agent was given, never in the args themselves",
        "tests/test_toolargs_stage_and_log_recovery.py"
      ]
    },
    {
      "extends_moat": "Moat 4",
      "fix": "a broken evidence log can no longer be silently grown over, and a hallucinated or malformed tool argument is refused before execution with the reason recorded",
      "name": "Decision-Log Self-Heal + Grounded Tool-Call Validity",
      "phase": "11",
      "shipped": [
        "DecisionLog refuses to extend an unverifiable log (fail closed); first_bad_line() names the exact break; recover() archives the bad log (naming the line, preserving the intact prefix) and starts a fresh chain",
        "found and recovered a live wedge: the frontier gate was denying every action because decisions.jsonl failed verification at line 7448; intact prefix + quarantined original archived under data/frontier/archive/",
        "cain_tool_arg_validity.py: deterministic schema + grounding validator for tool-call arguments (missing required, wrong type/format, unknown parameters, ungrounded/hallucinated values)",
        "tests/test_tool_arg_validity.py"
      ]
    },
    {
      "extends_moat": "Moat 2 + Moat 3",
      "fix": "closes three under-enforcements in the A2A governor: string-only capability comparison, attenuation measured against the request, and a '*' authorized-delegator wildcard (trust laundering)",
      "name": "Authority-Preserving Inter-Agent Delegation",
      "phase": "9",
      "shipped": [
        "cain_agent_delegation.py: ordered authority levels (NONE<READ<WRITE<EXECUTE<ADMIN) with a ceiling a child may never exceed",
        "capability intersection down the chain; shrinking expiry; self-delegation, cycle and depth refused; wildcard authorized-delegators rejected",
        "cascade revocation (revoking any subject revokes its whole delegation subtree; a revoked root kills the subtree)",
        "tests/test_agent_delegation.py"
      ]
    },
    {
      "extends_moat": "Moat 2",
      "fix": "the action-tool allowlist is now a real stage in the live pipeline (restriction-only; can never turn a deny into an allow), not only a library",
      "name": "Egress Screening Wired Into The Live Decision Path",
      "phase": "8",
      "shipped": [
        "TrustFabricControlPlane._egress_stage runs on every hosted decision; appears in /fabric/status enforcement.stages.egress and in the decision chain",
        "observe mode when a tenant has no rules (recorded, not blocked); enforcing once any rule is configured; hard-denies loopback/link-local/private/cloud-metadata regardless of rules",
        "mode pinning: a tenant may opt down to shadow but may not opt egress screening down",
        "tenant-key-gated management API GET/POST/DELETE /fabric/egress/rules (agent-forbidden)",
        "tests/test_fabric_egress_stage.py"
      ]
    },
    {
      "extends_moat": "Moat 2",
      "fix": "an agent cannot reach an unlisted destination even when every tool call was individually authorized; metadata/loopback are unreachable regardless of rules",
      "name": "Default-Deny Egress & Isolation",
      "phase": "7",
      "shipped": [
        "cain_egress_control.py: default-deny destination allowlist (host/.domain/CIDR, ports, schemes, tenant/agent scoped)",
        "bypass guards (name rule never grants a bare IP; CIDR matches IP hosts only; lookalike suffix rejected) and hard denies (loopback/link-local/private/metadata/.internal before any rule)",
        "IsolationProfile: mandatory sandbox baseline with per-setting weakness reporting",
        "tests/test_egress_control.py"
      ]
    },
    {
      "extends_moat": "Moat 2",
      "fix": "a decision can bind to a VERIFIED subject rather than a caller-supplied string; an identity that is unregistered, expired, tampered, mismatched or widened is refused",
      "name": "Agent Identity Interop",
      "phase": "6",
      "shipped": [
        "cain_agent_identity.py: SPIFFE-style workload identity (SVID) with signed, time-bound, registration-bound verification",
        "OAuth 2.0 token exchange (RFC 8693) with monotonic scope/lifetime attenuation; delegation 'act' chain; authenticated caller binding the verified subject",
        "tests/test_agent_identity.py"
      ]
    }
  ],
  "schema": "cain42.hardening.phases.v1"
}
