-- v12.3 semantic-versioning and contract-governance delta.
-- Prior migrations remain immutable. Legacy ambiguous weight contexts are blocked, not guessed.

alter table signal_engine.sell_decision_context
    add column if not exists weight_semantics_version smallint;

-- Every row created before this migration is conservatively classified as legacy/ambiguous.
update signal_engine.sell_decision_context
set weight_semantics_version = 1
where weight_semantics_version is null;

alter table signal_engine.sell_decision_context
    alter column weight_semantics_version set default 2,
    alter column weight_semantics_version set not null,
    drop constraint if exists ck_sell_context_weight_semantics_version,
    add constraint ck_sell_context_weight_semantics_version
        check (weight_semantics_version in (1, 2));

comment on column signal_engine.sell_decision_context.weight_semantics_version is
    '1=legacy ambiguous current_portfolio_weight backfill; 2=explicit security and lot weights from approved source. Decision reads require 2.';

alter table signal_engine.signal_decision
    add column if not exists decision_contract_version text,
    add column if not exists policy_trace_schema_version smallint;

update signal_engine.signal_decision
set decision_contract_version = coalesce(decision_contract_version, 'sell-decision.v2'),
    policy_trace_schema_version = coalesce(policy_trace_schema_version, 2)
where decision_contract_version is null
   or policy_trace_schema_version is null;

alter table signal_engine.signal_decision
    alter column decision_contract_version set default 'sell-decision.v2',
    alter column decision_contract_version set not null,
    alter column policy_trace_schema_version set default 2,
    alter column policy_trace_schema_version set not null,
    drop constraint if exists ck_signal_decision_contract_version,
    add constraint ck_signal_decision_contract_version
        check (decision_contract_version = 'sell-decision.v2'),
    drop constraint if exists ck_signal_decision_policy_trace_schema_version,
    add constraint ck_signal_decision_policy_trace_schema_version
        check (policy_trace_schema_version = 2);

create table if not exists signal_engine.policy_contract_definition (
    contract_version text primary key,
    content_hash text not null unique,
    policy_json jsonb not null,
    status text not null check (status in ('PROPOSED', 'APPROVED', 'RETIRED')),
    effective_from timestamptz null,
    approved_by text null,
    approved_at timestamptz null,
    created_at timestamptz not null default now(),
    check (jsonb_typeof(policy_json) = 'object')
);

insert into signal_engine.policy_contract_definition
(contract_version, content_hash, policy_json, status)
values
('sell-policy.v1', 'a269a0331b83c0f6ec108e7587de1d20c798036ff8d0c03d726cd854e73d8480', $$
{
  "changeControl": "MODEL_CHANGE_AND_GOLDEN_OOS_REQUIRED",
  "contractVersion": "sell-policy.v1",
  "decisionContractVersion": "sell-decision.v2",
  "policies": [
    {
      "condition": "approved hard impairment",
      "documentedRange": [
        0.8,
        1.0
      ],
      "mayCrossStrategicCore": true,
      "policyId": "ALG-SELL-001",
      "priority": 1000,
      "reentryEligible": false,
      "requestedSellRatioOfLot": 1.0,
      "terminal": true
    },
    {
      "condition": "capital floor breached",
      "decisionRequired": "Whether portfolio survival bypasses same-direction cooldown",
      "mayCrossStrategicCore": true,
      "policyId": "ALG-SELL-PORT-001",
      "priority": 900,
      "ratioSource": "server PIT risk context",
      "reentryEligible": true,
      "terminal": true
    },
    {
      "condition": {
        "gapBelowFloorAtrGte": 1.5
      },
      "documentedRange": [
        0.3,
        0.5
      ],
      "mayCrossStrategicCore": false,
      "policyId": "ALG-SELL-002",
      "priority": 800,
      "reentryEligible": true,
      "requestedSellRatioOfLot": 0.4,
      "terminal": false
    },
    {
      "condition": {
        "consecutiveCloseBreachesGte": 2
      },
      "documentedRange": [
        0.15,
        0.25
      ],
      "mayCrossStrategicCore": false,
      "policyId": "ALG-SELL-003",
      "priority": 700,
      "reentryEligible": true,
      "requestedSellRatioOfLot": 0.2,
      "terminal": false
    },
    {
      "condition": "approved concentration/liquidity excess ratio > 0",
      "mayCrossStrategicCore": false,
      "policyId": "ALG-SELL-004",
      "priority": 600,
      "ratioSource": "server PIT portfolio risk context",
      "reentryEligible": true,
      "terminal": false
    },
    {
      "condition": "opportunity edge lower bound > 0 and requested ratio > 0",
      "mayCrossStrategicCore": false,
      "policyId": "ALG-SELL-005",
      "priority": 500,
      "reentryEligible": true,
      "requestedRange": [
        0.1,
        0.25
      ],
      "terminal": false
    }
  ],
  "policyTraceSchemaVersion": 2,
  "status": "RESEARCH_CANDIDATE_NOT_PRODUCTION"
}
$$::jsonb, 'PROPOSED')
on conflict (contract_version) do nothing;

drop trigger if exists policy_contract_definition_immutable on signal_engine.policy_contract_definition;
create trigger policy_contract_definition_immutable
before update or delete on signal_engine.policy_contract_definition
for each row execute function signal_engine.prevent_immutable_change();
