DOCS

Start with the local service, add memory, recall with source quotes, and verify governed-action receipts with a pinned public key.

Governance & Verifiable Receipts

The proof boundary: provable governance with cryptographically-signed receipts anyone can verify offline, with a pinned signer key.

Three GDPR rights underpin Shomei Governed Memory's governance model:

  • Art. 17 (right to be forgotten): subject-scoped governed crypto-erasure
  • Art. 18 (right to restriction): reversible, non-destructive hold on recall
  • Art. 15 (right of access): portable subject-access export with proof

Every governed operation — deletion, restriction, export — emits an ed25519-signed, content-free receipt. Signed context traces use the same receipt envelope for recall integrity. A key-holder (DPO, General Counsel, auditor, or data subject) verifies the receipt offline, locally, using only public-key crypto and a pinned signer key. No access to the operator's systems. No network calls.

The Verification Ladder

Tier 1 (metadata): "we deleted it" — operator-reported, unverified.

Tier 2 (shipped today): Signed receipts + offline verification. A receipt authenticates the operator's signed statement and makes later byte tampering detectable. It does not independently observe that deletion happened. Verification requires pinning the signer's public key out-of-band, but no access to the operator's systems. This is what ships today.

Tier 3 (Business target; canary pipeline, product integration NOT shipped): A dedicated application KMS key plus an independently retrievable destruction bundle. An auditor independently retrieves AWS CloudTrail's rotating public key and verifies the AWS-signed digest, the referenced log hash, the AWS-internal DeleteKey event, and the PCLD-style key-state root. This proves deletion of the designated KMS key. It does not prove that plaintext was never copied or that every artifact was exclusively governed by that key.

Tier 4 (Enterprise target, NOT shipped): TEE-attested key handling plus Tier 3 destruction evidence. A measured enclave would bind plaintext key release and use to approved code, while the same independently verified KMS evidence closes the root-key lifecycle. Independently attested enumeration is a separate requirement; today's receipts attest the delivered set as of the signed timestamp, not that a dishonest operator's enumeration was complete.

Key custody is the proof boundary. A receipt is only as trustworthy as the pinned public key. Pin the signer key out-of-band (CISO/DPO infrastructure), not from the service you are auditing.


Crypto-Erasure (Art. 17): Subject Forgetting

The flow:

from mochi_memory import RemoteMemory

m = RemoteMemory.from_env()  # SHOMEI_BASE_URL, SHOMEI_API_KEY (admin-scoped)

# Erase all live memories for a subject (Art. 17)
result = m.forget(user_id="dana_smith")
# {
#   "subject": "dana_smith",
#   "erased_count": 41,
#   "deferred_count": 1,                # under legal hold; auto-erases on hold release
#   "external_delete_pending_count": 0, # direct provider rows still owed
#   "source_external_delete_pending_count": 0, # inference provider rows still owed
#   "erased": [ ... ],                  # list of erased memory ids
#   "deferred_held": [ ... ],           # ids deferred under hold (restricted now)
#   "receipt_hashes": [ {"id": ..., "receipt_hash": ...}, ... ],
#   "signed_receipts": [ {"id": ..., "signed_receipt": { ... }}, ... ],
#   "restriction_receipts": [ ... ],
#   # closure block (present when a signer + commitment key are configured):
#   "closure_receipt": { ... },
#   "closure_receipt_hash": "sha256:...",
#   "signed_closure_receipt": { ... },
#   "note": "governed crypto-erasure (Art.17). ..."
# }

forget requires an admin-scoped bearer key. There is no top-level signed_receipt — per-memory proofs are under signed_receipts (a list), and the subject-level proof is signed_closure_receipt. The matched-count and disposition live inside the closure receipt body, not at the top level.

