feat(switch-db): close third drift-gap direction, switch-side changes

Preventive companion to 126 (compatibility writes) + 138 (transceiver speed
changes): the same physically-impossible-compat invariant also depends on
switches.max_speed_gbps/ports_config via v_switch_effective_max, but nothing
guarded THAT write direction. Audit 2026-07-17: currently a no-op live
(nothing updates those columns today outside the one-time sql/125 backfill),
but the documented open item -- 110 switches need ports_config datasheet
enrichment -- is exactly the future job that would trigger this gap the same
way the daily modulation/FEC cron triggered 138. Closing it now. Verified with
a synthetic in-transaction test (1,250 rows correctly flagged, then rolled
back).
This commit is contained in:
root 2026-07-17 20:21:09 +00:00
parent 668911756c
commit bc53c5ebf3

View File

@ -0,0 +1,70 @@
-- 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');