# Evolution 4 — governance differential (decision regression)

```
signed contract exports (receipts + replay records + trust bundle)
        │  admit(): clean-room verifier (0 CAIN imports) under a PINNED key/root
        │           + runtime replay must reproduce the recorded decision
        ▼
corpus (one tenant, sorted receipt hashes -> corpus_digest)
        │
        │   candidate = forecast_policy? + templates{base_fp: body}? + version (git ref)?
        ▼
run():  for each decision
          baseline  = recorded (decision, reason)
          candidate = decide -> forecast gate -> quorum   (invoke()'s order)
                      under the candidate's policy / template / code version
          template replay: subject key swapped for a sandbox replay key on BOTH sides,
                           same request/nonce/ts re-signed; baseline must still reproduce
                           (else NOT_COMPARABLE)
          classify: UNCHANGED | REASON_CHANGED (MATERIAL) | TIGHTENED (EXECUTION-MATERIAL)
                    | WIDENED (AUTHORITY-MATERIAL) | CANDIDATE_ERROR (AUTHORITY-MATERIAL)
        ▼
report (deterministic core, no clock) -> report_digest, signed by the differential key
        │   grants_authority=false, epistemic=SIMULATED, limitations listed
        ▼
reviews: ACCEPT/REJECT per material change, Ed25519, registered reviewer != differential key
        ▼
regression_evidence() -> {e4_regression_report_digest, material_changes_unreviewed}
        ▼
E5 ProposalLedger.advance(..., "REGRESSION_ATTACHED", evidence)
```

## Surfaces
- Library: `platform-gateway/cain_governance_differential.py` (`admit`, `make_candidate`, `load_version`, `run`,
  `verify_report`, `sign_review`, `regression_evidence`).
- Route: `POST /fabric/contracts/differential` takes a forecast policy and/or templates. It needs the operator
  credential plus a non-agent tenant key and only sees the caller tenant's own history. Git-ref (code version)
  differentials are library/CLI only and never run over HTTP: `load_version` executes repository source from the
  named commit.
- Key: `<key_dir>/differential_ed25519.pem`, created atomically, so blue and green share one key. A signature from
  this key means "the replay computed this", nothing more.

## What makes it hard to get right (and what the tests pin)
- Proof-of-possession is bound to the contract fingerprint, so a changed template invalidates every recorded
  signature. Handled by sandbox re-signing on both sides plus a reproduce-check. Records whose recorded denial was
  about the signature itself (POP_INVALID) become NOT_COMPARABLE, not "unchanged".
- Decisions that stopped before the forecast step have no recorded forecast. A looser template that gets past
  decide() is reported as FORECAST_INVALID (flag FORECAST_NOT_RECORDED), never as an ALLOW.
- Older decide() versions take fewer arguments (E1 had no epoch or quorum). `Implementation.call_decide` passes what
  each version accepts. The old forecast module is bound to the old contract module.
- A crashing candidate counts as AUTHORITY-MATERIAL. It can never pass as "unchanged".
