Privacy / GDPR / CCPA — Operator Guide¶
This document is for operators, not for end users. The customer-facing artifact lives in
privacy-policy-template.md; publish a filled-in copy of that one.This is not legal advice. The procedures below describe the mechanical surface MAID exposes. Your legal counsel must determine whether the surface is sufficient for your jurisdiction, business model, and contracts.
What PII MAID collects¶
The engine, with the stock maid-stdlib + maid-classic-rpg content
packs, collects the following personally-identifiable data:
| Category | Data | Where stored | Purpose | Legal basis (GDPR Art 6) |
|---|---|---|---|---|
| Account | Username, email (optional), password hash (PBKDF2-SHA256, 600k rounds, per-account salt) | accounts table (Postgres) |
Authentication, password reset, account-tied notifications. | Contract (1)(b). |
| Session | Source IP, user-agent (web only), session token (signed cookie or opaque telnet token), connect/disconnect timestamps | sessions table; audit log |
Session reattachment, abuse mitigation, audit. | Contract (1)(b) + legitimate interest (1)(f) for audit. |
| Audit | Account ID, operator identity, action verb, target ID, timestamp, IP | data/logs/audit/audit-YYYY-MM-DD.jsonl (engine cwd) + 1000-entry in-memory ring buffer for get_recent() queries. See "Implementation status" below. |
Compliance, incident review, GDPR Art 17 evidence-of-action. | Legitimate interest (1)(f); legal obligation (1)© for any retention beyond operational defaults. |
| Player profile (optional) | In-game name, character description (free text), biography (if the deployment enables it) | documents store (player entity components) |
Game functionality. | Contract (1)(b). |
| NPC memory references | account_id foreign keys in NPC episodic / semantic memory rows |
documents (NPC entity components) |
NPC dialogue continuity ("Hello again, alice"). | Legitimate interest (1)(f). |
| AI prompts (transient) | Last N turns of NPC dialogue context, including player-supplied free text | Sent to the configured AI provider; cached briefly in Redis | NPC dialogue generation. | Legitimate interest (1)(f); see "Sub-processors" below. |
The engine does NOT collect, in a stock deployment:
- Voice / audio.
- Real-name verification documents.
- Payment information (no built-in commerce).
- Geolocation beyond the source-IP of the session.
- Tracking cookies (the admin UI session cookie is functional, not analytic).
If your content pack adds further PII fields, you MUST document them here and update the privacy-policy-template before going live.
What's in backups¶
A full backup (per durability matrix) is a filesystem-consistent snapshot of:
- The Postgres database
maid_<inst>— includesaccounts,sessions,documents, all content-pack tables. - The state directory
/var/lib/maid-engine/<inst>/— includes pidfile, runtime caches, content-pack data. - The env file
/etc/maid/<inst>.env(if your backup script captures it — most don't, by policy, to keep secrets out of the backup tier).
Backups are NOT encrypted by MAID.
deploy/scripts/backup.shperforms no encryption, and there is noMAID_DEPLOY_BACKUP_KEY. If you require encryption-at-rest for the backup tier you MUST provide it at the destination/storage layer — an encrypted filesystem or an encrypted object-store bucket on the backup host (transport is separately protected when you use thessh://transport).
Implication: an erasure of account_id=X from the live DB does
NOT erase X from existing backups. See "Retention" below for when
the player rolls out of every tier.
Interim
maidCLI (MAID_SRC): the CLI verbs shown below have no system-widemaidbinary until M1.4 (install.shleaves/opt/maid/venvempty), so they run from a source checkout as themaid-engineservice user.export MAID_SRC=/path/to/maid-checkoutand build its venv once:sudo -u maid-engine -H uv sync --frozen --project "${MAID_SRC}"(uv:/opt/maid/current/.uv/bin/uv). The commands invoke"${MAID_SRC}/.venv/bin/maid"; insidebash -cblocksMAID_SRCis forwarded via--preserve-env=MAID_SRC.
Subject Access Requests (GDPR Art 15)¶
A data subject can request a full machine-readable copy of all PII the engine holds about them.
Procedure (operator):
- Verify the request via your DPA register (identity verified, request ID, legal-basis review). NEVER act on a raw player message.
- Resolve the username to
account_id:
sudo -u postgres psql -d "maid_${INST}" -tAc \
"SELECT id FROM accounts WHERE username = '<player-username>';"
- Export the player's data:
# DEFERRED — `ops export-player-data` is NOT wired yet (pending M9); it
# is not a real CLI verb today and will error if invoked. Until it
# lands, use the manual SQL recipe in player_data_erasure.md Diagnostic
# Step 3, but with SELECT instead of UPDATE/DELETE.
#
# Future (path-correct) form, once M9 lands — it runs as the
# `maid-engine` service user from the source checkout (see the CLI note
# at the top of this doc), never a bare `maid`:
# export ACCOUNT_ID=<account-id-from-step-2>
# sudo -u maid-engine --preserve-env=MAID_SRC,ACCOUNT_ID bash -c '
# set -a; . /etc/maid/<inst>.env; set +a
# exec "${MAID_SRC}/.venv/bin/maid" ops export-player-data \
# --account-id "${ACCOUNT_ID}" \
# --output "/var/lib/maid/<inst>/sar-${ACCOUNT_ID}.json"'
- Hand the JSON file to the data subject via your standard secure channel (NOT email). Record the delivery in the DPA register.
Format: the export is a single JSON document with keys
account, sessions (last 90 days), documents (player-owned
entities), npc_memories (rows referencing this account), and
audit (admin actions targeting this account, redacted to remove
other-subject PII).
SLA: GDPR Art 12(3) mandates 1 month (extensible to 3 with written justification). Operators should target 14 days.
Erasure requests (GDPR Art 17)¶
The mechanical procedure is documented end-to-end in
docs/runbooks/player_data_erasure.md.
Read that runbook before touching anything.
The CLI verb is:
# Resolve <account-id> from step 2, then run the verb as the `maid-engine`
# service user from the source checkout (see the CLI note at the top of
# this doc). `delete-player-data` needs the instance DB env, so source the
# env file inside the sudo shell.
export ACCOUNT_ID=<account-id-from-step-2>
sudo -u maid-engine --preserve-env=MAID_SRC,ACCOUNT_ID bash -c '
set -a; . /etc/maid/<inst>.env; set +a
exec "${MAID_SRC}/.venv/bin/maid" ops delete-player-data \
--account-id "${ACCOUNT_ID}" \
--confirm \
--ticket "<DPO-ticket-id>"'
It refuses to run without all three flags, calls the SQL function
erase_account_cascade() (migration 0005), emits an
OPS_GDPR_ERASURE_REQUESTED audit entry, and prints a completion
summary listing every table affected and the follow-up date for
backup-tier expiry.
What it erases:
accounts.email→[REDACTED]accounts.username→erased_<random-suffix>(uniqueness preserved)accounts.password_hash→ NULL (account can never log in again)sessions.ip_address→[REDACTED]- NPC memory text containing the username →
[REDACTED]sentinel - Document-store entities owned by the account → DELETE
What it does NOT erase:
- Backups taken before the erasure (until they roll out of every tier — see Retention).
- Audit log entries (legitimate-interest basis; the engine does not enforce retention itself — see "Retention" + "Implementation status" below).
- Aggregated, non-PII statistics (player-count metrics, total-mail counts).
Retention¶
| Class | Retention | Justification |
|---|---|---|
| Audit log (JSONL on disk) | Not enforced by the engine — files at data/logs/audit/audit-YYYY-MM-DD.jsonl accumulate until you rotate them. Operator obligation: wire logrotate, archive the rotated tarballs, and document the policy in your privacy notice. See "Implementation status" for the MVP gap. |
|
| Audit log (in-memory ring buffer) | The AuditLogger keeps the last 1000 entries in a deque for fast get_recent() queries; the API-side AuditLogStore keeps the last 10 000 entries. Both are volatile — process restart drops them. The advisory MAID_OBSERVABILITY_AUDIT_RETENTION_DAYS setting (default 90) is declared in ObservabilitySettings (packages/maid-engine/src/maid_engine/config/settings.py:754-769) but is not yet consumed by the disk writer; treat it as a forward-looking knob, not an enforced policy. |
|
| Backup, daily tier | 14 days | Operational recovery (point-in-time within 2 weeks). |
| Backup, weekly tier | 8 weeks | Operational recovery (medium-term). |
| Backup, monthly tier | 90 days | DR + compliance retention. This is the longest tier; an erasure is "complete" only after the oldest monthly backup containing the subject rolls off. |
| Session rows | 30 days from disconnect | Session reattachment + short-term abuse forensics. |
| NPC memory | Until the player is erased OR the consolidation system drops it | NPC dialogue continuity; survives until explicit erasure. |
| AI provider prompts | Provider-dependent; see "Sub-processors" | We do not control this — request your provider's policy. |
After an erasure: the data subject's record is fully gone from live plus the daily tier within 14 days, weekly within 8 weeks, and monthly within 90 days. Report the 90-day "fully complete" date to the data subject in your erasure-completion notice.
Implementation status¶
This section calls out which controls in this document are fully implemented in the current engine and which are operator-policy or MVP-deferred. Your privacy notice MUST NOT promise a control that you have not actually operationalised.
| Control | Status | Notes |
|---|---|---|
GDPR Art 17 erasure CLI (maid ops delete-player-data) |
Implemented (this PR) | Calls migration 0005's erase_account_cascade() inline if present, otherwise enqueues a _pending_erasures row. Emits an OPS_GDPR_ERASURE_REQUESTED audit row in both paths. |
| Audit JSONL on disk | Implemented | Each AuditLogger.log() call writes to data/logs/audit/audit-YYYY-MM-DD.jsonl. |
| Audit log retention enforcement | MVP-deferred | MAID_OBSERVABILITY_AUDIT_RETENTION_DAYS is declared but unused; the engine ships no log-rotation cron, no archival worker, and no automatic purge. Operator MUST configure logrotate (or equivalent) and document the retention period in their published privacy notice. Tracking issue: post-M2 SIEM-forwarder roadmap. |
| Persistent audit sink (SIEM forwarding) | MVP-deferred | No syslog / SIEM / object-store forwarder ships with M1. Operators requiring tamper-evident audit MUST tee the JSONL into their own pipeline (rsyslog → SIEM, or filebeat → ES). |
| Art 13 / 14 notice copy | Operator-policy | The customer-facing template (privacy-policy-template.md) covers the structure; you must fill in the jurisdiction-specific identity, contact, and lawful-basis fields. |
| Art 30 RoPA register | Operator-policy | The engine does not generate a RoPA. Use this guide's "What PII MAID collects" table as the data inventory, and maintain the register out-of-band (spreadsheet, GRC tool). |
| CCPA "Do Not Sell" | N/A | MAID does not sell personal data. The template makes this disclosure for you. |
Data residency¶
MAID stores all live data on the host operator runs the engine on. Residency is therefore determined by where the host is:
- The host's physical location (cloud region or co-location facility).
- The backup destination at
MAID_DEPLOY_BACKUP_REMOTE.
Operator obligation: document both above in your published
privacy policy (the customer-facing
privacy-policy-template.md has
<JURISDICTION> placeholders for exactly this).
EU operators: the backup destination MUST be in the EU/EEA OR under an approved transfer mechanism (SCCs, adequacy decision, BCRs). US-hosted backup of EU data is a documentation-and-legal exercise; the engine does not enforce it.
US operators (CCPA): Californian residents have access and deletion rights similar to GDPR Arts 15 and 17. The same CLI verbs satisfy both; record CCPA requests in the same DPA register.
Sub-processors¶
The following third parties may receive personal data when their provider is configured:
| Provider | Data shared | When |
|---|---|---|
| Anthropic (Claude) | NPC dialogue context: last N turns of in-game conversation with the player, including the player's IGN, any free-text they sent, and the relevant NPC memory references. NOT the player's email, password hash, or audit log. | Every NPC dialogue turn that goes through Anthropic, IF MAID_AI_DEFAULT_PROVIDER=anthropic. |
| OpenAI | As above. | IF MAID_AI_DEFAULT_PROVIDER=openai. |
| Ollama (local) | As above, but to a host you operate. No third-party transfer if Ollama runs on the MAID host or your private network. | IF MAID_AI_DEFAULT_PROVIDER=ollama. |
| Your backup destination | Backup tarballs. MAID does not encrypt them — the destination sees plaintext unless you enable encryption-at-rest at the storage layer (an encrypted bucket/filesystem on the backup host). | Nightly + on-demand. |
Operator obligation: publish your sub-processor list (with the
data classes shared) in your privacy policy.
privacy-policy-template.md has a
placeholder section for this.
Disabling AI entirely: set MAID_AI_ANTHROPIC_API_KEY= (empty)
and MAID_AI_OPENAI_API_KEY= (empty), and the AI dialogue system
will not call any provider; NPCs fall back to scripted lines.
Cookie policy¶
The admin UI sets exactly one cookie:
| Name | Purpose | Lifetime | HttpOnly | Secure | SameSite |
|---|---|---|---|---|---|
maid_admin_session |
Admin authentication. Signed (HMAC) so the engine can verify the cookie was issued by us. | Session (browser close) by default; configurable | Yes | Yes (in production) | Strict |
There are NO tracking cookies, NO analytic cookies, NO third-party cookies set by the admin UI.
The player-facing web client (packages/maid-engine/player_frontend/)
sets a similar functional session cookie for character login; same
properties.
Where this fits¶
- For the public-facing artifact you publish:
privacy-policy-template.md. - For the operator procedure to fulfil an erasure:
runbooks/player_data_erasure.md. - For the security baseline these procedures assume:
security_checklist.md. - For the data retention math (backup tiers, audit log retention):
durability_matrix.md.