diff --git a/internal/db/queries/operations.sql b/internal/db/queries/operations.sql index 0742d55..03a261a 100644 --- a/internal/db/queries/operations.sql +++ b/internal/db/queries/operations.sql @@ -63,7 +63,9 @@ SELECT cd.entity_id, cd.target_id, cd.target_type, cd.kind, cd.config, e.slug AS entity_slug FROM check_defs cd JOIN entities e ON e.id = cd.entity_id +LEFT JOIN entities tgt ON tgt.id = cd.target_id WHERE cd.enabled = true + AND (tgt.id IS NULL OR tgt.state NOT IN ('deprecated', 'destroyed')) AND (cd.last_run_at IS NULL OR cd.last_run_at <= now() - make_interval(secs => cd.interval_s)); diff --git a/internal/db/sqlcgen/operations.sql.go b/internal/db/sqlcgen/operations.sql.go index 4424565..637d7b2 100644 --- a/internal/db/sqlcgen/operations.sql.go +++ b/internal/db/sqlcgen/operations.sql.go @@ -696,7 +696,9 @@ SELECT cd.entity_id, cd.target_id, cd.target_type, cd.kind, cd.config, e.slug AS entity_slug FROM check_defs cd JOIN entities e ON e.id = cd.entity_id +LEFT JOIN entities tgt ON tgt.id = cd.target_id WHERE cd.enabled = true + AND (tgt.id IS NULL OR tgt.state NOT IN ('deprecated', 'destroyed')) AND (cd.last_run_at IS NULL OR cd.last_run_at <= now() - make_interval(secs => cd.interval_s)) ` diff --git a/internal/scheduler/coverage.go b/internal/scheduler/coverage.go index ae07051..be53fbf 100644 --- a/internal/scheduler/coverage.go +++ b/internal/scheduler/coverage.go @@ -85,7 +85,16 @@ func coverageSweep(ctx context.Context, pool *db.Pool) { case !mon.Declared: undeclared++ case mon.None(): - // Explicitly unmonitorable. Nothing to say. + // Explicitly unmonitorable. Nothing to raise — but a type that + // USED to declare monitoring (e.g. dns-zone, [dns]→none) may have + // open `unmonitored` signals from before the change. They are no + // longer a gap, so close them; otherwise they linger forever, + // because resolveCoverageSignal only runs from the hasCheck path + // and a None() entity never gains a check. + if resolveCoverageSignal(ctx, pool, e.id) { + resolved++ + slog.Info("scheduler: type now unmonitorable, resolving stale signal", "entity", e.slug) + } case e.hasCheck: if resolveCoverageSignal(ctx, pool, e.id) { resolved++