# Evolution 18 — causal evidence graph and receipt, version 2

Canonical form everywhere: JSON with sorted keys, separators `,` and `:`, ASCII only. A digest is
`sha256(canonical({"domain": D, ...fields}))` in hex; a signature is Ed25519 over the ASCII bytes of that hex digest,
base64. (Same construction as `cain45/canon.py`.)

## Node (`cain42.causal_node.v2`, domain `CAIN42/EVOLUTION18/CAUSAL-NODE/v2`)

| Field | Meaning |
|---|---|
| `kind` | `PRINCIPAL`, `MISSION_CONTRACT`, `AGENT_PASSPORT`, `CANONICAL_ACTION`, `AUTHORIZATION`, `ENFORCEMENT`, `EXECUTION`, `OBSERVATION`, `EVIDENCE_GAP`, `CONTRADICTION`, `POSTCONDITION`, `RECOVERY`, `RECEIPT`, `VERDICT` |
| `parents` | `[{id, rel}]`, sorted. `rel` is causal (`DELEGATES`, `PROPOSES`, `AUTHORIZES`, `ADMITS`, `EXECUTES`, `OBSERVES`, `DERIVES`, `COMPENSATES`, `ATTESTS`) or non-causal (`TEMPORAL`, `CORRELATES`) |
| `correlation_id` | `<mission_id>:<task_id>`; every node of a graph has the same one |
| `seq` | logical clock; strictly greater than every parent's |
| `source` | `{id, role, pub, basis}`; `basis` is `SIGNED_BY_SOURCE` (then `sig` verifies under `pub`) or `ASSERTED_BY_RECORDER` (then `sig` is null) |
| `payload` | kind-specific. `AUTHORIZATION` and `OBSERVATION` nodes *carry* a record signed by a third party (`carries: APPROVER_SIGNED_RECORDS` / `SENSOR_SIGNED_RECORD`); the node itself is the recorder's, the record inside is checked on its own signature |
| `clock` | `{t, uncertainty_s}`; informational. Never used to order nodes or to infer cause (one exception, stated: an admission's `t` is compared with the authorization's expiry) |
| `node_id` | digest of all of the above |

Rules a graph must satisfy (checked on `add`, on `load`, and again by the verifier): node digest matches; signature
matches the basis; no duplicate; one correlation id; every parent appears earlier (so no cycle can be expressed);
`seq` after every parent. Graph root = digest over the ordered list of node ids
(domain `CAIN42/EVOLUTION18/CAUSAL-GRAPH-ROOT/v2`).

`causal_path(a, d)` follows only causal relations. Two nodes joined only by `TEMPORAL` or `CORRELATES` edges are not
causally linked, whatever their timestamps.

Which nodes are context and which are evidence: `PRINCIPAL`, `MISSION_CONTRACT`, `AGENT_PASSPORT`,
`CANONICAL_ACTION` are `ASSERTED_BY_RECORDER`. The enforcer did not see the principal sign anything; those nodes repeat
what the authorization says. The authorization's own signature is what binds principal, mission and agent.

## Receipt (`cain42.execution_truth_receipt.v2`, domain `CAIN42/EVOLUTION18/EXECUTION-TRUTH-RECEIPT/v2`)

Signed by the enforcer over every field except `signature` and `graph`; the graph is bound through `graph_root`, and
the graph's `RECEIPT` node carries the digest of the receipt without `graph_root`.

`status`: `BLOCKED` (refused before anything ran) · `COMPLETED` · `FAILED` (executor raised; it claimed nothing) ·
`PARTIAL` (some effects, then stopped) · `STOPPED_BEFORE_COMMIT` (authority ended at the commit point; nothing done) ·
`AMBIGUOUS_OUTCOME` (found on restart: admitted, no outcome recorded).

Other fields: the approver-signed `authorization` and `binding` unchanged; `declared_effects`; `executor_claims`
(self-reported); `observations` (each `{observation, sig}` exactly as the sensor produced it); `sensor_failures`;
`instrumented` (the enforcer's own before/after of the declared objects); `grade`; `postconditions`;
`verdict {claim, verdict, reasons}`; `trust_level`; `toctou_protection` (`DESCRIPTOR_PINNED` or `PATH_CHECK_ONLY`);
`authority_at_completion`; `recovery`; `scope_statement {observed_roots, not_observed, independence_ceiling}`.

## Sensor observation (`cain42.sensor_observation.v2`, domain `CAIN42/EVOLUTION18/SENSOR-OBSERVATION/v2`)

`{sensor {id, method, pid, boundary, pub}, nonce, bind {auth_id, task_id, effect_set_digest},
scope {root, observable, complete, gaps}, changes [{effect, resource, kinds, type, sha256}], t_arm, t_report}`,
signed by the key generated inside the sensor process.

## Resource binding (`cain42.resource_binding.v1`, domain `CAIN42/EVOLUTION18/RESOURCE-BINDING/v1`)

Signed by the approver with the authorization: `{auth_id, effect_set_digest, tool, passport {tool, version,
body_digest}, root, resources [{resource, exists, dev, ino, size, mtime_ns, sha256, parent [dev, ino]}]}`.

## Fixtures

`fixtures/` holds receipts produced once by `scripts/evolution18/run_evidence.py`, with public anchors only:
`valid` (VERIFIED), `incomplete` (one sensor: INCOMPLETE), `contradictory` (sensors disagree: FAILED), `replayed`
(same receipt twice: FAILED), `reordered` (nodes reversed: FAILED), `tampered` (observation edited: FAILED),
`temporal_only` (a lying enforcer re-signed the graph with observations attached by time only: FAILED).
`test_stored_fixtures_verify_as_recorded` verifies them without sensors, clocks or private keys.
