fix(symlinkguard): deliver alerts beside the scan, not inside it (#164) #180

Merged
mathias merged 3 commits from w3/164-symlinkguard-delivery into main 2026-10-05 14:48:16 +00:00
3 Commits
Author SHA1 Message Date
mathiasandClaude Opus 5.5 cadc0cc02f fix(symlinkguard): test the WithoutCancel final flush; log what shutdown drops
CI / Mirror to GitHub (pull_request) Skipped
CI / Lint / Test / Vet (pull_request) Successful in 1m5s
PR #180 round-2 review (comments 15467, 15487):
- toggleNotifier now fails on a done ctx, like audit.NtfyNotifier, so
  replacing context.WithoutCancel(ctx) with ctx in the final flush turns
  TestWatchFinalFlushSendsAQueuedAlertOnACtxThatOutlivesTheCancel (renamed
  from ...FlushesTheQueueOnceMoreAtShutdown) red.
- After the final flush, any undelivered or suppressed alerts are logged as
  one error with both counts, never silently.
- The !ok first-receive branch now swaps overflow into suppressed.
- Deterministic deliver() tests for the drain-on-close path and the
  closed-and-empty overflow case.
- Watch doc states the shutdown path only runs for a cancelling caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:27:54 +02:00
mathiasandClaude Opus 5.5 52d4bfeeab fix(symlinkguard): flush on handoff and at shutdown; test the handoff
CI / Lint / Test / Vet (pull_request) Successful in 56s
CI / Mirror to GitHub (pull_request) Skipped
PR #180 review (BLOCK) found that moving delivery out of the scan left
two problems:
- An alert waited for deliver's own tick, about one interval. It is
  now flushed as soon as it is handed over, and on each tick after
  that while it is still pending.
- On ctx end deliver returned without draining, so an alert handed over
  just before shutdown was lost even with a healthy ntfy. Watch now
  closes the handoff channel, and deliver drains it and flushes once
  more (bounded by shutdownFlushTimeout) before Watch returns.

The review also showed the tests didn't cover the handoff. The new
tests are each flip-tested red:
- the lifetime test uses a notifier slower than the interval, so a
  blocking handoff fails it
- the overflow-accounting test uses a slow notifier
- a join test checks that Notify is no longer running when Watch
  returns
- a shutdown-flush test, and a prompt-delivery test

Refs #164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:45:15 +02:00
mathiasandClaude Opus 5.5 b7a10485af fix(symlinkguard): deliver alerts beside the scan, not inside it
CI / Lint / Test / Vet (pull_request) Successful in 54s
CI / Mirror to GitHub (pull_request) Skipped
Watch flushed the alert queue in the scan goroutine. With a hung ntfy
(accepts, never answers until the 10 s timeout) and a full queue, one
flush took ~6 notify timeouts, so a planted symlink stayed readable for
several intervals instead of at most one.

Delivery now runs in its own goroutine that owns the queue. The scan
hands alerts over on a bounded channel without blocking; an alert that
does not fit is counted in the never-dropped summary, so the
"delivered or counted" property holds.

Repro test (interval 50 ms, notifier latency 40 ms, full queue): worst
link lifetime 243 ms before, under 2 intervals after.

Closes #164

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:34:27 +02:00