-- sql/141-switch-speed-drift-guard.sql
-- Preventive close of the THIRD direction of the same drift-gap class fixed in
-- sql/126 (compat_speed_guard, guards compatibility writes) and sql/138
-- (compat_speed_guard_on_transceiver_change, guards transceivers.speed_gbps
-- changes). The "no physically-impossible compatibility rows" invariant is
-- derived from THREE tables' data: compatibility.status, transceivers.speed_gbps,
-- and switches.max_speed_gbps/ports_config (via v_switch_effective_max). Only
-- two of those three write paths were guarded.
--
-- Audit 2026-07-17 found switches.max_speed_gbps/ports_config are currently
-- write-once (set at scrape/seed time, sql/125's one-time backfill) — no live
-- job updates them today, so this direction is NOT actively exploited yet. But
-- the documented open item "110 switches with understated ports_config need
-- datasheet enrichment" ([[tip-switch-database-audit-2026-07-06]]) describes
-- exactly the future job that WOULD trigger this gap: correcting a switch's
-- max_speed_gbps or ports_config downward after compat rows already exist would
-- silently leave now-impossible rows marked 'compatible', same failure mode as
-- sql/138, just from the other table. Closing it now, before that automation
-- ships, rather than waiting for the next backlog surprise.
--
-- Same "no false positives" design as 126/138: only algorithmic (non-curated)
-- rows are ever forced to incompatible; curated rows are trusted as-is.

create or replace function compat_speed_guard_on_switch_change() returns trigger
language plpgsql as $$
declare
  new_ceiling numeric;
begin
  if NEW.max_speed_gbps is distinct from OLD.max_speed_gbps
     or NEW.ports_config is distinct from OLD.ports_config then
    select greatest(
             coalesce(NEW.max_speed_gbps, 0),
             coalesce(fn_ports_max_gbps(NEW.ports_config), 0),
             coalesce((select max(t.speed_gbps)
                         from compatibility c join transceivers t on t.id = c.transceiver_id
                        where c.switch_id = NEW.id and c.status = 'compatible'
                          and c.verification_method in ('vendor_matrix','vendor_compat')
                          and t.speed_gbps > 0), 0)
           )
      into new_ceiling;

    if new_ceiling > 0 then
      update compatibility c
         set status = 'incompatible',
             notes  = case when c.notes is null or c.notes = ''
                           then 'auto(141): transceiver speed exceeds switch max port speed (post-hoc drift)'
                           else c.notes || ' | auto(141): transceiver speed exceeds switch max port speed (post-hoc drift)' end
        from transceivers t
       where t.id = c.transceiver_id
         and c.switch_id = NEW.id
         and c.status = 'compatible'
         and c.verification_method = 'spec_match'
         and t.speed_gbps is not null
         and t.speed_gbps > new_ceiling;
    end if;
  end if;
  return NEW;
end $$;

comment on function compat_speed_guard_on_switch_change() is
  'AFTER UPDATE OF max_speed_gbps/ports_config on switches: re-validates existing compatible spec_match compatibility rows for this switch against the (re-derived) evidence-based ceiling. Third leg of the compat_speed_guard family (126=compatibility writes, 138=transceiver speed changes, this=switch capacity changes). Never touches curated rows. Currently a no-op in practice -- nothing updates these columns live yet -- this is preventive, closing the gap before the planned switch datasheet-enrichment automation ships.';

drop trigger if exists trg_compat_speed_guard_on_switch_change on switches;
create trigger trg_compat_speed_guard_on_switch_change
  after update of max_speed_gbps, ports_config on switches
  for each row execute function compat_speed_guard_on_switch_change();

insert into _migrations (filename, applied_at)
select '141-switch-speed-drift-guard.sql', now()
where not exists (select 1 from _migrations where filename = '141-switch-speed-drift-guard.sql');