For an external vector backend, nonzero external_delete_pending_count or source_external_delete_pending_count means local DEK destruction committed but exact provider absence has not been confirmed. The HTTP surface returns a structured retryable 503, not a successful erasure. Engine-side subject_closure_state() and ClosureChecker keep fully_erased / fully_erased_now false while attributable provider debt remains; a legacy pending row with no exact retry-owner link is conservatively counted for every subject too.

Subject forget commits an opaque governed+inference target journal before erasing the first target. A crash between targets is replayed on workspace reopen. This is a crash-resumable saga, not cross-provider ACID; provider debt or an unresolved pre-crash upsert keeps global success and fully_erased false.

External deployments require more than an immutable operator binding_id and causally_ordered_point_reads=True. The adapter must also inject a provider-enforced revision-CAS and idempotency implementation with a provider-issued immutable resource id. Every upsert/delete carries a pre-persisted random operation id and the workspace's exact provider revision; the provider verifies that revision at mutation apply time, atomically advances it with the mutation, retains crash-reconcilable operation status, and fences point reads and candidate queries at the same exact revision. After local settlement, provider acknowledgement must synchronously destroy the operation-status mapping to the vector id/result/receipt (a random operation-id/revision tombstone may remain). Bootstrap must prove the resource empty and never bound. The fencing authority must hold the sole mutation credentials—an ordinary SDK call behind a client-only lock or boolean is not conforming.

Shomei persists the binding and provider revision, refuses local/provider/resource drift, refuses to adopt a provider's newer revision from an old workspace copy, and reconciles an applied-but-unrecorded operation only through its pre-persisted operation id. The bundled Pinecone, Azure, Databricks, pgvector, and Snowflake SDK translators default to unsupported for the synchronous external-delete claim unless such a genuine provider-side implementation is injected. API-level absence still does not prove deletion from provider replicas, backups, transport/application logs, or physical media. The local no-readable-copy claim also assumes no writable/offline copy of the SQLite workspace exists; provider fencing prevents a stale copy from resurrecting a remote vector, but cannot erase readable payload bytes inside that copied database.

What happens:

  1. Every LIVE memory for the subject is found.
  2. Each record's per-row data-encryption key is destroyed (not just a tombstone). At-rest cells are sealed with per-row DEK envelope encryption — this is NOT SQLCipher page-encryption; destroying the row's DEK makes that ciphertext cryptographically undecryptable (under production KMS custody).
  3. Once the per-row DEK is destroyed under production KMS custody, that row's ciphertext is cryptographically undecryptable even if recovered from a data-volume backup. Honest scope: wrapped-KEK destruction is best-effort on SSD/FTL/copy-on-write media, and a KEK under a CMK only scheduled for deletion stays recoverable via KMS CancelKeyDeletion until the AWS window elapses; the receipt does not assert that copies in other systems were erased.
  4. Held memories are NOT erased now: they are RESTRICTED (Art. 18, out of recall) and carry a durable obligation that auto-fires the crypto-erase when the hold releases (reported under deferred_held / deferred_count).
  5. A signed subject-closure receipt is minted, attesting the closed set, counts, and disposition.

The subject-closure receipt body (shomei.subject_closure.v1, or shomei.subject_closure.v2 when the enriched combined-store fields are present) carries:

  • subject_pseudonym — a keyed commitment, not the raw user_id
  • closed_set_commitment — a keyed commitment over the erased set (auditor-reproducible)
  • closed_count, matched_count, erased_count, deferred_count, live_count
  • dispositioncomplete, partial, deferred, or noop_no_match (re-derived from the counts; a body can never be signed with an overclaiming disposition)
  • match_basisexact_user_id or scoped_user_id
  • applied_at — the timestamp the operation took effect
  • nonce — fresh per forget() call, so repeated forget attempts have distinct signed bodies

Honest non-claims (preserved in the framing):

  • Does NOT assert removal from copies, backups, or third-party systems outside the governed store
  • Does NOT erase facts inferable from correlated records elsewhere
  • Subject identity is pseudonymized, not anonymized (the holder of the tenant key can re-derive it)
  • Attests the at-rest state at the signed time; an auditor recomputes the commitment to verify current state

