-- Schema-only rollback for ops/teleo_v3_epistemic_contract.sql.
--
-- The rollback refuses to discard proposal-linked V3 data. A disposable
-- verifier may delete its own canary rows first; a real migration needs an
-- explicit export/port decision before this file can run.

\set ON_ERROR_STOP on
set timezone = 'UTC';

begin;

do $ownership_guard$
declare
  v_artifacts_present boolean;
  v_ledger_owner_valid boolean;
  v_ledger_row_valid boolean;
  v_missing_objects text;
begin
  select
    pg_catalog.to_regclass('kb_stage.teleo_v3_installation_ownership') is not null
    or pg_catalog.to_regclass('public.claim_evidence_assessments') is not null
    or exists (
      select 1
        from information_schema.columns columns
       where columns.table_schema = 'public'
         and (columns.table_name, columns.column_name) in (
           ('claims', 'owner_agent_id'),
           ('claims', 'proposition'),
           ('claims', 'body_markdown'),
           ('claims', 'scope'),
           ('claims', 'access_scope'),
           ('claims', 'admission_state'),
           ('claims', 'revision_criteria'),
           ('claims', 'revision_kind'),
           ('claims', 'supersedes_id'),
           ('claims', 'accepted_by_proposal_id'),
           ('sources', 'canonical_url'),
           ('sources', 'storage_uri'),
           ('sources', 'content_hash'),
           ('sources', 'access_scope'),
           ('sources', 'ingestion_origin'),
           ('sources', 'provenance_status'),
           ('sources', 'source_class'),
           ('sources', 'auto_promotion_policy'),
           ('sources', 'accepted_by_proposal_id'),
           ('claim_evidence', 'excerpt'),
           ('claim_evidence', 'locator_json'),
           ('claim_evidence', 'source_content_hash'),
           ('claim_evidence', 'rationale'),
           ('claim_evidence', 'polarity'),
           ('claim_evidence', 'accepted_from_proposal_id'),
           ('claim_edges', 'relation_type'),
           ('claim_edges', 'rationale'),
           ('claim_edges', 'scope_or_conditions'),
           ('claim_edges', 'accepted_by_proposal_id')
         )
    )
    or exists (
      select 1
        from pg_catalog.pg_proc procedure
        join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
       where namespace.nspname = 'kb_stage'
         and procedure.proname like 'teleo\_v3\_%' escape '\'
    )
    or exists (
      select 1
        from pg_catalog.pg_trigger trigger_row
       where not trigger_row.tgisinternal
         and trigger_row.tgname like 'teleo\_v3\_%' escape '\'
    )
    or exists (
      select 1
        from pg_catalog.pg_constraint constraint_row
       where constraint_row.conname like 'teleo\_v3\_%' escape '\'
    )
    or exists (
      select 1
        from pg_catalog.pg_class index_row
       where index_row.relkind = 'i'
         and index_row.relname like 'teleo\_v3\_%' escape '\'
    )
    into v_artifacts_present;

  if pg_catalog.to_regclass('kb_stage.teleo_v3_installation_ownership') is null then
    if v_artifacts_present then
      raise exception using
        errcode = '55000',
        message = 'teleo_v3 rollback refused: V3-named objects lack exact installation ownership';
    end if;
    return;
  end if;

  select owner_role.rolname = 'kb_gate_owner'
    into v_ledger_owner_valid
    from pg_catalog.pg_class relation
    join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
    join pg_catalog.pg_roles owner_role on owner_role.oid = relation.relowner
   where namespace.nspname = 'kb_stage'
     and relation.relname = 'teleo_v3_installation_ownership'
     and relation.relkind in ('r', 'p');
  if not coalesce(v_ledger_owner_valid, false) then
    raise exception using
      errcode = '55000',
      message = 'teleo_v3 rollback refused: installation ownership ledger owner is invalid';
  end if;

  execute $ownership_query$
    select count(*) = 1
       and bool_and(
         contract_name = 'livingip.teleo-v3-epistemic-contract'
         and contract_version = 1
         and installation_token = 'collision-free-install-v1'
       )
      from kb_stage.teleo_v3_installation_ownership
  $ownership_query$ into v_ledger_row_valid;
  if not coalesce(v_ledger_row_valid, false) then
    raise exception using
      errcode = '55000',
      message = 'teleo_v3 rollback refused: installation ownership ledger is not exact';
  end if;

  with expected_columns(table_name, column_name) as (
    values
      ('claims', 'owner_agent_id'),
      ('claims', 'proposition'),
      ('claims', 'body_markdown'),
      ('claims', 'scope'),
      ('claims', 'access_scope'),
      ('claims', 'admission_state'),
      ('claims', 'revision_criteria'),
      ('claims', 'revision_kind'),
      ('claims', 'supersedes_id'),
      ('claims', 'accepted_by_proposal_id'),
      ('sources', 'canonical_url'),
      ('sources', 'storage_uri'),
      ('sources', 'content_hash'),
      ('sources', 'access_scope'),
      ('sources', 'ingestion_origin'),
      ('sources', 'provenance_status'),
      ('sources', 'source_class'),
      ('sources', 'auto_promotion_policy'),
      ('sources', 'accepted_by_proposal_id'),
      ('claim_evidence', 'excerpt'),
      ('claim_evidence', 'locator_json'),
      ('claim_evidence', 'source_content_hash'),
      ('claim_evidence', 'rationale'),
      ('claim_evidence', 'polarity'),
      ('claim_evidence', 'accepted_from_proposal_id'),
      ('claim_edges', 'relation_type'),
      ('claim_edges', 'rationale'),
      ('claim_edges', 'scope_or_conditions'),
      ('claim_edges', 'accepted_by_proposal_id')
  ), missing(object_name) as (
    select pg_catalog.format('%I.%I', expected.table_name, expected.column_name)
      from expected_columns expected
     where not exists (
       select 1
         from information_schema.columns columns
        where columns.table_schema = 'public'
          and columns.table_name = expected.table_name
          and columns.column_name = expected.column_name
     )
    union all
    select 'public.claim_evidence_assessments'
     where pg_catalog.to_regclass('public.claim_evidence_assessments') is null
  )
  select pg_catalog.string_agg(object_name, ', ' order by object_name)
    into v_missing_objects
    from missing;
  if v_missing_objects is not null then
    raise exception using
      errcode = '55000',
      message = pg_catalog.format(
        'teleo_v3 rollback refused: installation-owned object set is incomplete: %s',
        v_missing_objects
      );
  end if;
