-- 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);