Processing Restriction (Art. 18): Reversible Hold

Restriction takes a memory out of recall without destroying it — distinct from a legal hold (which preserves and allows use). A restricted memory is excluded from recall/search/get but remains available for audit, subject-access, and legal claims.

The restrict() / release_restriction() verbs exist on both SDK surfaces: the in-process mochi_memory.Memory and the HTTP RemoteMemory (which calls POST /v1/memories/{id}/restrict to restrict and DELETE /v1/memories/{id}/restrict to lift — both admin-scoped). The two surfaces return slightly different shapes: the in-process verb returns {"memory_id", "restricted", ...} with a note; RemoteMemory returns the service shape {"id", "restricted", "changed", ...}.

The flow (in-process SDK):

from mochi_memory import Memory

m = Memory.from_env()

# Restrict a single memory from use (e.g., pending legal review)
result = m.restrict(memory_id="lm_abc123", reason="art18_request")
# {
#   "memory_id": "lm_abc123",
#   "restricted": true,
#   "receipt_hash": "sha256:...",
#   "signed_receipt": { ... },
#   "note": "Art.18 restriction: out of recall, preserved + reversible; ..."
# }

# Lift the restriction later (non-destructive)
result = m.release_restriction(memory_id="lm_abc123")
# { "memory_id": "lm_abc123", "restriction_lifted": true,
#   "receipt_hash": "sha256:...", "signed_receipt": { ... } }

The reason must be one of the controlled-vocabulary reason codes (free text is rejected so content can never be smuggled into a content-free receipt): art18_request, art18_lifted, art17_erasure_deferred_under_hold, accuracy_contested, unlawful_processing, needed_for_legal_claims, objection_pending, consent_withdrawn, consent_restored. Lifting always records art18_lifted regardless of the caller-supplied reason.

What happens:

  1. The memory is taken OUT OF RECALL at the index level.
  2. It is preserved intact and remains reachable for audit and legal access.
  3. It does NOT contribute to semantic search or context recall.
  4. The restriction is reversible — lifting it returns the memory to normal use.
  5. A signed restriction receipt (shomei.restriction.v1) is minted, attesting the action and timestamp.

The restriction-receipt body carries action (applied | released), reason_code, restricted_state, applied_at, and an optional key-derived subject_commitment. The top-level SDK response surfaces memory_id, restricted, receipt_hash, signed_receipt, and a note; the reason_code / restricted_state / applied_at fields live inside the receipt body, not at the top level.

Use cases:

  • Data subject exercises Art. 18 to halt processing of their personal data
  • Compliance hold pending litigation or investigation
  • Temporary exclusion from AI-driven features while reviewing content

Subject Access Export (Art. 15): Portable Evidence

Export produces an evidence bundle for a subject: live governed memories, content-free erasure evidence for prior forgets, the subject's inference-derived and derived-artifact records, and a signed subject-export receipt — all verifiable offline.

The flow:

from mochi_memory import RemoteMemory

m = RemoteMemory.from_env()

# Export a subject's data + erasure evidence + signed proof
result = m.export(user_id="dana_smith", include_content=False)
# {
#   "subject": "dana_smith",
#   "count": 12,                          # live governed memories
#   "content_included": false,
#   "memories": [ ... ],                  # each row: id, semantic_type, held, restricted,
#                                         #   recallable (= not restricted), consent_basis, verify
#   "erased_count": 41,
#   "erased": [ ... ],                    # content-free proof of prior erasures
#   "inference": { "sources": [...], "states": [...] },
#   "inference_count": 7,
#   "inference_restricted_retained": [ ... ],
#   "inference_restricted_retained_count": 0,
#   "derived": [ {"id": "artifact:9f3c...", "status": "active", "recallable": true}, ... ],
#   "derived_count": 3,
#   "live_count": 22,                     # = len(memories) + inference records + derived ids
#   "note": "evidence export: LIVE governed memories + ...",
#   # present when a commitment key is configured:
#   "receipt": { ... },
#   "receipt_hash": "sha256:...",
#   # present when a signer is also configured:
#   "signed_receipt": { ... }
# }

