-- Base schema snapshot: the database shape immediately BEFORE migration 0009. -- -- This file exists solely to feed TestSchemaMatchesMigrations. Applying this -- file and then running every migration in migrations/ must produce the same -- schema as applying schema.sql directly. That is the invariant the test -- enforces, and it is what keeps schema.sql and the migrations in lockstep. -- -- HOW THIS WAS DERIVED -- -- Not from history. It was reconstructed by taking the current schema.sql and -- reversing migrations 0029 down to 0009: -- -- 0029 drop stripe_processed_events -- 0028 drop devices.secret_lookup and its index -- 0027 drop users.registry_domain -- 0026 drop webhooks.last_fired_at -- 0025 drop taken_down_subjects and labeler_cursor -- 0023 (labels table is created by 0023 and dropped by 0025: absent here) -- 0022 drop jetstream_cursor -- 0021 drop repository_stats_daily -- 0020 drop manifests.subject_digest and its index -- 0019 drop advisor_suggestions -- 0018 drop users.oci_client -- 0017 drop repo_pages.user_edited -- 0015 drop webhooks and scans -- 0014 drop users.default_hold_did -- (hold_captain_records.supporter_badge_tiers is added by 0014 and -- dropped by 0016, so it nets out and is absent here) -- 0013 drop crypto_keys -- 0012 drop layers.annotations -- 0011 hold_captain_records is rebuilt by 0011; 0010 added successor, so -- the pre-0010 table has neither successor nor supporter_badge_tiers -- 0009 restore the 11 dead columns on manifests that 0009 removes -- -- WHAT THIS DOES AND DOES NOT PROVE -- -- It proves that migrations 0009-0029 transform this shape into schema.sql, and -- it will prove the same for every migration added from now on. That is the -- point: new work is covered automatically. -- -- It does NOT independently verify tables that appear in no migration (tags, -- oauth_sessions, stars, and the rest predate the migration system). Those are -- copied from schema.sql here, so the test compares them against themselves and -- can only ever agree. Drift in those tables on a real database is caught by the -- startup drift check in schema.go, not by this test. -- -- The 11 dead columns on manifests are typed TEXT because migration 0009 never -- reads them (its INSERT ... SELECT names only the columns it keeps), so their -- original types cannot be recovered and do not affect the outcome. -- -- WHEN YOU ADD A MIGRATION: do not touch this file. It is a fixed starting -- point. Update schema.sql instead, which is what the migration must converge -- on. Only edit this file if you are correcting the reconstruction itself. CREATE TABLE IF NOT EXISTS schema_migrations ( version INTEGER PRIMARY KEY, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- Versions 1-8 are recorded as already applied, because "the state before 0009" -- means exactly that. Only 0001 still exists as a file; 0002-0008 were deleted -- from the tree after they shipped, and migration 0009's own comment confirms -- 0004 was recorded on production. -- -- This also keeps the test off a live rake: migration 0001 is the example -- migration, whose query is `SELECT COUNT(*) FROM schema_migrations`. go-libsql -- rejects a row-returning statement passed to Exec ("Execute returned rows"), so -- 0001 would fail hard on any database that has not recorded it. Every real -- database recorded it years ago, so this is invisible in production, but a -- future migration that opens with a SELECT would fail the same way. Migrations -- must not return rows. INSERT INTO schema_migrations (version) VALUES (1), (2), (3), (4), (5), (6), (7), (8); CREATE TABLE IF NOT EXISTS users ( did TEXT PRIMARY KEY, handle TEXT NOT NULL, pds_endpoint TEXT NOT NULL, avatar TEXT, last_seen TIMESTAMP NOT NULL, UNIQUE(handle) ); CREATE INDEX IF NOT EXISTS idx_users_handle ON users(handle); -- Pre-0009: still carries the 11 columns that migration 0009 removes. CREATE TABLE IF NOT EXISTS manifests ( id INTEGER PRIMARY KEY AUTOINCREMENT, did TEXT NOT NULL, repository TEXT NOT NULL, digest TEXT NOT NULL, hold_endpoint TEXT NOT NULL, schema_version INTEGER NOT NULL, media_type TEXT NOT NULL, config_digest TEXT, config_size INTEGER, artifact_type TEXT NOT NULL DEFAULT 'container-image', title TEXT, description TEXT, source_url TEXT, documentation_url TEXT, licenses TEXT, icon_url TEXT, readme_url TEXT, platform_os TEXT, platform_architecture TEXT, platform_variant TEXT, platform_os_version TEXT, created_at TIMESTAMP NOT NULL, UNIQUE(did, repository, digest), FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_manifests_did_repo ON manifests(did, repository); CREATE INDEX IF NOT EXISTS idx_manifests_created_at ON manifests(created_at DESC); CREATE INDEX IF NOT EXISTS idx_manifests_digest ON manifests(digest); CREATE INDEX IF NOT EXISTS idx_manifests_artifact_type ON manifests(artifact_type); CREATE TABLE IF NOT EXISTS repository_annotations ( did TEXT NOT NULL, repository TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(did, repository, key), FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_repository_annotations_did_repo ON repository_annotations(did, repository); CREATE INDEX IF NOT EXISTS idx_repository_annotations_key ON repository_annotations(key); CREATE TABLE IF NOT EXISTS layers ( manifest_id INTEGER NOT NULL, digest TEXT NOT NULL, size INTEGER NOT NULL, media_type TEXT NOT NULL, layer_index INTEGER NOT NULL, PRIMARY KEY(manifest_id, layer_index), FOREIGN KEY(manifest_id) REFERENCES manifests(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_layers_digest ON layers(digest); CREATE TABLE IF NOT EXISTS manifest_references ( manifest_id INTEGER NOT NULL, digest TEXT NOT NULL, media_type TEXT NOT NULL, size INTEGER NOT NULL, platform_architecture TEXT, platform_os TEXT, platform_variant TEXT, platform_os_version TEXT, is_attestation BOOLEAN DEFAULT FALSE, reference_index INTEGER NOT NULL, PRIMARY KEY(manifest_id, reference_index), FOREIGN KEY(manifest_id) REFERENCES manifests(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_manifest_references_digest ON manifest_references(digest); CREATE TABLE IF NOT EXISTS tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, did TEXT NOT NULL, repository TEXT NOT NULL, tag TEXT NOT NULL, digest TEXT NOT NULL, created_at TIMESTAMP NOT NULL, UNIQUE(did, repository, tag), FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_tags_did_repo ON tags(did, repository); CREATE TABLE IF NOT EXISTS oauth_sessions ( session_key TEXT PRIMARY KEY, account_did TEXT NOT NULL, session_id TEXT NOT NULL, session_data TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(account_did, session_id) ); CREATE INDEX IF NOT EXISTS idx_oauth_sessions_did ON oauth_sessions(account_did); CREATE INDEX IF NOT EXISTS idx_oauth_sessions_updated ON oauth_sessions(updated_at DESC); CREATE TABLE IF NOT EXISTS oauth_auth_requests ( state TEXT PRIMARY KEY, request_data TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_oauth_auth_requests_created ON oauth_auth_requests(created_at); CREATE TABLE IF NOT EXISTS ui_sessions ( id TEXT PRIMARY KEY, did TEXT NOT NULL, handle TEXT NOT NULL, pds_endpoint TEXT NOT NULL, oauth_session_id TEXT, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_ui_sessions_did ON ui_sessions(did); CREATE INDEX IF NOT EXISTS idx_ui_sessions_expires ON ui_sessions(expires_at); CREATE TABLE IF NOT EXISTS devices ( id TEXT PRIMARY KEY, did TEXT NOT NULL, handle TEXT NOT NULL, name TEXT NOT NULL, secret_hash TEXT NOT NULL UNIQUE, ip_address TEXT, location TEXT, user_agent TEXT, created_at TIMESTAMP NOT NULL, last_used TIMESTAMP, FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_devices_did ON devices(did); CREATE INDEX IF NOT EXISTS idx_devices_hash ON devices(secret_hash); CREATE TABLE IF NOT EXISTS pending_device_auth ( device_code TEXT PRIMARY KEY, user_code TEXT NOT NULL UNIQUE, device_name TEXT NOT NULL, ip_address TEXT, user_agent TEXT, expires_at TIMESTAMP NOT NULL, approved_did TEXT, approved_at TIMESTAMP, device_secret TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_pending_device_auth_user_code ON pending_device_auth(user_code); CREATE INDEX IF NOT EXISTS idx_pending_device_auth_expires ON pending_device_auth(expires_at); CREATE TABLE IF NOT EXISTS repository_stats ( did TEXT NOT NULL, repository TEXT NOT NULL, pull_count INTEGER NOT NULL DEFAULT 0, last_pull TIMESTAMP, push_count INTEGER NOT NULL DEFAULT 0, last_push TIMESTAMP, PRIMARY KEY(did, repository), FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_repository_stats_did ON repository_stats(did); CREATE INDEX IF NOT EXISTS idx_repository_stats_pull_count ON repository_stats(pull_count DESC); CREATE TABLE IF NOT EXISTS stars ( starrer_did TEXT NOT NULL, owner_did TEXT NOT NULL, repository TEXT NOT NULL, created_at TIMESTAMP NOT NULL, PRIMARY KEY(starrer_did, owner_did, repository), FOREIGN KEY(starrer_did) REFERENCES users(did) ON DELETE CASCADE, FOREIGN KEY(owner_did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_stars_owner_repo ON stars(owner_did, repository); CREATE INDEX IF NOT EXISTS idx_stars_starrer ON stars(starrer_did); -- Pre-0010: no successor column yet (0010 adds it, 0011 rebuilds the table). CREATE TABLE IF NOT EXISTS hold_captain_records ( hold_did TEXT PRIMARY KEY, owner_did TEXT NOT NULL, public BOOLEAN NOT NULL, allow_all_crew BOOLEAN NOT NULL, deployed_at TEXT, region TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_hold_captain_updated ON hold_captain_records(updated_at); CREATE TABLE IF NOT EXISTS hold_crew_approvals ( hold_did TEXT NOT NULL, user_did TEXT NOT NULL, approved_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, PRIMARY KEY(hold_did, user_did) ); CREATE INDEX IF NOT EXISTS idx_crew_approvals_expires ON hold_crew_approvals(expires_at); CREATE TABLE IF NOT EXISTS hold_crew_denials ( hold_did TEXT NOT NULL, user_did TEXT NOT NULL, denial_count INTEGER NOT NULL DEFAULT 1, next_retry_at TIMESTAMP NOT NULL, last_denied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(hold_did, user_did) ); CREATE INDEX IF NOT EXISTS idx_crew_denials_retry ON hold_crew_denials(next_retry_at); CREATE TABLE IF NOT EXISTS hold_crew_members ( hold_did TEXT NOT NULL, member_did TEXT NOT NULL, rkey TEXT NOT NULL, role TEXT, permissions TEXT, tier TEXT, added_at TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (hold_did, member_did) ); CREATE INDEX IF NOT EXISTS idx_hold_crew_member ON hold_crew_members(member_did); CREATE INDEX IF NOT EXISTS idx_hold_crew_hold ON hold_crew_members(hold_did); CREATE INDEX IF NOT EXISTS idx_hold_crew_rkey ON hold_crew_members(hold_did, rkey); CREATE TABLE IF NOT EXISTS repo_pages ( did TEXT NOT NULL, repository TEXT NOT NULL, description TEXT, avatar_cid TEXT, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, PRIMARY KEY(did, repository), FOREIGN KEY(did) REFERENCES users(did) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_repo_pages_did ON repo_pages(did);