Deployment & Key Custody
Shomei Governed Memory is designed for customer-VPC deployment with complete data residency and key control. Your data and keys never leave your environment.
Storage Model
Data is stored in per-tenant SQLite databases on a volume you control, with per-row encryption and managed key custody via AWS KMS (BYOK).
At-Rest Encryption: Per-Row DEK Envelope
Each memory's payload and embedding cells are encrypted under a per-row Data Encryption Key (DEK), wrapped by the workspace Key Encryption Key (KEK):
Memory record
├─ Payload (ciphertext under DEK_row)
├─ Embedding (ciphertext under DEK_row)
└─ Wrapped DEK_row (encrypted under workspace KEK)
- DEK envelope: the wrapped DEK is stored alongside the row; the plaintext DEK never lives at rest. The wrapping key is a domain-separated subkey of the workspace KEK (HKDF-SHA256), so the raw KEK is never used directly as an encryption key.
- Workspace KEK: 32 bytes, KMS-wrapped under your Customer-Managed Key (CMK). The plaintext KEK never touches disk.
- Governance metadata (memory ids, timestamps,
tenant_id_hash, wrapped-DEK blobs) is plaintext at rest — full database page encryption via SQLCipher is not shipped. - Crypto-erasure:
DELETE /v1/memories/{id}andPOST /v1/forgetdestroy per-row DEKs irreversibly; even with raw volume access, the affected plaintext is unrecoverable.
Honest Boundary
This is per-row DEK envelope encryption, NOT SQLCipher. Say so in docs; never market this path as "SQLCipher." The proof boundary is provable governance (deletion + signed receipts), not encryption coverage. Receipts are content-free: they attest authorship of a governed operation, not that copies or backups held elsewhere were also erased.
Key Custody Options
AWS KMS (Production BYOK)
The workspace KEK is stored as a KMS-wrapped ciphertext blob on disk and unwrapped on-demand:
Your AWS Account
├─ Customer-Managed CMK (your root key)
│ └─ Wraps the workspace KEK (32-byte plaintext never touches disk)
└─ EC2/ECS instance with minimal IAM policy
└─ kms:Encrypt, kms:Decrypt, kms:DescribeKey only
Kill-switch model:
- Per-memory erasure:
DELETE /v1/memories/{id}andPOST /v1/forgetdestroy the per-row DEK (immediate, no CMK action needed). - Workspace erasure:
POST /v1/admin/erase-tenantis the whole-tenant kill switch. In the default shared-CMK / BYOK posture (SHOMEI_KMS_PER_TENANT=shared), it destroys the local KMS-wrapped KEK blob and zeroizes the in-process key cache — it does not schedule your CMK for deletion (deleting a shared CMK would brick every other tenant). The CMK is only scheduled for deletion (AWSScheduleKeyDeletion, with its mandated 7–30 day PendingDeletion window) when the service owns a per-tenant CMK it minted itself (SHOMEI_KMS_PER_TENANT=auto). - The instance role does NOT need
kms:ScheduleKeyDeletionfor BYOK. Tenant erasure crypto-disables the workspace via the local wrapped blob; deciding to destroy your CMK is a separate, deliberately-authorized admin action that the data-plane process should never be able to trigger.
Honest custody boundary.
destroy_root/destroy_localis an at-rest control: it governs cold unwrap of the KEK, not a key already unsealed and held live in a process. Per-row DEK crypto-erasure is the granular control; the workspace kill switch is the whole-workspace root nuke. Under a CMK that is scheduled for deletion, the key is recoverable via KMSCancelKeyDeletionuntil the AWS window elapses; only after that is it irreversible.
File KEK Provider (Dev Only)
For local development without AWS:
SHOMEI_KEK_PROVIDER=file # generates/persists a 32-byte key under SHOMEI_DATA_DIR
SHOMEI_ALLOW_DEV_CUSTODY=1 # required to start
- The key file must hold exactly 32 bytes with no group/other permission bits; reads use
O_NOFOLLOWand validate the file on the same descriptor to close symlink-redirect / TOCTOU windows. destroy_rootoverwrites-then-unlinks the file (best-effort; not a hardware-erasure primitive).- Restart survival: the key persists on disk and reattaches on process restart.
In-Memory KEK Provider (Tests Only)
SHOMEI_KEK_PROVIDER=inmemory
- Random 32-byte key generated per process, held in a zeroizable bytearray.
- Ephemeral: receipt signers change on restart, pinned keys become stale.
destroy_rootzeroizes the key in memory.- Dev/test only — do not use in production.
Both
fileandinmemoryare dev-custody providers. The service refuses to start on either (or on a weak/short/defaultSHOMEI_COMMIT_SECRET) unlessSHOMEI_ALLOW_DEV_CUSTODY=1is explicitly set. Dev custody is dev-only; it is not a production posture.
Deployment Topology
┌─ Your VPC / Account ──────────────────────────────────────┐
│ │
│ Shomei Memory Service │
│ (Docker / systemd) │
│ ├─ Per-tenant SQLite DBs (per-row DEK-sealed) │
│ │ └─ SHOMEI_DATA_DIR (encrypted volume) │
│ │ │
│ └──→ AWS KMS (Decrypt/Encrypt) │
│ └─ Your Customer-Managed CMK │
│ └─ Workspace KEK wrapped │
│ │
│ Egress: signed receipts only (no PII, no keys) │
│ ┌─→ S3 / audit / transparency log (optional) │
│ └─→ Your downstream / DPO │
│ │
└──────────────────────────────────────────────────────────┘
Environment Variables
The canonical prefix is SHOMEI_. Legacy MOCHI_* names are still honored as a fallback for backward compatibility (SHOMEI_* wins if both are set), but new deployments should use SHOMEI_*. Any secret variable also accepts a <NAME>_SSM form, which resolves an AWS SSM SecureString parameter (KMS-decrypted at boot) — prefer the *_SSM form for all secrets.
Core (Production)
| Variable | Required | Default | Notes |
|---|---|---|---|
SHOMEI_API_KEYS (or _SSM) |
✅ | — | Format: key1:tenantA,key2:tenantB. Bearer token → tenant (constant-time compared). Service exits if empty. Prefer the _SSM form. |
SHOMEI_KEK_PROVIDER |
✅ (prod) | file |
aws-kms for production BYOK. file/inmemory are dev-only and require SHOMEI_ALLOW_DEV_CUSTODY=1. |
SHOMEI_ALLOW_DEV_CUSTODY |
— | unset | Set to 1 to permit a dev-custody provider or a weak commit secret. Never set in production. |
SHOMEI_COMMIT_SECRET (or _SSM) |
✅ (prod) | dev-commit-secret |
Per-deployment commitment secret (≥16 chars). Keys the evidence commitments. The service refuses to start on the dev default unless SHOMEI_ALLOW_DEV_CUSTODY=1. Prefer the _SSM form. |
SHOMEI_KMS_KEY_ID |
shared aws-kms | — | Your CMK id or ARN. Required only for legacy aws-kms with SHOMEI_KMS_PER_TENANT=shared. |
SHOMEI_AWS_REGION |
aws-kms / SSM | us-east-1 |
Region where your CMK lives. |
SHOMEI_KMS_PER_TENANT |
— | auto for aws-kms |
auto (service mints a per-tenant CMK) or legacy shared (one CMK across tenants). Production refuses shared unless SHOMEI_ALLOW_SHARED_KMS_CMK=1 is set for a documented single-tenant/legacy deployment. Governs whether erase-tenant schedules CMK deletion (see kill-switch model). |
SHOMEI_CUSTODY_VERSION |
— | v2-kek-hkdf |
Keep the default. Production refuses legacy v1-commit-secret unless SHOMEI_ALLOW_LEGACY_CUSTODY_V1=1 is set for an approved rollback. |
SHOMEI_DATA_DIR |
— | ./mochi-data (cwd-relative) |
Per-tenant DBs + KEK material. Treat backups as secret material. (Docker/systemd mount this at /var/lib/shomei-memory — that is a mount path, not the app default.) |
SHOMEI_BIND_HOST |
— | 127.0.0.1 |
Bind address. Bind to loopback and front with an ingress, or bind wider only behind a security group. |
SHOMEI_PORT |
— | 8088 |
Service port. Never expose directly to the internet; terminate TLS at your ingress or set SHOMEI_TLS_*. |
SHOMEI_TLS_CERT / SHOMEI_TLS_KEY |
— | — | Paths to PEM cert/key. Setting both enables HTTPS on the service (on whatever SHOMEI_PORT is); otherwise terminate TLS at your load balancer. |
Embedder
| Variable | Default | Notes |
|---|---|---|
SHOMEI_EMBEDDER |
hash |
hash (deterministic, non-semantic, no dependency — local/offline smoke only) | ollama (self-hosted Ollama; set SHOMEI_OLLAMA_HOST) | bedrock (AWS Titan v2). Production should use bedrock or ollama. |
SHOMEI_EMBED_DIMS |
256 |
Embedding dimension. Bedrock Titan v2 is MRL-native at 256 / 512 / 1024. Must match the backend model's native dimension. |
SHOMEI_EMBED_MODEL |
— | Backend model id (e.g. amazon.titan-embed-text-v2:0 for bedrock). |
SHOMEI_OLLAMA_HOST |
— | Required when SHOMEI_EMBEDDER=ollama. |
SHOMEI_EMBEDDER_STRICT |
1 (on) |
With a real backend (bedrock/ollama), fails loud if the backend is unreachable instead of silently falling back to the non-semantic hash floor. Leave on in production. |
SHOMEI_EMBED_CONCURRENCY |
4 |
Bounded worker count for the per-add write-path embed pool (a governed add(infer=True) mints several retrieval keys; their embedding round-trips run concurrently). Only the embed calls run on the pool: all DB writes stay on the caller thread, results apply in the serial order (stored values byte-identical to serial), and a strict-mode embed failure still fails the whole add closed. 1 = serial (pre-pool) behavior; max 16. Ignored by the in-process hash embedder. |
SHOMEI_BULK_IMPORT_ENABLED |
0 (dark) |
Enables POST /v1/memories/bulk. Ships dark until the staged flip criterion is met: full race battery green in CI (red controls included), one 10k-item staging rehearsal with zero vector-liveness findings and p95 same-tenant read latency under 2x baseline during accept, and an erase issued mid-rehearsal verifiably beating every pending item. |
SHOMEI_BULK_CHUNK_SIZE |
50 |
Items per bulk-import transaction chunk. The facade lock and the write transaction are held per chunk (yielding between chunks), bounding same-tenant read-latency impact during an import; the rehearsal tunes this against p95 read latency. |
SHOMEI_BULK_EMBED_MODE |
ondemand |
How BULK-imported items' vectors are computed: ondemand = the shared async worker calls the embedder per key (bulk drains at low priority, weighted 4:1 interactive-first); batch = bulk jobs park for Bedrock batch inference with the SAME Titan model (byte-identical vectors, 100k records/job, ~50% on-demand price) so imports never consume the on-demand embedding quota. Batch mode is dark until the staging rehearsal; requires SHOMEI_BULK_EMBED_S3_BUCKET. |
SHOMEI_BULK_EMBED_S3_BUCKET |
(unset) | S3 bucket for batch-inference JSONL staging (SSE-encrypted). Inputs/outputs are deleted after result ingestion; an erase during a pending batch voids the batch record and deletes its S3 objects fail-closed (the erase raises rather than false-claiming if S3 deletion fails). |
SHOMEI_BULK_EMBED_ROLE_ARN |
(unset) | IAM role Bedrock assumes for batch-inference jobs (roleArn on create_model_invocation_job); needs read on the input JSONL and write on the output prefix. Required for SHOMEI_BULK_EMBED_MODE=batch. |
SHOMEI_BULK_EMBED_REGION |
AWS_REGION / us-east-1 |
Region for the batch-inference and S3 staging clients. |
SHOMEI_BULK_EMBED_POLL_SECONDS |
60 |
Batch drain daemon cycle: sweep erase-voided batches, poll submitted provider jobs (stage results, re-queue parked jobs, delete S3 JSONL), submit the next batch. Correctness never depends on the daemon — a dead daemon leaves jobs parked/queued and the erase closure runs on the erase verbs themselves. |
Graph-edge proposer
| Variable | Default | Notes |
|---|---|---|
SHOMEI_EDGE_PROPOSER |
off |
off leaves edge proposal and storage unchanged/disabled; stub enables the deterministic author-to-token mentions proposer for demo/tests; bedrock enables model-backed relationship proposals at write time. Every mode still passes through byte-grounded admission. The stub proves plumbing and erasure, not semantic relationship extraction. |
SHOMEI_EDGE_PROPOSER_MODEL_ID |
Claude Haiku Bedrock id | Bedrock-only model override. Never exposed by the metadata-only entity projection, which emits only `edge_source: stub |
For a zero-model-cost entity-graph seed, also keep
SHOMEI_EXTRACTOR_PROPOSER=deterministic and SHOMEI_EMBEDDER=hash; see
Entity Memory Graph demo seeding.
No-Resurrection Ledger
| Variable | Default | Notes |
|---|---|---|
SHOMEI_FORBIDDEN_LEDGER |
sqlite |
sqlite = co-located <db>.forbidden.db (vulnerable to a whole-volume restore) | s3 = restore-immune via S3 Object Lock, off the data volume. |
SHOMEI_FORBIDDEN_S3_BUCKET |
— | Required when SHOMEI_FORBIDDEN_LEDGER=s3. |
Custody Version
| Variable | Default | Notes |
|---|---|---|
SHOMEI_CUSTODY_VERSION |
v2-kek-hkdf |
v2 (default): each tenant's receipt signer is HKDF-derived from that tenant's own KEK — no shared secret. v1-commit-secret is the legacy shared-signer mode. |
SHOMEI_RECEIPT_SIGNING_KEY (or _SSM) |
— | Pins one explicit ed25519 signing identity across all tenants. Forbidden under v2 custody — leave unset to use per-tenant derived signers. |
AWS KMS Setup (BYOK)
1. Create a Customer-Managed CMK
In your AWS account (the region where the service runs):
aws kms create-key \
--description "Shomei Memory workspace KEK root" \
--region us-east-1
# Create an alias for convenience:
aws kms create-alias \
--alias-name alias/shomei-workspace \
--target-key-id <cmk-id> \
--region us-east-1
2. Grant the Service Instance Role Minimum Permissions
The EC2/ECS instance role needs only wrap and unwrap:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:111122223333:key/12345678-1234-1234-1234-123456789012"
}
]
}
If using SHOMEI_EMBEDDER=bedrock for semantic embeddings, also grant:
{
"Effect": "Allow",
"Action": ["bedrock:InvokeModel"],
"Resource": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0"
}
3. Do NOT Grant kms:ScheduleKeyDeletion to the Service Role
Under the default shared-CMK / BYOK posture, the service never schedules your CMK for deletion — tenant erasure works by destroying the local KMS-wrapped KEK blob, so kms:Encrypt / kms:Decrypt / kms:DescribeKey are sufficient. Destroying the CMK itself is the ultimate kill switch and must be a deliberate, separately-authorized admin action; never let the data-plane process destroy your root key. Per-memory erasure (crypto-erase of per-row DEKs) is decoupled and requires no CMK action at all.
4. Use IMDSv2 Only
On the EC2 instance, enforce IMDSv2 with a hop limit of 1 (disable IMDSv1):
aws ec2 modify-instance-metadata-options \
--instance-id <id> \
--http-tokens required \
--http-put-response-hop-limit 1
5. Store Secrets in SSM SecureString
Instead of plaintext env files:
aws ssm put-parameter \
--name /shomei/prod/api-keys \
--type SecureString \
--value "key1:tenantA,key2:tenantB" \
--key-id arn:aws:kms:us-east-1:111122223333:key/... \
--region us-east-1
aws ssm put-parameter \
--name /shomei/prod/commit-secret \
--type SecureString \
--value "$(openssl rand -hex 32)" \
--key-id arn:aws:kms:... \
--region us-east-1
Then point the service to these parameters:
SHOMEI_API_KEYS_SSM=/shomei/prod/api-keys
SHOMEI_COMMIT_SECRET_SSM=/shomei/prod/commit-secret
The instance role needs ssm:GetParameter scoped to /shomei/prod/*:
{
"Effect": "Allow",
"Action": ["ssm:GetParameter"],
"Resource": "arn:aws:ssm:us-east-1:111122223333:parameter/shomei/prod/*"
}
A complete Terraform module (CMK, IAM, SSM, compute, user-data) ships at
deploy/shomei-memory/terraform/.
Docker Compose Quickstart
# 1. Copy the example config
cp deploy/shomei-memory/.env.example deploy/shomei-memory/.env
# 2. Edit .env: set your BYOK CMK, API keys, and commit secret
# (or use SSM secret parameters: SHOMEI_API_KEYS_SSM, SHOMEI_COMMIT_SECRET_SSM)
nano deploy/shomei-memory/.env
# 3. Build and start
docker compose -f deploy/shomei-memory/docker-compose.yml up -d --build
# 4. Check health
curl -fsS http://127.0.0.1:8088/v1/healthz
The compose file:
- Builds the service from the repo root (
context: ../..) - Mounts a persistent volume at
/var/lib/shomei-memoryfor tenant DBs and KEK material - Publishes port 8088 on loopback only (
127.0.0.1:8088:8088) — never to the internet - Runs with
no-new-privileges:true - Checks
/v1/healthzevery 30 seconds
Systemd Deployment (VM)
For a non-Docker VM (e.g., EC2):
# 1. Install Python 3.10+ and dependencies (the project floor is >=3.10)
sudo apt-get install python3.10 python3.10-venv
# 2. Create venv and install (the [aws] extra pulls in boto3 for KMS/SSM/S3 paths)
# NB: use .venv (with the leading dot) — the shipped systemd unit's ExecStart
# points at /opt/shomei-memory/.venv/bin/python.
python3.10 -m venv /opt/shomei-memory/.venv
/opt/shomei-memory/.venv/bin/pip install -e '.[aws]'
# 3. Create config directory and env file
sudo mkdir -p /etc/shomei-memory
sudo touch /etc/shomei-memory/service.env
sudo chmod 600 /etc/shomei-memory/service.env
# 4. Fill in env vars (use SSM parameters for secrets)
sudo tee /etc/shomei-memory/service.env >/dev/null <<EOF
SHOMEI_API_KEYS_SSM=/shomei/prod/api-keys
SHOMEI_KEK_PROVIDER=aws-kms
SHOMEI_KMS_KEY_ID=arn:aws:kms:us-east-1:111122223333:key/...
SHOMEI_AWS_REGION=us-east-1
SHOMEI_COMMIT_SECRET_SSM=/shomei/prod/commit-secret
SHOMEI_EMBEDDER=bedrock
SHOMEI_BIND_HOST=127.0.0.1
SHOMEI_PORT=8088
SHOMEI_TLS_CERT=/etc/shomei-memory/tls/cert.pem
SHOMEI_TLS_KEY=/etc/shomei-memory/tls/key.pem
EOF
# 5. Install systemd unit
sudo cp deploy/shomei-memory/systemd/shomei-memory.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now shomei-memory
# 6. Check status
sudo systemctl status shomei-memory
curl -fsS https://localhost:8088/v1/healthz --insecure # or via your load balancer
Setting both
SHOMEI_TLS_CERTandSHOMEI_TLS_KEYmakes the service speak HTTPS onSHOMEI_PORT. There is no separate HTTPS port; TLS rides on whateverSHOMEI_PORTis set to.
HTTP API Endpoints
All routes require a bearer token (Authorization: Bearer <api-key>) mapping to a tenant, except /v1/healthz and the self-serve signup routes. Destructive and control-plane routes additionally require an admin-scoped key (a data-scoped key gets 403). This table covers the deployment-relevant surface; see the HTTP API reference for the complete route list and response shapes.
| Method | Path | Purpose | Auth |
|---|---|---|---|
| GET | /v1/healthz |
Liveness probe — returns {"status": "ok"} |
No |
| POST | /v1/memories |
Governed add ({messages, semantic_type?, user_id?, …}) |
✅ |
| POST | /v1/search |
Recall / semantic search ({query, limit?, user_id?}) |
✅ |
| GET | /v1/memories |
List tenant memories (?limit=, ?user_id=) |
✅ |
| GET | /v1/memories/{id} |
Get one memory (404 once erased/restricted) | ✅ |
| GET | /v1/memories/{id}/verify |
Operator-reported governance status (valid, retrieval_safe, embedding_retired, physically_purged, failures) |
✅ |
| GET | /v1/memories/{id}/history |
Lifecycle history (governance receipts) | ✅ |
| PUT | /v1/memories/{id} |
Governed update (revoke-and-recreate) | ✅ admin |
| DELETE | /v1/memories/{id} |
Governed delete (crypto-erase). 200 erased / 423 held / 404 not-found | ✅ admin |
| POST | /v1/forget |
Art.17 subject erasure (held memories are deferred via Art.18 restriction + auto-erase on hold release) | ✅ admin |
| GET | /v1/export?user_id= |
Art.15 subject access (live memories, erasure evidence, derived-artifact set); content-free by default. ?include_content=true requires admin. |
✅ (admin only for include_content=true) |
| POST | /v1/admin/erase-tenant |
Workspace kill switch — returns {erased, restore_immune, signed_receipt} |
✅ admin |
| GET | /v1/account/signer-key |
This tenant's receipt signer public key (to pin for offline verify) | ✅ |
| GET | /v1/admin/health |
Operational status — returns {status, tenants_loaded, warm_load_failure_count, decay_scheduler} |
✅ admin |
Admin-scoped routes require an API key issued with admin scope. Keys embedded in client apps should be data-scoped only (they receive 403 on destructive/control-plane actions).
Verifying Deployment
1. Liveness
curl -fsS http://127.0.0.1:8088/v1/healthz
# -> {"status": "ok"}
2. Governance Smoke Test
Run the customer acceptance test (black-box: governed add/recall, crypto-erasure, legal hold, Art.18 restriction, subject export/forget, tenant isolation, offline receipt verification):
python acceptance/customer_acceptance.py
3. Verify a Deletion / Forget Receipt Offline
When you call DELETE /v1/memories/{id} the response includes a signed_receipt; POST /v1/forget returns per-memory proofs under signed_receipts (a list of {id, signed_receipt}) plus a subject-level signed_closure_receipt when a commitment key is configured. Every signed-receipt envelope has exactly these fields:
{
"schema": "shomei.vector_delete.v1",
"receipt_hash": "sha256:<hex over canonical body>",
"signer_id": "shomei-receipt:<keyed tenant id>",
"public_key_hex": "<ed25519 pubkey hex>",
"signature_hex": "<ed25519 signature hex>",
"body": { ... content-free receipt body ... }
}
Verify offline with the open-source verifier. It takes a parsed receipt dict (not a JSON string), and the authentication gate is authenticated (signature consistent and matched against the key you pinned), not valid alone:
from shomei_memory_verify import verify_signed_receipt
import json
receipt = json.load(open("signed_receipt.json")) # the signed_receipt object, parsed
result = verify_signed_receipt(
receipt,
expected_public_key_hex="<your-pinned-tenant-key>",
)
print(result) # VerifyResult(valid=True, authenticated=True, reason='ok', ...)
Or from the command line:
python -m shomei_memory_verify signed_receipt.json --pin <your-pinned-tenant-key>
# exit 0 only when authenticated against the pinned key
Pin the tenant's public key out-of-band: retrieve it from the startup log line or call GET /v1/account/signer-key once (returns {"tenant": ..., "signer_public_key_hex": ...}) and store it securely.
Hardening Checklist
- [ ] Custody:
SHOMEI_KEK_PROVIDER=aws-kmswith a strongSHOMEI_COMMIT_SECRET(≥16 chars or use_SSM). The service refuses to start on a dev-custody provider or the dev commit-secret default unlessSHOMEI_ALLOW_DEV_CUSTODY=1is set — never set that flag in production. - [ ] IAM: Instance role has
kms:Encrypt,kms:Decrypt,kms:DescribeKeyonly. Nokms:ScheduleKeyDeletion. Also grantssm:GetParameter(scoped) if using*_SSMsecret parameters, andbedrock:InvokeModel(scoped) if using the bedrock embedder. - [ ] Network: IMDSv2 enforced (hop limit 1), no public SSH, security group allows only your ingress. The service port is never internet-facing.
- [ ] TLS: Terminated here via
SHOMEI_TLS_*(both cert and key set) or at an ingress/ALB; enforce HTTPS client-side. - [ ] Storage:
SHOMEI_DATA_DIRon an encrypted volume (EBS with encryption, etc.). Treat backups as secret material. - [ ] Ledger separation: The no-resurrection forbidden ledger (
<db>.forbidden.db) must not be included in per-tenant data backups — a whole-volume restore would roll it back. Back it up separately or use S3 Object Lock (SHOMEI_FORBIDDEN_LEDGER=s3) for restore immunity. - [ ] Secrets: All secrets via SSM SecureString (
*_SSMvars), never plain env files. PreventSHOMEI_API_KEYSandSHOMEI_COMMIT_SECRETfrom appearing in/proc/<pid>/environ. - [ ] Monitoring: Alert on any non-200 from
/v1/healthz. - [ ] Strict embedder: If using
bedrockorollama, keepSHOMEI_EMBEDDER_STRICT=1so a backend outage fails loud (503) instead of silently degrading to non-semantic hash recall. - [ ] (Optional) Read-only root filesystem: Run the container with a read-only root + tmpfs
/tmpfor additional defense.
What Leaves Your Environment
Only receipts — content-free, signed attestations of add/delete/forget/restrict/export operations. No memory content, no embeddings, no raw subject identity, no keys. Receipts prove authorship of the governed operation; they do not prove that copies or backups held outside this deployment were erased.
Egress destinations (optional, customer-controlled):
- Audit / transparency log (if enabled)
- DPO / data subject (subject access export)
- Downstream systems (via webhook, if configured)
Verify receipts offline with shomei_memory_verify and a pinned signer key.
Verifiability vs. attestation tier (honest scope). Today's signed receipts are operator-verifiable (Tier-2): you cryptographically verify that this operator authored the deletion/restriction/export, and the offline verifier checks the signature against the pinned signer key. They do not yet prove, against a dishonest operator, that the operator's enumeration of a subject's records was complete — that completeness-under-adversary guarantee requires the measured-enclave (TEE) attestation track, which is roadmap, not shipped. Key custody is the proof boundary: the per-tenant signer is derived from your own KEK, so no shared secret can forge another tenant's receipts.
Scaling Notes
- Single-writer SQLite per tenant → single-node, vertical scale. One service instance owns a tenant's DB file.
- Horizontal scale = container-per-tenant or sharded deployments (e.g., tenant
foo→ service instance A, tenantbar→ service instance B). - Do not imply elastic horizontal scale (no shared-DB cluster without explicit refactoring).
Receipt Signing & Custody Versions
By default (v2 custody, SHOMEI_CUSTODY_VERSION=v2-kek-hkdf), each tenant's receipt signer is derived from that tenant's own KEK with domain separation (HKDF-SHA256). One tenant's KEK roots only that tenant's receipts — no single shared secret forges every tenant's proofs.
Pin the receipt signer's public key (hex):
- Logged at startup, one line per tenant:
[shomei] receipt signer (<src>) tenant=<t> public_key=<hex> - Also available from
GET /v1/account/signer-key(auth required) - Pass it to
python -m shomei_memory_verify <receipt> --pin <key-hex>to verify receipts offline
Legacy v1 custody (shared-signer derivation across all tenants) is available via SHOMEI_CUSTODY_VERSION=v1-commit-secret but deprecated; v2 is the default. Under v2, pinning an explicit SHOMEI_RECEIPT_SIGNING_KEY is forbidden — the per-tenant derived signer is used instead.