The subject key is subject (not user_id). The signed envelope is signed_receipt. RemoteMemory.export takes user_id, include_content, and include_restricted; setting include_content=True or include_restricted=True requires an admin-scoped key.

include_restricted=True is the Art.15 subject-access mode; over HTTP, subject_access=true is accepted as an alias. It only renders restricted-but-retained content when combined with include_content=True, and it is gated by the same admin authorization as content export. A data-scoped key receives 403. Restricted rows remain out of recall; crypto-erased rows stay unreturnable.

What's included:

  • All LIVE governed memories for the subject (metadata; content only when include_content=True). Each row carries recallable (true unless the memory is restricted).
  • Erasure evidence (erased): for every prior erasure, a content-free record proving it happened.
  • Inference-derived records (inference) and any restricted-but-retained inference sources (inference_restricted_retained, Art. 18 out-of-recall, not erased).
  • The subject's derived-artifact set (derived): the Art. 15 mirror of the Art. 17 deletion cascade. Each entry is a content-free opaque artifact:<hex> node id with status: "active" and an honest per-node recallable flag — recallable is true only when at least one supporting source is still served, so a restricted-source derived node correctly reports recallable: false.
  • A signed subject-export receipt, verifiable by the subject or a third party.

The receipt:

The receipt body schema is shomei.subject_export.v1. The body carries tenant_id_hash, a keyed subject_commitment, exported_at, live_count (which includes the derived-artifact count), erased_count, content_included, a nonce, and exported_set_witness — a recomputable witness commitment over the sorted union of live governed ids, live inference logical ids, restricted-retained inference ids, content-free erased-evidence ids, and the derived-artifact node ids. The witness is committed under set-schema shomei.subject_export_set.v2, so access-completeness over the derived-artifact graph is attested the same way deletion-completeness is.

Honest scope. The witness proves the delivered set is untampered as of exported_at. It does not prove the operator's enumeration was complete — completeness against a dishonest operator needs the measured-enclave roadmap (Tier 3).


Attested Recall: Context Trace Receipts

context() / POST /v1/context returns a signed trace_receipt when the tenant has a receipt signer. The body schema is shomei.context_trace.v1. It is content-free and carries:

  • subject_pseudonym — a keyed commitment, or null for an anonymous query; never the raw user_id
  • query_commitment — a keyed commitment; never the raw query
  • served_set_witness — a commitment over the served opaque id set
  • omissions_commitment — a commitment over the withheld/omitted set
  • nonce — fresh per context render
  • proof_scopeintegrity_of_served_and_withheld_set_as_of_query_not_retrieval_optimality

The keyed query_commitment above exists in the signed receipt returned to the caller; Shomei does not retain it in the durable context-trace table. The durable row stores only unretained_<trace_id> (or an erased_<rowid> tombstone after linked content is erased), and records only whether a scope was bound—not the caller-derived scope commitment.

Before signing, the trace accounting requires every candidate artifact to be represented either in the served set or in the omission set. That is the served-set completeness guarantee: the signature binds the served/withheld accounting for this query. It does not prove retrieval optimality, answer correctness, or that a dishonest runtime selected the best candidates.

Verify offline with the standalone shomei_memory_verify library:

from shomei_memory_verify import verify_context_trace_receipt

result = verify_context_trace_receipt(
    trace_receipt,
    served_ids=observed_served_ids,
    omissions=observed_omissions,  # optional; omit if you only retained the served ids
    expected_public_key_hex=pin,
)
assert result.authenticated and result.served_set_matches