end
$ownership_guard$;

do $data_guard$
declare
  contract_rows_exist boolean := false;
begin
  if to_regclass('public.claim_evidence_assessments') is not null then
    execute 'select exists (select 1 from public.claim_evidence_assessments)'
      into contract_rows_exist;
    if contract_rows_exist then
      raise exception using
        errcode = '55000',
        message = 'teleo_v3 rollback refused: claim_evidence_assessments contains V3 data';
    end if;
  end if;

  if exists (
    select 1 from information_schema.columns
     where table_schema = 'public' and table_name = 'claim_edges'
       and column_name = 'accepted_by_proposal_id'
  ) then
    execute 'select exists (select 1 from public.claim_edges where accepted_by_proposal_id is not null)'
      into contract_rows_exist;
    if contract_rows_exist then
      raise exception using
        errcode = '55000',
        message = 'teleo_v3 rollback refused: claim_edges contains proposal-linked V3 data';
    end if;
  end if;

  if exists (
    select 1 from information_schema.columns
     where table_schema = 'public' and table_name = 'claim_evidence'
       and column_name = 'accepted_from_proposal_id'
  ) then
    execute 'select exists (select 1 from public.claim_evidence where accepted_from_proposal_id is not null)'
      into contract_rows_exist;
    if contract_rows_exist then
      raise exception using
        errcode = '55000',
        message = 'teleo_v3 rollback refused: claim_evidence contains proposal-linked V3 data';
    end if;
  end if;

  if exists (
    select 1 from information_schema.columns
     where table_schema = 'public' and table_name = 'sources'
       and column_name = 'accepted_by_proposal_id'
  ) then
    execute 'select exists (select 1 from public.sources where accepted_by_proposal_id is not null)'
      into contract_rows_exist;
    if contract_rows_exist then
      raise exception using
        errcode = '55000',
        message = 'teleo_v3 rollback refused: sources contains proposal-linked V3 data';
    end if;
  end if;

  if exists (
    select 1 from information_schema.columns
     where table_schema = 'public' and table_name = 'claims'
       and column_name = 'admission_state'
  ) then
    execute $query$
      select exists (
        select 1 from public.claims
         where admission_state = 'admitted' or accepted_by_proposal_id is not null
      )
    $query$ into contract_rows_exist;
    if contract_rows_exist then
      raise exception using
        errcode = '55000',
        message = 'teleo_v3 rollback refused: claims contains admitted or proposal-linked V3 data';
    end if;
  end if;
end
$data_guard$;

drop trigger if exists teleo_v3_guard_linked_proposal_decision on kb_stage.kb_proposals;
drop trigger if exists teleo_v3_00_enforce_apply_contract_cutover on kb_stage.kb_proposals;
alter table kb_stage.kb_proposals
  drop constraint if exists teleo_v3_proposal_apply_receipt_check;

