Skip to content

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> — includes accounts, 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.sh performs no encryption, and there is no MAID_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 the ssh:// 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 maid CLI (MAID_SRC): the CLI verbs shown below have no system-wide maid binary until M1.4 (install.sh leaves /opt/maid/venv empty), so they run from a source checkout as the maid-engine service user. export MAID_SRC=/path/to/maid-checkout and 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"; inside bash -c blocks MAID_SRC is 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):

  1. Verify the request via your DPA register (identity verified, request ID, legal-basis review). NEVER act on a raw player message.
  2. Resolve the username to account_id:
sudo -u postgres psql -d "maid_${INST}" -tAc \
    "SELECT id FROM accounts WHERE username = '<player-username>';"
  1. 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"'
  1. 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.usernameerased_<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.

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