Tampering with a served id causes served_set_witness_mismatch; tampering with supplied omissions causes omissions_commitment_mismatch.


Producer ON by default

The derived-artifact lineage producer is ON by default wherever a receipt signer is present. The hosted service and the SDK's Memory.from_profile / Memory.from_env constructors always wire a signer (derived from the tenant KEK), so the derived-artifact lineage — and its disclosure in export() — is active by default. There is nothing to opt into for the Art. 15 derived-artifact set.


Receipts: Content-Free, Signed, Verifiable Offline

Every receipt is a small JSON envelope, ed25519-signed, with exactly six fields:

{
  "schema": "shomei.vector_delete.v1",
  "receipt_hash": "sha256:<canonical-body-hash>",
  "signer_id": "shomei-receipt:<tenant>",
  "public_key_hex": "<ed25519 public key, hex>",
  "signature_hex": "<ed25519 signature, hex>",
  "body": {
    "schema": "shomei.vector_delete.v1",
    "tenant_id_hash": "<keyed hash>",
    "memory_id": "lm_abc123",
    "physical_purge_state": "...",
    "key_custody": "kms",
    "...": "content-free metadata only"
  }
}

The envelope field names are exactly schema, receipt_hash, signer_id, public_key_hex, signature_hex, bodynot signature / public_key / signed_at. There is no top-level signed_at; timestamps (applied_at, exported_at, etc.) live inside body. The signature_hex is an ed25519 signature over the RFC 8785 (JCS) canonical bytes of body, and receipt_hash is sha256: over those same canonical bytes.

Receipt body schemas you will encounter:

Operation Body schema
Record deletion (delete) shomei.vector_delete.v1
Subject erasure closure (forget) shomei.subject_closure.v1 / .v2
Restriction (restrict/lift) shomei.restriction.v1
Subject export (export) shomei.subject_export.v1
Context trace (context) shomei.context_trace.v1
Memory add shomei.memory_add.v1
Memory update shomei.memory_update.v1

key_custody in a delete receipt is stamped from the configured KEK provider tier: kms, file, in_memory_dev, escrowed, tpm_simulated, or unknown for an unrecognized custom provider. kms / tpm_simulated / escrowed are production tiers; file / in_memory_dev are dev floors and self-qualify the receipt as such.

Properties:

  • Content-free: the receipt body contains NO plaintext data, only opaque ids, keyed hashes, and operation metadata.
  • Signed: ed25519 over RFC 8785 (JCS) canonical bytes — a reproducible serialization.
  • Offline-verifiable: public-key crypto alone; no network, no private keys.
  • Auditor-friendly: small, human-readable metadata.

Verifying a Receipt

In the SDK (Python)

After a deletion:

from mochi_memory import RemoteMemory

m = RemoteMemory.from_env()
deleted = m.delete(memory_id="lm_abc123")
receipt = deleted["signed_receipt"]

# Fetch or pin the signer's public key (production: pin out-of-band)
pin = m.signer_key()["public_key_hex"]

# Verify locally (no server round-trip)
result = m.verify_receipt(receipt, expected_public_key_hex=pin)
assert result.authenticated, f"Not authenticated: {result.reason}"
print(f"Verified {result.body['memory_id']} (schema {result.schema})")

RemoteMemory.verify_receipt is a static method that delegates to the standalone offline verifier; it raises RemoteMemoryError if the verifier package is not installed.

Offline CLI Verifier

Install the verifier (standalone, the one artifact Shomei publishes — cryptography is its only third-party dependency, Python 3.9+):

pip install shomei-memory-verify

Verify a receipt file:

# Verify against a pinned key; print a human-readable report
shomei-memory-verify receipt.json --pin <signer_public_key_hex> --render

# Quick verdict (exit code 0 = authenticated)
shomei-memory-verify receipt.json --pin <key>

