Files
J3vbandClaude bd397d2bbc feat(b3-7): alpha-shaped dataset — deterministic seed profile and the v1.2.0-alpha.4 snapshot (#1469)
go run ./cmd/seed -confirm-dev -profile alpha fills an empty database with
the plan's dataset: 100 users (1/2/5/92 across the four roles), 12 channels
(10 text + 2 voice; 3 role-override, 2 user-override, 1 archived), 20,000
messages over 30 simulated days on a diurnal curve (exactly 15% in DMs
across 40 pairs), 300 attachment rows (60/10/10/20%, 10KB-5MB), 500
reactions, 30 invites (10 revoked), one disabled plugin row. Deterministic
by construction — fixed seed, fixed clock, constant bcrypt hash, explicit
ids and timestamps, VACUUM INTO as the canonical bytes — and
TestAlphaProfileByteIdentical holds the property (two full runs compared
byte for byte; a schema_versions wall-clock leak was the one leak found,
now pinned by the scrub). Two constants deliberately leave no rows and say
why in the package comment: voice sessions are LiveKit-ephemeral, and the
replay log is empty exactly as on a server restarted for an upgrade.

The committed snapshot (3.2MB, under the 5MB LFS line) is the scrubbed
VACUUM of that profile at the alpha.4 migration set - the schema has not
moved since the tag, so it is a true alpha.4 artifact. scrub.sql beside it
also anonymises a real donated database. db/alpha_snapshot_test.go is the
standing canary: provenance (31 applied migrations), HEAD migrations apply
cleanly, and every promised row count checks out, FTS included. Consumers
(B4 HP-4, B6 upgrade rehearsal, B10 in-place upgrade) are named in the
snapshot README and docs/deployment.md.


Claude-Session: https://claude.ai/code/session_01B8dwVLEihnGZYtH9X631F4

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-31 06:12:09 +00:00

59 lines
2.5 KiB
SQL

-- Identity and credential scrub for alpha snapshots (B3-7 item 2).
--
-- Applied by `go run ./cmd/seed -profile alpha -snapshot … -scrub this-file`
-- before VACUUM INTO writes the committed snapshot. Every statement is
-- idempotent. Scope, stated precisely (Codex on #1469): this script removes
-- account identities, credentials, session/token material, invite codes,
-- audit detail, user-chosen filenames and wall-clock apply times. It
-- DELIBERATELY DOES NOT touch message content, channel names/topics, or the
-- FTS index built from them — the committed snapshot's content is synthetic
-- lexicon text with nothing to hide, and content anonymisation of a REAL
-- database is a judgement call no blanket UPDATE can make. A donated
-- production database is NOT made shareable by this script alone: its
-- messages and channel names must be cleared or rewritten first (and the
-- FTS index rebuilt), or the donation refused. Statements are split on ";"
-- with comment lines stripped, so keep one statement per block and comments
-- on their own lines.
UPDATE users
SET username = 'user' || printf('%03d', id),
display_name = NULL,
about = NULL,
custom_status = NULL,
avatar = NULL,
totp_secret = NULL,
identity_public_key = NULL,
ban_reason = CASE WHEN ban_reason IS NULL THEN NULL ELSE 'scrubbed' END,
password = '$2a$12$EBrRXmplT1ryU0o/HzELSePreo.gK5.z5Tjo4ec/ISchy5gKwxtQq';
-- Sessions, tokens and rate-limit state authenticate or profile people —
-- a snapshot carries none of them.
DELETE FROM sessions;
DELETE FROM api_tokens;
DELETE FROM login_attempts;
DELETE FROM rate_lockouts;
-- Invite codes are shared secrets; keep the rows, rotate the codes.
UPDATE invites SET code = 'ALPHA-INV-' || printf('%02d', id);
-- Audit detail can quote user-entered text; the action skeleton is enough.
UPDATE audit_log SET detail = '';
-- Migration tracking records wall-clock apply times; the snapshot's
-- canonical form pins them to the window end so two generation runs produce
-- identical bytes (and a real donated database stops dating its operator).
UPDATE schema_versions SET applied_at = '2026-08-01 00:00:00';
-- Attachment filenames are user-chosen text.
UPDATE attachments
SET filename = 'file-' || substr(id, 1, 8) ||
CASE
WHEN mime_type LIKE 'image/%' THEN '.png'
WHEN mime_type LIKE 'audio/%' THEN '.ogg'
WHEN mime_type LIKE 'video/%' THEN '.mp4'
ELSE '.bin'
END;