-- Durable external MCP version lifecycle receipts.
--
-- Safety contract:
--   * catalog metadata only; no package install, tool call, credential, raw page,
--     request body, response body, or RAG content is stored;
--   * additive schema only, so application rollback does not delete evidence;
--   * every observation is bound to one run_id / trace_id / work_item_id;
--   * tenant RLS remains fail-closed through app.project_id.

BEGIN;

CREATE TABLE IF NOT EXISTS awooop_mcp_version_lifecycle_run (
    run_id                  UUID PRIMARY KEY,
    trace_id                VARCHAR(128) NOT NULL,
    work_item_id            VARCHAR(128) NOT NULL,
    project_id              VARCHAR(64) NOT NULL
        REFERENCES awooop_projects(project_id) ON DELETE RESTRICT,
    trigger_kind            VARCHAR(32) NOT NULL,
    status                  VARCHAR(48) NOT NULL,
    candidate_count         INTEGER NOT NULL DEFAULT 0,
    observed_count          INTEGER NOT NULL DEFAULT 0,
    baseline_count          INTEGER NOT NULL DEFAULT 0,
    changed_count           INTEGER NOT NULL DEFAULT 0,
    policy_blocked_count    INTEGER NOT NULL DEFAULT 0,
    error_count             INTEGER NOT NULL DEFAULT 0,
    receipt                 JSONB NOT NULL DEFAULT '{}'::jsonb,
    started_at              TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    ended_at                TIMESTAMPTZ,

    CONSTRAINT uix_mcp_version_lifecycle_trace
        UNIQUE (project_id, trace_id),
    CONSTRAINT chk_mcp_version_lifecycle_trigger
        CHECK (trigger_kind IN ('startup','scheduled','operator_replay','test')),
    CONSTRAINT chk_mcp_version_lifecycle_status
        CHECK (status IN (
            'running',
            'completed_no_change',
            'completed_no_write_policy_blocked',
            'partial_degraded',
            'failed'
        )),
    CONSTRAINT chk_mcp_version_lifecycle_counts
        CHECK (
            candidate_count >= 0 AND observed_count >= 0 AND baseline_count >= 0
            AND changed_count >= 0 AND policy_blocked_count >= 0
            AND error_count >= 0
        ),
    CONSTRAINT chk_mcp_version_lifecycle_terminal_time
        CHECK (
            (status = 'running' AND ended_at IS NULL)
            OR (status <> 'running' AND ended_at IS NOT NULL)
        )
);

CREATE TABLE IF NOT EXISTS awooop_mcp_version_observation (
    observation_id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    run_id                  UUID NOT NULL
        REFERENCES awooop_mcp_version_lifecycle_run(run_id) ON DELETE RESTRICT,
    trace_id                VARCHAR(128) NOT NULL,
    work_item_id            VARCHAR(128) NOT NULL,
    project_id              VARCHAR(64) NOT NULL
        REFERENCES awooop_projects(project_id) ON DELETE RESTRICT,
    candidate_id            VARCHAR(160) NOT NULL,
    catalog_source          VARCHAR(32) NOT NULL,
    source_url              TEXT NOT NULL,
    source_url_sha256       VARCHAR(64) NOT NULL,
    http_status             INTEGER,
    content_sha256          VARCHAR(64),
    observed_version        VARCHAR(128),
    previous_version        VARCHAR(128),
    discovery_status        VARCHAR(32) NOT NULL,
    change_state            VARCHAR(32) NOT NULL,
    lifecycle_terminal      VARCHAR(48) NOT NULL,
    policy_decision         JSONB NOT NULL DEFAULT '{}'::jsonb,
    stage_receipts          JSONB NOT NULL DEFAULT '{}'::jsonb,
    error_class             VARCHAR(128),
    observed_at             TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT uix_mcp_version_observation_run_candidate
        UNIQUE (run_id, candidate_id),
    CONSTRAINT chk_mcp_version_catalog_source
        CHECK (catalog_source IN ('smithery','mcp-so')),
    CONSTRAINT chk_mcp_version_source_url_hash
        CHECK (source_url_sha256 ~ '^[0-9a-f]{64}$'),
    CONSTRAINT chk_mcp_version_content_hash
        CHECK (content_sha256 IS NULL OR content_sha256 ~ '^[0-9a-f]{64}$'),
    CONSTRAINT chk_mcp_version_discovery_status
        CHECK (discovery_status IN ('observed','source_error','policy_blocked')),
    CONSTRAINT chk_mcp_version_change_state
        CHECK (change_state IN (
            'baseline_created','no_change','version_changed',
            'content_changed','source_unavailable'
        )),
    CONSTRAINT chk_mcp_version_terminal
        CHECK (lifecycle_terminal IN (
            'no_write_baseline','no_write_no_change','no_write_policy_blocked',
            'no_write_source_error','replay_passed_canary_pending',
            'promoted','rolled_back'
        ))
);

CREATE INDEX IF NOT EXISTS idx_mcp_version_lifecycle_recent
    ON awooop_mcp_version_lifecycle_run (project_id, started_at DESC);

CREATE INDEX IF NOT EXISTS idx_mcp_version_observation_candidate_recent
    ON awooop_mcp_version_observation
        (project_id, candidate_id, observed_at DESC);

CREATE INDEX IF NOT EXISTS idx_mcp_version_observation_run
    ON awooop_mcp_version_observation (project_id, run_id, observed_at);

ALTER TABLE awooop_mcp_version_lifecycle_run ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_mcp_version_lifecycle_run FORCE ROW LEVEL SECURITY;
ALTER TABLE awooop_mcp_version_observation ENABLE ROW LEVEL SECURITY;
ALTER TABLE awooop_mcp_version_observation FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS mcp_version_lifecycle_run_tenant
    ON awooop_mcp_version_lifecycle_run;
CREATE POLICY mcp_version_lifecycle_run_tenant
    ON awooop_mcp_version_lifecycle_run
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

DROP POLICY IF EXISTS mcp_version_observation_tenant
    ON awooop_mcp_version_observation;
CREATE POLICY mcp_version_observation_tenant
    ON awooop_mcp_version_observation
    USING (project_id = current_setting('app.project_id', TRUE))
    WITH CHECK (project_id = current_setting('app.project_id', TRUE));

GRANT SELECT, INSERT, UPDATE ON awooop_mcp_version_lifecycle_run TO awooop_app;
GRANT SELECT, INSERT ON awooop_mcp_version_observation TO awooop_app;
GRANT SELECT, INSERT, UPDATE ON awooop_mcp_version_lifecycle_run TO awoooi;
GRANT SELECT, INSERT ON awooop_mcp_version_observation TO awoooi;

COMMENT ON TABLE awooop_mcp_version_lifecycle_run IS
    'Durable controlled-apply lifecycle run receipts; additive and rollback-safe.';
COMMENT ON TABLE awooop_mcp_version_observation IS
    'Normalized external MCP catalog observations; raw external content is forbidden.';

COMMIT;