# Verify without pinning (self-consistency only; exits non-zero)
shomei-memory-verify receipt.json --render

# Read the receipt from stdin
shomei-memory-verify - --pin <key> --json

# A mismatched pin fails authentication
shomei-memory-verify receipt.json --pin <wrong_key> --render

You can also invoke the module form, python -m shomei_memory_verify receipt.json --pin <hex>, which is identical. --pin has the alias --expected-public-key, and - as the receipt path reads stdin.

Output:

  • --render: a one-page, plain-language report for a GC, DPO, or regulator. Authenticated language is emitted only for an authenticated result; an unpinned-but-valid receipt is downgraded to "ASSERTED, pending verification," and an invalid receipt gets no authenticated report.
  • --json: structured result with exactly valid, authenticated, reason, schema, public_key_hex.
  • Exit code: 0 only when authenticated=true (pinned key + valid signature). Unpinned-but-valid, mismatched, invalid, and malformed all exit 1.

Offline Library Verification

Python:

import json
from shomei_memory_verify import verify_signed_receipt, render

# verify_signed_receipt accepts a SignedReceipt OR a parsed mapping (dict), not a raw JSON string
receipt = json.load(open("receipt.json"))

# Pin the signer key (obtained out-of-band) to AUTHENTICATE
pin = "ed25519_public_key_hex"
result = verify_signed_receipt(receipt, expected_public_key_hex=pin)
assert result.authenticated, f"Not authenticated: {result.reason}"

# Human-readable statement
print(render(result))

verify_signed_receipt(receipt, *, expected_public_key_hex=None) returns a VerifyResult. bool(result) is authentication-gated — it is truthy only when the result is authenticated against a pinned key, so a plain if result: is a safe trust gate.

Result fields (VerifyResult, a frozen dataclass):

@dataclass(frozen=True)
class VerifyResult:
    valid: bool                 # envelope well-formed AND ed25519 sig consistent over body AND hash matches
    authenticated: bool         # valid AND verified against a caller-PINNED key
    reason: str                 # see reason codes below
    public_key_hex: str = ""    # the authenticated signer key (trust this only when authenticated)
    schema: str = ""            # the SIGNED body schema (the envelope schema is not surfaced)
    signer_id: str = ""         # ADVISORY ONLY — unsigned envelope metadata, attacker-relabelable
    body: dict = {}             # the content-free receipt claims (ids, counts, timestamps)

reason is one of exactly five codes:

  • 'ok' — pinned key matches, signature and hash valid (valid=true, authenticated=true)
  • 'ok_unpinned' — valid but no key was pinned, self-consistent only (valid=true, authenticated=false)
  • 'public_key_mismatch' — the pinned key does not match the embedded key
  • 'signature_or_hash_invalid' — signature invalid or receipt_hash mismatch
  • 'malformed: <detail>' — the envelope is not parseable (missing required key, body not a mapping, etc.)

valid and authenticated are distinct. valid means the receipt is internally consistent (well-formed envelope + signature consistent over the body + hash matches). authenticated means valid AND the signature was checked against a key you pinned out-of-band. The trust gate is authenticated. signer_id is unsigned advisory metadata — never trust it; trust public_key_hex (and only when authenticated).


The Honest Framing

What a receipt proves:

  • The operator signed the receipt with a specific ed25519 private key
  • The receipt's content-free claims (timestamp, opaque ids, counts) are bound to that signature
  • The key-holder can verify this offline, after pinning the signer key out-of-band

What a receipt does NOT prove:

  • That the operator did not copy the data elsewhere before erasing
  • That correlated data from other sources is also erased
  • That the data was not logged or backed up outside the governed memory store
  • That the subject identity is anonymized (it is pseudonymized with the tenant key)
  • That the operator's enumeration of the subject's set was complete (the witness binds the delivered set as of the signed time; completeness against a dishonest operator is the Tier-3 / measured-enclave roadmap)
  • The current state (only the at-rest state at the signed timestamp; an auditor recomputes the commitment to verify current state)

