fix(switch-db): close compat_speed_guard drift gap (transceiver-side speed updates)

126 only guards writes to compatibility; a later transceivers.speed_gbps update
(e.g. daily enrich-modulation-fec.sh self-heal) never re-validated existing
compatible spec_match rows. Found live 2026-07-12: 189 rows had drifted
physically-impossible again. One-time remediation (189 rows -> incompatible,
backed up to compat_speed_drift_backup_20260712) + AFTER UPDATE OF speed_gbps
trigger on transceivers closes the gap for good. check_switch_quality() passes.
This commit is contained in:
root 2026-07-12 16:14:38 +00:00
parent 7ec26c04ac
commit fcb1220586

View File

@ -0,0 +1,83 @@
-- sql/138-transceiver-speed-drift-guard.sql
-- Closes a gap in the sql/125+126 "no physically-impossible compatibility rows"
-- invariant: compat_speed_guard() (126) only fires on INSERT/UPDATE of the
-- `compatibility` table itself. It never re-validates existing 'compatible'
-- spec_match rows when a TRANSCEIVER's speed_gbps changes later (e.g. the daily
-- enrich-modulation-fec.sh self-heal, sql/135, recovering speed from part-number
-- tokens for previously NULL/junk rows). Found live 2026-07-12: 189 compat rows
-- had gone physically-impossible because their transceiver's speed_gbps was
-- updated (all sampled rows dated today, i.e. by the 03:30 cron) after the
-- compat row was already written as 'compatible'.
--
-- Fix mirrors 126 exactly, just triggered from the other table: AFTER UPDATE OF
-- speed_gbps on transceivers, re-validate every 'compatible' spec_match
-- compatibility row referencing that transceiver against the same evidence-based
-- ceiling (v_switch_effective_max) used by v_compat_speed_errors. Curated rows
-- (vendor_matrix/vendor_compat/tested/datasheet) are never touched. Idempotent,
-- safe to re-run.
-- Backup of affected rows before remediation (audit trail, matches
-- tip_speed_gbps_rollback precedent from sql/136).
create table if not exists compat_speed_drift_backup_20260712 as
with em as materialized (
select switch_id, effective_max_gbps from v_switch_effective_max)
select c.*, e.effective_max_gbps, t.speed_gbps as tx_speed_gbps, now() as backed_up_at
from compatibility c
join em e on e.switch_id = c.switch_id
join transceivers t on t.id = c.transceiver_id
where c.status = 'compatible'
and c.verification_method = 'spec_match'
and e.effective_max_gbps > 0
and t.speed_gbps is not null
and t.speed_gbps > e.effective_max_gbps;
-- One-time remediation: flip the rows found live today (same logic as 125's
-- Remediation B, using the full evidence-based ceiling incl. curated bump).
with em as materialized (
select switch_id, effective_max_gbps from v_switch_effective_max)
update compatibility c
set status = 'incompatible',
notes = case when c.notes is null or c.notes = ''
then 'auto(138): transceiver speed exceeds switch max port speed (post-hoc drift)'
else c.notes || ' | auto(138): transceiver speed exceeds switch max port speed (post-hoc drift)' end
from em e, transceivers t
where e.switch_id = c.switch_id
and t.id = c.transceiver_id
and c.status = 'compatible'
and c.verification_method = 'spec_match'
and e.effective_max_gbps > 0
and t.speed_gbps is not null
and t.speed_gbps > e.effective_max_gbps;
-- Durable fix: re-validate on transceiver speed change.
create or replace function compat_speed_guard_on_transceiver_change() returns trigger
language plpgsql as $$
begin
if NEW.speed_gbps is distinct from OLD.speed_gbps and NEW.speed_gbps is not null then
update compatibility c
set status = 'incompatible',
notes = case when c.notes is null or c.notes = ''
then 'auto(138): transceiver speed exceeds switch max port speed (post-hoc drift)'
else c.notes || ' | auto(138): transceiver speed exceeds switch max port speed (post-hoc drift)' end
from v_switch_effective_max e
where e.switch_id = c.switch_id
and c.transceiver_id = NEW.id
and c.status = 'compatible'
and c.verification_method = 'spec_match'
and e.effective_max_gbps > 0
and NEW.speed_gbps > e.effective_max_gbps;
end if;
return NEW;
end $$;
comment on function compat_speed_guard_on_transceiver_change() is
'AFTER UPDATE OF speed_gbps on transceivers: re-validates existing compatible spec_match rows for this transceiver against the evidence-based switch ceiling, closing the drift gap left by compat_speed_guard() (126), which only fires on compatibility writes. Never touches curated rows.';
drop trigger if exists trg_compat_speed_guard_on_transceiver_change on transceivers;
create trigger trg_compat_speed_guard_on_transceiver_change
after update of speed_gbps on transceivers
for each row execute function compat_speed_guard_on_transceiver_change();
insert into _migrations (filename, applied_at)
select '138-transceiver-speed-drift-guard.sql', now()
where not exists (select 1 from _migrations where filename = '138-transceiver-speed-drift-guard.sql');