-- 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, 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_project CHECK (project_id = 'awoooi'), 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, 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_observation_project CHECK (project_id = 'awoooi'), 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;