Verification levels:

  1. Self-consistent only (valid=true, authenticated=false, reason ok_unpinned): the receipt is structurally sound and internally consistent, but you did NOT pin the signer's key, so anyone could have signed it.
  2. Authenticated (authenticated=true, reason ok): the signature matches the public key you pinned out-of-band. The operator (or someone with their key) signed it.

The verifier downgrades unpinned or invalid receipts to explicit unauthenticated language to prevent over-reliance.


Key Custody & Trust

Key custody is the proof boundary. A receipt is only as trustworthy as the key you pin.

For production:

  1. Pin the signer's public key out-of-band before verification. Store it in your security infrastructure (GC, CISO, DPO systems), not in the governed memory service you are auditing.
  2. Run the verifier locally on auditor machines, DPO systems, or in an air-gapped environment.
  3. Rotate keys via your KMS (AWS KMS, BYOK). The service derives its per-tenant signer key from the tenant KEK; under the default v2 custody there is no shared signing secret.

For demos/testing only:

The SDK can fetch the signer key from the service:

key = m.signer_key()["public_key_hex"]

But in production, fetch this once, verify it out-of-band with your CISO, and pin it locally. Fetching the key from the service you are auditing is a dev-only convenience — it is not a trust anchor.


Receipts in the Service API

A signed receipt is returned by:

  • DELETE /v1/memories/{id}signed_receipt (record crypto-erasure; admin)
  • POST /v1/forgetsigned_closure_receipt + per-memory signed_receipts (subject erasure; admin)
  • GET /v1/export?user_id=...signed_receipt (subject access)
  • POST /v1/contexttrace_receipt (recall served/withheld-set integrity; present when a signer is configured)
  • POST /v1/memories/{id}/restrictsigned_receipt (Art. 18 restriction; admin)

Signer public key:

  • GET /v1/account/signer-key{"tenant": "...", "signer_public_key_hex": "..."}

Operator-reported verdict (NOT cryptographic proof):

  • GET /v1/memories/{id}/verify{"valid", "retrieval_safe", "embedding_retired", "physically_purged", "failures"}, or {"valid": null, "reason": "not_found"}. This is self-reported governance status, not a signed proof.

Example: End-to-End Governance Workflow

Scenario: Dana requests her data subject rights (Art. 15 + 17).

from mochi_memory import RemoteMemory
import json

m = RemoteMemory.from_env()  # admin-scoped key

# 1. Export her data (Art. 15)
export_result = m.export(user_id="dana_smith", include_content=True)
export_receipt = export_result["signed_receipt"]
print(f"Export: {export_result['count']} live governed, "
      f"{export_result['derived_count']} derived artifacts, "
      f"{export_result['erased_count']} prior erasures")

# 2. Verify the export receipt
pin = m.signer_key()["public_key_hex"]  # in production, pin out-of-band instead
verify_result = m.verify_receipt(export_receipt, expected_public_key_hex=pin)
assert verify_result.authenticated, "Export receipt not authentic"
print(f"Export verified (schema {verify_result.schema})")

# 3. Ship her data + receipt
with open("dana_smith_export.json", "w") as f:
    json.dump(export_result, f, indent=2)

# 4. Erase her remaining memories (Art. 17)
forget_result = m.forget(user_id="dana_smith")
forget_receipt = forget_result["signed_closure_receipt"]

# 5. Verify the forget closure receipt
verify_result = m.verify_receipt(forget_receipt, expected_public_key_hex=pin)
assert verify_result.authenticated, "Forget receipt not authentic"
print(f"Forget verified: {forget_result['erased_count']} erased, "
      f"{forget_result['deferred_count']} deferred")

# 6. Auditor verifies offline (no service access needed)
# $ shomei-memory-verify dana_smith_export.json --pin <pinned_key> --render