Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/ledger-integrity.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Ledger integrity — usage-event tamper-evidence (#108)

> **Layer boundary:** this document covers the **dataplane** layer — storage
> integrity over the append-only event store. Authority-layer attestation
> (signed receipts binding actor, boundary, evidence, action, result) is a
> separate, independently verifiable claim; see
> [Two-layer integrity: dataplane storage root and authority-layer receipts](spec-two-layer-integrity.md)
> for the split, and [Authorized Action Receipts](authorized-action-receipts.md)
> for the authority-layer mechanism.

Plutus stores usage in an integer-exact, independently re-queryable ledger
(`SUM(usage_events.cost_micros)`). That makes the dollars *reproducible* — but,
on its own, the table was append-only **by convention** only. Nothing stopped an
Expand Down Expand Up @@ -176,7 +184,7 @@ corruption and casual tampering, not a motivated operator with DB access.

## Guardrail

**No public document may claim Plutus is "tamper-evident" until this ships _and_
**No public document may claim Plutus is "tamper-evident" until this ships *and*
an external cryptographic review covers it** (the SOW drafted for the Perseus
Vault audit-chain review). Until then, savings statements carry the caveat the
harness/one-pager already print: the ledger is *re-queryable* and now
Expand Down
94 changes: 94 additions & 0 deletions docs/spec-two-layer-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Two-layer integrity: dataplane storage root and authority-layer receipts

Status: spec
Date: 2026-08-04
Resolves: #206
Related: [ledger-integrity](ledger-integrity.md) (dataplane) ·
[authorized-action-receipts](authorized-action-receipts.md) (authority layer) ·
[evidence-receipts](evidence-receipts.md) (task-scoped receipt view) ·
[continuous-attestation](continuous-attestation.md) (#201 semantics)

Ledger separates two guarantees that are enforced by different mechanisms.
They are composable, verifiable independently, and **neither implies the
other**. This is the same split the ecosystem documents elsewhere: a dataplane
layer seals security-relevant events into a hash root, and a hosting/authority
layer signs receipts. Storage integrity and authority attestation are distinct
claims; mixing them weakens the audit story because one failing (or one
passing) says nothing about the other.

## Layer 1 — dataplane: storage integrity over the append-only event store

The dataplane layer proves the store did not silently change. It is
implemented by the per-org event hash chain and out-of-band checkpoints
(`docs/ledger-integrity.md`):

- every `usage_events` row carries `prev_hash`/`row_hash` over canonical,
column-tagged immutable fields; SHA-256 by default, HMAC-SHA256 when a key
is set;
- verification (`plutus verify`) replays the chain from genesis and fails at
the first divergence — exit 0 = intact, exit 2 = tampered;
- retained checkpoints (`plutus checkpoint` / `verify-checkpoints`) pin a
chain head out of band so a rewritten history cannot be re-chained into
agreement with a head someone else already holds.

The chain head (or a Merkle-style root over it) is a **storage-root claim
only**: it answers "did the store change?" and nothing else.

## Layer 2 — authority layer: receipts signed/attested by the control plane

The authority layer proves who authorized what, under which evidence. It is
implemented by Authorized Action Receipts (AAR, `docs/authorized-action-receipts.md`):

- a receipt binds actor, boundary (org/workspace/scope), evidence references,
action, and result;
- Vault is the authoritative control plane; Ledger commits only opaque,
validated references and hashes — never raw prompts, secrets, tool output,
or policy bodies;
- the receipt's authority bindings are checkable against the control plane's
manifests and approval state independently of any storage root.

A receipt is an **authority claim only**: it answers "who authorized what"
and nothing about whether the store has since changed.

## Independence

Each layer has its own verifier, and neither verifier replays the other:

| Claim to check | Verifier | Does not require |
|---|---|---|
| Storage did not change | `plutus verify` / `verify-checkpoints` (chain + retained heads) | Replaying receipt signatures; any receipt at all |
| Receipt is genuine and authorized | AAR authority checks (manifest state, approval state, opaque reference validity) | Replaying the storage chain; the chain root |

The two verifiers may run in any order, on different machines, at different
times. A receipt can be verified long after the storage root has rotated
away, and the storage root can be verified with no authority material
present.

## Failure cases are distinguishable

| Case | Dataplane verdict | Authority verdict | Distinguishable outcome |
|---|---|---|---|
| Tampered store, valid receipts | **broken** — chain/checkpoint divergence (`plutus verify` exit 2) | **intact** — receipts still verify against manifests | Storage compromised; authority trail intact. Do not conflate "receipts check out" with "store intact". |
| Valid store, revoked/absent signature | **intact** — chain verifies | **failed** — signature missing or manifest revoked | Store intact; authority compromised/expired. Do not conflate "chain verifies" with "action was authorized". |
| Both intact | intact | intact | The only case where "what happened" and "it was authorized" both hold. |
| Both failed | broken | failed | Either layer independently broken; both must be remediated. |

The receipt record keeps the two verdicts separate: `verify` output never
silently "passes" a receipt-bearing org whose chain is broken, and a receipt's
authority fields are never inferred from chain position.

## Composition

Where both layers apply to the same event, they compose in the obvious way:
the dataplane root proves the event is the event, the authority receipt proves
the event was authorized. Neither claim upgrades the other — in particular, a
valid signature does not repair a broken chain, and an intact chain does not
authorize an unsigned event.

## Non-goals

- No new cryptographic primitive or schema change is required by this spec;
it canonizes the existing split (chain/checkpoints vs. AAR receipts).
- The two-layer model is not a claim that every recorded event needs an
authority receipt; receipts attach where authority applies (authorized
actions), while the dataplane layer covers the whole append-only stream.
Loading