diff --git a/README.md b/README.md index 001eea2..f08b152 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ This is a product and architecture position, not a claim of handling CUI or sati Each product is useful on its own and integrates through documented, runtime-neutral contracts. Ledger does not require Perseus, Vault, or any specific agent runtime. +For the governance bridge between durable memory decisions, recall posture, and +hash-only Ledger evidence, see [Memory governance and Ledger provenance](docs/memory-governance-provenance.md). +For a copy-pasteable local setup, see [Local Perseus + Vault + Ledger integration](docs/local-perseus-vault-ledger.md). + ## Quick start: record a verifiable event ```bash diff --git a/docs/authorized-action-receipts.md b/docs/authorized-action-receipts.md index 4a3df69..d013d3c 100644 --- a/docs/authorized-action-receipts.md +++ b/docs/authorized-action-receipts.md @@ -16,6 +16,11 @@ references and hashes and commits them to the organization event chain. This keeps raw prompts, secrets, tool output, and policy bodies outside the Ledger while preserving an independently verifiable evidence trail. +For the related memory retention, admission, deletion, curation, and recall +posture mapping, see [Memory governance and Ledger provenance](memory-governance-provenance.md). +The [local Perseus + Vault + Ledger integration guide](local-perseus-vault-ledger.md) +shows the same boundary in a scratch deployment. + ## Roles | System | Responsibility | diff --git a/docs/evidence-receipts.md b/docs/evidence-receipts.md index 4e1e059..00877aa 100644 --- a/docs/evidence-receipts.md +++ b/docs/evidence-receipts.md @@ -1,5 +1,10 @@ # Perseus Evidence Receipts +For the cross-product mapping from memory lifecycle and recall posture to these +hash-only fields, see [Memory governance and Ledger provenance](memory-governance-provenance.md). +For a local Perseus + Vault + Ledger walkthrough, see +[the local integration guide](local-perseus-vault-ledger.md). + An evidence receipt is a task-scoped, machine-readable view of hash-chained Ledger events. It answers a bounded question: > For this externally identified task or artifact, which recorded autonomous-system actions exist, what resource allocation accompanied them, and does their containing organization ledger verify? diff --git a/docs/local-perseus-vault-ledger.md b/docs/local-perseus-vault-ledger.md new file mode 100644 index 0000000..5c52dea --- /dev/null +++ b/docs/local-perseus-vault-ledger.md @@ -0,0 +1,431 @@ +# Local Perseus + Vault + Ledger integration + +This guide wires the three products together on one workstation with explicit, +throwaway paths: + +- **Perseus** resolves the active workspace context and selects the recall + posture. +- **Perseus Vault** is the encrypted local memory/control plane. It owns + admission, retention, deletion, curation, visibility, and recall. +- **Perseus Ledger** is the evidence layer. It records supplied usage, + provenance references, and hash-covered receipts; it does not own Vault's + lifecycle. + +The walkthrough uses no provider credentials, prompts, memory bodies, or +network transport. It creates one encrypted Vault database and one local +Ledger database under `$HOME/.perseus-ledger-local` by default. The smoke event +uses zero tokens and zero cost and exists only to verify the receipt and +integrity paths. + +For the governance mapping behind the hash-only fields, see +[Memory governance and Ledger provenance](memory-governance-provenance.md). + +## 1. Prerequisites and one environment + +The commands below use `uv` so Perseus and the local Ledger package share one +Python environment. They use the public Perseus Vault installer for the Rust +binary. + +```bash +set -eu +umask 077 + +ROOT="${ROOT:-$HOME/.perseus-ledger-local}" +LEDGER_SOURCE="${LEDGER_SOURCE:-$ROOT/src/ledger}" +mkdir -p "$ROOT/src" + +# Install the current public Ledger checkout only when one is not supplied. +if [ ! -f "$LEDGER_SOURCE/pyproject.toml" ]; then + git clone --depth 1 https://github.com/Perseus-Computing-LLC/ledger.git "$LEDGER_SOURCE" +fi + +# Keep Perseus and plutus-agent in the same environment: Perseus's optional +# local metering imports plutus_agent lazily from this interpreter. +if [ ! -x "$ROOT/.venv/bin/python" ]; then + uv venv "$ROOT/.venv" +fi +uv pip install --python "$ROOT/.venv/bin/python" perseus-ctx +uv pip install --python "$ROOT/.venv/bin/python" -e "$LEDGER_SOURCE" +export PATH="$ROOT/.venv/bin:$HOME/.local/bin:$PATH" + +# Install the local Vault binary if it is not already available. +if ! command -v perseus-vault >/dev/null 2>&1; then + curl -sSf https://raw.githubusercontent.com/Perseus-Computing-LLC/perseus-vault/main/scripts/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" +fi + +command -v perseus +command -v perseus-vault +command -v plutus +``` + +If the binary is built from source instead, `cargo install --git +https://github.com/Perseus-Computing-LLC/perseus-vault` supplies the same +`perseus-vault` command. Keep the command name and the explicit paths below; +do not rely on an implicit database selected from a different installation. + +## 2. Declare the paths + +These are the paths used by every subsequent command. Explicit paths avoid +accidentally opening a second Vault database or a second Ledger database. + +```bash +VAULT_BIN="$(command -v perseus-vault)" +WORKSPACE="$ROOT/workspace" +VAULT_DIR="$ROOT/vault" +VAULT_DB="$VAULT_DIR/perseus-vault.db" +VAULT_KEY="$VAULT_DIR/secret.key" +LEDGER_ROOT="$ROOT/ledger-state" +LEDGER_DB="$LEDGER_ROOT/plutus.db" +PERSEUS_HOME="$ROOT/perseus-home" + +# Opaque smoke-test references. They are identifiers, not memory content. +ORG_REF="org_local_ledger_check" +PROVIDER_REF="provider_local" +MODEL_REF="model_local" +TASK_REF="task_receipt_check" +WORKSPACE_REF="workspace_local" +RECEIPT_REF="ref_local_ledger_check" + +mkdir -p "$WORKSPACE/.perseus" "$VAULT_DIR" "$LEDGER_ROOT" "$PERSEUS_HOME" + +# Current config path/environment contracts. +export PERSEUS_HOME +export PLUTUS_HOME="$LEDGER_ROOT" +export PLUTUS_CONFIG="$LEDGER_ROOT/config.yaml" +export PLUTUS_DB="$LEDGER_DB" +``` + +The resulting layout is: + +| Component | Path | +|---|---| +| Perseus workspace config | `$WORKSPACE/.perseus/config.yaml` | +| Perseus context source | `$WORKSPACE/.perseus/context.md` | +| Perseus rendered output | `$WORKSPACE/AGENTS.md` | +| Perseus global home | `$PERSEUS_HOME` | +| Vault database | `$VAULT_DB` | +| Vault AES-256-GCM key file | `$VAULT_KEY` | +| Ledger config | `$PLUTUS_CONFIG` | +| Ledger SQLite database | `$LEDGER_DB` | +| Perseus metering health | `$ROOT/metering-status.json` | + +`perseus-vault` currently prefers the explicit `--db` path. Its fresh-install +default is under `~/.perseus-vault`, and older installations may be discovered +under compatibility paths; this guide deliberately bypasses that discovery. + +## 3. Initialize and check encrypted Vault + +`keygen` writes a raw 32-byte AES-256-GCM key file. `init` creates the database, +enables encryption, and writes the encryption canary. The key is never put in +SQLite or in this document. + +```bash +# Never generate a replacement key for an existing database. +if [ -e "$VAULT_DB" ] && [ ! -f "$VAULT_KEY" ]; then + printf '%s\n' 'Vault DB exists but its key file is missing; stop rather than rotating blindly.' >&2 + exit 1 +fi + +if [ ! -f "$VAULT_KEY" ]; then + "$VAULT_BIN" keygen --key-file "$VAULT_KEY" +fi +if [ ! -f "$VAULT_DB" ]; then + "$VAULT_BIN" init --db "$VAULT_DB" --key-file "$VAULT_KEY" +fi +chmod 600 "$VAULT_KEY" 2>/dev/null || true + +# doctor reports the on-disk encryption state without printing key material. +"$VAULT_BIN" doctor --db "$VAULT_DB" + +# A report-only maintenance pass: no curation, archive, purge, or VACUUM is applied. +"$VAULT_BIN" maintain --db "$VAULT_DB" \ + --encryption-key "$VAULT_KEY" --dry-run +``` + +Treat these states differently: + +- `Encrypted`/canary-present is the expected result for this walkthrough. +- `Plaintext` means this database was not initialized by the block above; do + not treat it as an encrypted deployment. +- A wrong or missing key for an encrypted database is a stop condition. Pass + the same explicit key to `serve` and every write-capable maintenance command. +- A mixed state requires the Vault migration/rekey procedure owned by Vault; + do not repair it by deleting or replacing the key file. + +## 4. Initialize Ledger and write the Perseus config + +Ledger's current local configuration contracts are `PLUTUS_HOME`, +`PLUTUS_CONFIG`, and `PLUTUS_DB`. `plutus init` creates the config/database; +the optional hash-chain HMAC is `ledger.hmac_key` or the +`PLUTUS_CHAIN_HMAC_KEY` environment variable. This local smoke test leaves the +optional HMAC secret unset and uses the default SHA-256 chain. + +```bash +if [ ! -f "$LEDGER_DB" ]; then + plutus init --org "$ORG_REF" +else + plutus init +fi +``` + +Write the workspace-local Perseus config. Every key below is from the current +`perseus_vault` connector and `plutus` metering contracts; the Vault command +contains both the database path and the key path so the MCP child cannot select +an unintended store. + +```bash +cat >"$WORKSPACE/.perseus/config.yaml" <"$WORKSPACE/.perseus/context.md" <<'EOF' +@perseus +@vault query="ref_local_context" k=5 +EOF +``` + +`profiles.default.memory: on_demand` is the recall-first posture: Perseus +renders a retrieval pointer rather than a pre-materialized memory dump. Use +`relevant` only when trigger-matched injection is explicitly desired. An +unconditional `always` posture is an explicit compatibility choice, not the +recommended default for a consequential action. + +The `plutus` block enables only provider-usage metering. It does not infer Vault +lifecycle decisions or context provenance. The host that performs a +consequential action must explicitly send the hash-only context bindings +shown in [Memory governance and Ledger provenance](memory-governance-provenance.md). + +## 5. Run Perseus, Vault, and MCP checks + +First run Perseus's readiness check. It attempts the Vault MCP handshake and a +health call when `perseus_vault.enabled` is true. The filter below prints no +paths, bodies, or configuration values. + +```bash +perseus doctor --workspace "$WORKSPACE" --json >"$ROOT/perseus-doctor.json" + +"$ROOT/.venv/bin/python" - "$ROOT/perseus-doctor.json" <<'PY' +import json +import sys + +with open(sys.argv[1], encoding="utf-8") as handle: + report = json.load(handle) +checks = {item["id"]: item for item in report.get("checks", [])} +for required in ("vault_connectivity", "mcp_server"): + if checks.get(required, {}).get("status") != "ok": + raise SystemExit(f"Perseus doctor did not pass {required}") +print("Perseus doctor: Vault bridge and MCP checks passed") +PY +``` + +Then exercise the Vault binary directly over MCP stdio. This sends only +protocol metadata and a health request; it does not write a memory entity. + +```bash +printf '%s\n' \ + '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"local-ledger-check","version":"1"}}}' \ + '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}' \ + '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \ + '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"perseus_vault_health","arguments":{}}}' \ +| "$VAULT_BIN" serve --db "$VAULT_DB" --encryption-key "$VAULT_KEY" \ +| "$ROOT/.venv/bin/python" -c ' +import json +import sys + +responses = {} +for line in sys.stdin: + try: + item = json.loads(line) + except json.JSONDecodeError: + continue + if "id" in item: + responses[item["id"]] = item + +if "error" in responses.get(1, {}): + raise SystemExit("Vault initialize failed") +tools = responses.get(2, {}).get("result", {}).get("tools", []) +names = {tool.get("name") for tool in tools} +if "perseus_vault_recall" not in names: + raise SystemExit("canonical Vault recall tool was not advertised") +health = responses.get(3, {}).get("result") +if not isinstance(health, dict) or health.get("isError"): + raise SystemExit("Vault health call failed") +print(f"Vault MCP: {len(names)} tools advertised; health call returned") +' +``` + +Finally render the context from the workspace. An empty result from a healthy +new Vault is a valid no-match state; it is not the same as an unreachable Vault. + +```bash +( + cd "$WORKSPACE" + perseus render .perseus/context.md --output AGENTS.md --strict +) +``` + +## 6. Harmless Ledger receipt and dry-run verification + +Create one synthetic, zero-cost event in the scratch Ledger. `--ref` becomes +`external_ref`, so the event can be selected by the task-scoped receipt without +embedding a prompt or memory body. + +```bash +plutus meter \ + --org "$ORG_REF" \ + --provider "$PROVIDER_REF" \ + --model "$MODEL_REF" \ + --task "$TASK_REF" \ + --workspace "$WORKSPACE_REF" \ + --input 0 --output 0 --cost 0 \ + --ref "$RECEIPT_REF" --json + +# Read-only chain verification. Exit 0 is required. +plutus verify --org "$ORG_REF" --json + +# Reconciliation is dry-run unless --apply is supplied. This writes nothing. +plutus reconcile --org "$ORG_REF" \ + --provider "$PROVIDER_REF" --amount 0 --json +``` + +Run the local receipt endpoint on loopback and check only its contract fields. +The default Ledger config has dashboard auth disabled for localhost; if a local +operator enables auth, use the normal org-scoped API key out of band rather than +putting it in a workspace file. + +```bash +LEDGER_PORT="${LEDGER_PORT:-18420}" +plutus serve --host 127.0.0.1 --port "$LEDGER_PORT" \ + >"$LEDGER_ROOT/server.log" 2>&1 & +LEDGER_PID=$! +cleanup_ledger() { + kill "$LEDGER_PID" 2>/dev/null || true + wait "$LEDGER_PID" 2>/dev/null || true +} +trap cleanup_ledger EXIT + +ready=0 +i=0 +while [ "$i" -lt 50 ]; do + if curl -fsS "http://127.0.0.1:${LEDGER_PORT}/healthz" >/dev/null 2>&1; then + ready=1 + break + fi + sleep 0.1 + i=$((i + 1)) +done +test "$ready" -eq 1 + +curl -fsS "http://127.0.0.1:${LEDGER_PORT}/api/audit?external_ref=${RECEIPT_REF}" \ +| RECEIPT_REF="$RECEIPT_REF" "$ROOT/.venv/bin/python" -c ' +import json +import os +import sys + +receipt = json.load(sys.stdin) +if receipt.get("receipt_version") != "perseus-evidence-receipt/v1": + raise SystemExit("unexpected receipt version") +if receipt.get("external_ref") != os.environ["RECEIPT_REF"]: + raise SystemExit("receipt correlation mismatch") +if receipt.get("verification", {}).get("chain_ok") is not True: + raise SystemExit("Ledger chain did not verify") +for event in receipt.get("events", []): + encoded = json.dumps(event, sort_keys=True).lower() + if any(forbidden in encoded for forbidden in ("prompt", "body", "credentials", "tool_arguments")): + raise SystemExit("receipt contains a forbidden raw-material field") +print(f"Ledger receipt: {len(receipt.get('events', []))} event(s), chain verified, raw-material check passed") +' +``` + +For a production or consequential action, the usage event can add the +existing hash-only fields below. The API key, if the Ledger instance requires +one, comes from an environment or secret manager and is not part of this +configuration. + +```json +{ + "provider": "provider_id", + "model": "model_id", + "task_type": "task_id", + "workspace": "workspace_id", + "input_tokens": 0, + "output_tokens": 0, + "external_ref": "ref_7c91d2a4", + "evidence_hashes": [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ], + "policy_version": "policy_4f18c0e2", + "result_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "context_render_schema": "perseus-context-render-trace/v1", + "context_render_hash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "served_memory_provenance_hash": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "action_receipt_hash": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +} +``` + +When any context-render digest is supplied, include +`context_render_schema`. Ledger rejects malformed digest values and +hash-covers every supplied optional field. It does not verify the external +artifact itself; the caller must recompute that digest in the owning system. + +## 7. Degraded states and safe decisions + +| State | Check | Meaning and safe response | +|---|---|---| +| Vault binary missing or MCP health fails | `perseus doctor --workspace "$WORKSPACE" --json`; inspect `vault_connectivity` | `fallback_to_local: true` may keep a render alive, but local fallback is not proof of durable Vault recall. Hold/abstain when the action requires Vault. | +| Vault health is successful but recall is empty | `perseus_vault_health` plus the render result | This can be a healthy no-match. Do not call it an outage or invent evidence. | +| Encrypted DB with a missing/wrong key | `perseus-vault doctor --db "$VAULT_DB"`; explicit `--encryption-key` on serve/maintenance | Stop. Do not generate a replacement key or allow plaintext writes beside ciphertext. | +| Ledger metering is disabled or degraded | `perseus doctor --workspace "$WORKSPACE" --json`; inspect `plutus_metering`; inspect `$ROOT/metering-status.json` when present | `fail_open` behavior keeps the caller running but dropped events make evidence incomplete. Reconcile before claiming coverage. | +| Receipt has `chain_ok: false` | `plutus verify --json` and the receipt's `verification` object | Stop evidence claims and investigate the first divergence. A later receipt cannot repair a broken chain. | +| Ledger endpoint unavailable | local process health and the metering status file | Do not silently label an action evidenced. Retry or record an explicit held/degraded outcome in the owning control plane. | + +Ledger's `verify` command and a receipt's `chain_ok` are necessary checks, not a +claim that Vault's lifecycle decision was correct. Keep the owning Vault and +control-plane references available for any later verification. + +## 8. Migration and related contracts + +- [Memory governance and Ledger provenance](memory-governance-provenance.md) + (#199) explains retention, admission, deletion, curation, and recall-posture + linkage. +- [Vault migration guide](https://github.com/Perseus-Computing-LLC/perseus-vault/blob/main/docs/migration/legacy-tool-prefixes.md) + covers the canonical MCP tool-prefix transition. New config and integrations + in this guide use `perseus_vault_*` names directly. +- [Vault encryption specification](https://github.com/Perseus-Computing-LLC/perseus-vault/blob/main/docs/ENCRYPTION.md) + documents AES-256-GCM scope, key custody, and the plaintext FTS caveat. +- [Perseus setup and configuration](https://github.com/Perseus-Computing-LLC/perseus/blob/main/SETUP-GUIDE.md) + documents the `perseus_vault` connector and recall postures. +- [Evidence Receipts](evidence-receipts.md) and + [Authorized Action Receipts](authorized-action-receipts.md) document the + Ledger-side receipt contracts. + +Do not place API keys, encryption key material, raw prompts, raw tool output, or +memory bodies in `.perseus/config.yaml`, `AGENTS.md`, Ledger receipts, or +tracked documentation. diff --git a/docs/memory-governance-provenance.md b/docs/memory-governance-provenance.md new file mode 100644 index 0000000..be18a06 --- /dev/null +++ b/docs/memory-governance-provenance.md @@ -0,0 +1,166 @@ +# Memory governance and Ledger provenance (#199) + +Perseus resolves active context. Perseus Vault owns durable memory and its +lifecycle. Perseus Ledger records what an integration supplies as +hash-covered evidence. These are complementary responsibilities, not one +shared policy engine. + +This page defines the boundary for a cross-product integration. It does not +make Ledger a memory store, a retention controller, an admission service, or an +authorization engine. + +## The ownership boundary + +| Layer | Owns | Does not claim | +|---|---|---| +| **Perseus** | Workspace resolution, the selected recall posture, render identity, and the decision to make a context available to a caller | That a memory was durable, authoritative, or retained by merely rendering a reference | +| **Perseus Vault** | Admission decisions, workspace/visibility checks, retention and history policy, archive/purge behavior, correction and curation, and recall results | That a downstream action occurred or that an action was reported to Ledger | +| **Perseus Ledger** | The supplied event, opaque correlation references, supplied evidence digests, decision context, optional action provenance, context-render bindings, and the per-organization hash chain | Vault lifecycle enforcement, recall ranking, deletion, approval, or facts that were never supplied | + +A Ledger receipt therefore answers a bounded question: *which hash-covered +claims and resource facts were supplied for this event, and does the containing +Ledger chain verify?* It cannot answer whether Vault should have retained a +record or whether every action was reported. + +## The hash-only projection + +A consequential action can carry a compact projection of the context and memory +state that influenced it. Values that identify a record, policy, workspace, or +artifact must be opaque references; content is represented by a full lowercase +SHA-256 digest. The fixed schema value below is not a memory body. + +```json +{ + "external_ref": "ref_7c91d2a4", + "evidence_hashes": [ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ], + "policy_version": "policy_4f18c0e2", + "result_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "human_review": "approved", + "correction_ref": null, + "context_render_schema": "perseus-context-render-trace/v1", + "context_render_hash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "served_memory_provenance_hash": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "action_receipt_hash": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" +} +``` + +The projection uses existing Ledger fields: + +- `external_ref` is an opaque, tenant-scoped correlation reference. It is the + selector used by the task-scoped Evidence Receipt. +- `evidence_hashes` is a canonical sorted, de-duplicated list of source or + decision digests. A Vault admission `record_digest` or `decision_digest` + may be included when the action depends on that admission result. +- `policy_version` identifies the immutable policy/configuration reference in + effect. It is a reference, not the policy body. +- `result_hash` identifies the output artifact or conclusion without copying it + into Ledger. +- `human_review` is the bounded value `approved`, `rejected`, or `corrected`; + `correction_ref` is required for `corrected` and remains opaque. +- `context_render_schema` is required when any context-render binding is + supplied. `context_render_hash` binds the rendered context, while + `served_memory_provenance_hash` binds the hash-only explanation of which + Vault records were served and why. `action_receipt_hash` can bind an + upstream control-plane receipt. + +Ledger validates the digest-shaped fields and hash-covers every supplied value +in the event chain. It does not dereference any digest, inspect Vault, or infer +missing fields. + +## Lifecycle decisions and evidence linkage + +The lifecycle remains in Vault. The integration decides which bounded +references and digests are material to a later verification, then supplies +those values to Ledger at the time of the consequential event. + +| Vault/Perseus decision | Control-plane owner | Hash-only Ledger linkage | Boundary to preserve | +|---|---|---|---| +| **Admission** — accept, quarantine, suppress, escalate, abstain, or revoke a record | Vault | Include the relevant source/admission digests in `evidence_hashes`; identify the policy with `policy_version`; correlate with `external_ref` | Ledger records the supplied admission evidence. It does not re-evaluate the admission outcome or make a non-authoritative record visible. | +| **Retention** — keep current state, retain history, compact, or apply a bounded retention policy | Vault | Use an opaque policy reference in `policy_version` and, when applicable, a checkpoint or decision digest in `evidence_hashes` | Ledger does not copy Vault history, extend Vault retention, or promise that a retained Ledger event preserves a deleted memory body. | +| **Deletion** — archive with `perseus_vault_forget`, bulk archive with the curation tools, or permanently purge with `perseus_vault_purge` | Vault | For a consequential deletion, bind an action intent/approval projection and the deletion result digest; use `action_status`, `action_intent_hash`, `approval_ref`, `result_hash`, and `external_ref` as applicable | Vault owns the deletion and its authorization. A Ledger event is evidence that a supplied deletion claim was reported, not proof that Ledger deleted or can restore Vault content. | +| **Curation** — correct, supersede, consolidate, promote, demote, or run maintenance | Vault, with any required operator/control-plane approval | Bind the source-set, correction, successor, or maintenance-report digests; use `correction_ref` for a correction and `evidence_hashes` for the supporting set | Ledger does not choose the winning fact, rewrite Vault history, or turn a derived summary into an authoritative source. | +| **Recall posture** — `on_demand`, `relevant`, or an explicit `always` posture | Perseus selects the posture; Vault enforces recall/visibility invariants | Record an opaque posture/policy reference in `policy_version`, the actual `context_render_hash`, the served-memory provenance digest, and the action receipt digest | Ledger records the posture used by the caller. It does not select recall mode, rank memories, or treat a rendered context as durable memory. | + +For a derived curation result, hash the final result and its supporting set +after all destination scope and policy metadata are assembled. Do not copy a +source admission envelope onto a transformed record. + +## A concrete evidence flow + +1. **Vault decides.** Vault validates source identity, scope, trust, time, and + relevance, then creates or updates its own admission evidence and lifecycle + state. Quarantined, suppressed, escalated, abstained, and revoked outcomes + remain non-authoritative under Vault's rules. +2. **Perseus resolves.** Perseus uses the configured recall posture and + workspace scope to ask Vault for the context needed for the current task. + The render path can produce a versioned, hash-only trace of the served + memory references and reasons. +3. **The action boundary binds.** The action runner keeps the raw context and + memory bodies in their owning systems. It computes the render/result + digests, retains opaque control-plane references, and decides whether the + action is allowed to proceed. +4. **Ledger records.** The runner sends the normal usage event to + `POST /v1/usage`, adding only the optional hash/reference fields supported by + the current contract. The optional fields are trailing and preserve + compatibility for events that do not carry them. +5. **The receipt is checked.** A caller retrieves + `GET /api/audit?org=&external_ref=`, checks the receipt + version and every expected binding, and requires `verification.chain_ok`. + A retained external checkpoint is stronger than an anchor stored only in + the same operator-controlled database; see [Ledger integrity](ledger-integrity.md). + +The final receipt is evidence of Ledger-recorded activity. It is not evidence +that the upstream runner reported every action or that Vault's lifecycle policy +was correct. + +## Retention, deletion, and curation without overclaiming + +Use these rules when designing a connector or an operator runbook: + +1. **Record the policy reference, not the policy body.** A policy change should + produce a new opaque `policy_version`. The event that used it can then be + compared with the policy artifact held by the control plane. +2. **Record decisions at the decision boundary.** If admission, retention, + deletion, or curation changes whether a later action can occur, emit an + event after the control plane has decided and before the action is claimed as + complete. A later receipt cannot reconstruct an unreported decision. +3. **Hash the supporting set.** A source-set digest, result digest, or externally + retained checkpoint lets a verifier detect substitution without sending the + source bytes to Ledger. +4. **Keep erasure semantics explicit.** Vault may permanently purge a body or + history. Keep only the references and digests that the applicable retention + policy permits. A surviving Ledger row proves the supplied event, not the + erased bytes. +5. **Treat degraded recall as a different posture.** Local fallback, an empty + result, an unavailable Vault process, and a healthy Vault with no matches are + different states. If a consequential action requires durable Vault recall, + hold or abstain when the required integration is unavailable; do not let a + generic empty result become a provenance claim. +6. **Verify before making a claim.** `chain_ok` proves the Ledger chain it + verifies. It does not validate a digest against an external artifact, so the + verifier must also resolve the permitted external reference and recompute + the expected digest in the owning system. + +## Privacy boundary + +The cross-product projection MUST NOT contain credentials, raw prompts, raw +memory bodies, raw tool arguments, or raw action results. It may contain only: + +- bounded status/enum values; +- opaque IDs and correlation references; and +- full SHA-256 digests of source, context, result, policy, checkpoint, or + control-plane artifacts. + +If a value cannot be safely represented as an opaque reference or digest, keep +it in Vault or in the owning control plane and do not send it to Ledger. + +## Related contracts + +- [Evidence Receipts](evidence-receipts.md) — task-scoped receipt shape and + hash-covered decision context. +- [Authorized Action Receipts](authorized-action-receipts.md) — Vault-owned + authority and approval boundary with Ledger-side provenance. +- [Local Perseus + Vault + Ledger integration](local-perseus-vault-ledger.md) — + copy-pasteable local wiring and degraded-state checks.