drop trigger if exists teleo_v3_edge_immutable on public.claim_edges;
drop trigger if exists teleo_v3_edge_reject_delete on public.claim_edges;
drop trigger if exists teleo_v3_edge_validate_endpoints on public.claim_edges;
drop trigger if exists teleo_v3_edge_require_accepted_proposal on public.claim_edges;
drop trigger if exists teleo_v3_00_edge_require_linked_insert on public.claim_edges;

drop trigger if exists teleo_v3_evidence_immutable on public.claim_evidence;
drop trigger if exists teleo_v3_evidence_reject_delete on public.claim_evidence;
drop trigger if exists teleo_v3_evidence_validate_provenance on public.claim_evidence;
drop trigger if exists teleo_v3_evidence_require_accepted_proposal on public.claim_evidence;
drop trigger if exists teleo_v3_00_evidence_require_linked_insert on public.claim_evidence;

drop trigger if exists teleo_v3_source_immutable on public.sources;
drop trigger if exists teleo_v3_source_reject_delete on public.sources;
drop trigger if exists teleo_v3_source_require_accepted_proposal on public.sources;
drop trigger if exists teleo_v3_00_source_require_linked_insert on public.sources;

drop trigger if exists teleo_v3_claim_immutable on public.claims;
drop trigger if exists teleo_v3_claim_reject_delete on public.claims;
drop trigger if exists teleo_v3_claim_require_accepted_proposal on public.claims;
drop trigger if exists teleo_v3_00_claim_require_linked_insert on public.claims;

drop index if exists public.teleo_v3_edge_typed_adjacency_idx;
drop index if exists public.teleo_v3_evidence_claim_polarity_idx;
drop index if exists public.teleo_v3_assessment_one_successor_idx;
drop index if exists public.teleo_v3_claim_one_admitted_successor_idx;

drop table if exists public.claim_evidence_assessments;

alter table public.claim_edges
  drop constraint if exists teleo_v3_edge_contract_check,
  drop constraint if exists teleo_v3_edge_accepted_proposal_fkey,
  drop column if exists relation_type,
  drop column if exists rationale,
  drop column if exists scope_or_conditions,
  drop column if exists accepted_by_proposal_id;

alter table public.claim_evidence
  drop constraint if exists teleo_v3_evidence_contract_check,
  drop constraint if exists teleo_v3_evidence_accepted_proposal_fkey,
  drop column if exists excerpt,
  drop column if exists locator_json,
  drop column if exists source_content_hash,
  drop column if exists rationale,
  drop column if exists polarity,
  drop column if exists accepted_from_proposal_id;

alter table public.sources
  drop constraint if exists teleo_v3_source_contract_check,
  drop constraint if exists teleo_v3_source_accepted_proposal_fkey,
  drop column if exists canonical_url,
  drop column if exists storage_uri,
  drop column if exists content_hash,
  drop column if exists access_scope,
  drop column if exists ingestion_origin,
  drop column if exists provenance_status,
  drop column if exists source_class,
  drop column if exists auto_promotion_policy,
  drop column if exists accepted_by_proposal_id;

alter table public.claims
  drop constraint if exists teleo_v3_claim_admission_check,
  drop constraint if exists teleo_v3_claim_accepted_proposal_fkey,
  drop constraint if exists teleo_v3_claim_supersedes_fkey,
  drop constraint if exists teleo_v3_claim_owner_agent_fkey,
  drop column if exists owner_agent_id,
  drop column if exists proposition,
  drop column if exists body_markdown,
  drop column if exists scope,
  drop column if exists access_scope,
  drop column if exists admission_state,
  drop column if exists revision_criteria,
  drop column if exists revision_kind,
  drop column if exists supersedes_id,
  drop column if exists accepted_by_proposal_id;

drop function if exists kb_stage.teleo_v3_guard_linked_proposal_decision();
drop function if exists kb_stage.teleo_v3_acquire_protected_relation_locks();
drop function if exists kb_stage.teleo_v3_enforce_apply_contract_cutover();
drop function if exists kb_stage.teleo_v3_validate_evidence_assessment();
drop function if exists kb_stage.teleo_v3_validate_claim_edge();
drop function if exists kb_stage.teleo_v3_validate_claim_evidence();
drop function if exists kb_stage.teleo_v3_guard_immutable_fields();
drop function if exists kb_stage.teleo_v3_reject_delete();
drop function if exists kb_stage.teleo_v3_require_linked_insert();
drop function if exists kb_stage.teleo_v3_require_accepted_proposal();

drop table if exists kb_stage.teleo_v3_installation_ownership;

commit;

select jsonb_build_object(
  'contract', 'livingip.teleo-v3-epistemic-contract',
  'version', 1,
  'migration', 'rolled_back'
)::text;
