Skip to content

feat(alerts): give collector notices the same dedupe and push that errors get (CashPilot-vb78) - #326

Merged
GeiserX merged 1 commit into
mainfrom
fix/notice-dedupe-push
Aug 10, 2026
Merged

feat(alerts): give collector notices the same dedupe and push that errors get (CashPilot-vb78)#326
GeiserX merged 1 commit into
mainfrom
fix/notice-dedupe-push

Conversation

@GeiserX

@GeiserX GeiserX commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Why

A notice — a caveat on a successful reading, like #321's "satellites cannot reach this node" — only ever reached the notification bell, which is read by whoever happens to open the UI. "Nobody looked for days" is the incident that class of warning comes from. Errors already push out-of-band once per window; notices got nothing.

What

  • Notices now pass through database.record_alert("notice", ...) — the same once-per-window dedupe errors use — before notify.send(kind="notice"). A node broken for a week does not notify every hour.
  • A warning-free success clears the stored notice row, so the next warning pushes again instead of being deduped into silence forever (mirrors the error-recovery path).
  • The bell keeps its existing per-cycle behaviour; only the out-of-band push is gated.

Testing

Four tests including two negative controls: a deduped notice must stay silent, and a success with no prior notice must clear nothing. Full suite on the rebased head: see below. ruff clean.

Summary by CodeRabbit

  • New Features

    • Collection runs now distinguish active platform notices from collector failures.
    • Successful runs with warnings record and send notifications through the existing notification channel.
    • Repeated identical notices are deduplicated to prevent duplicate alerts.
  • Bug Fixes

    • Warning-free successful runs clear previously stored notices.
    • After a notice is cleared, future warnings can trigger notifications again.

…rors get (CashPilot-vb78)

A notice — a caveat on a SUCCESSFUL reading, like 'satellites cannot
reach this node' — only ever reached the bell, which is read by whoever
happens to open the UI. 'Nobody looked for days' is the incident that
class of warning comes from, while errors already pushed out-of-band on
first occurrence.

Notices now go through database.record_alert('notice', ...) with the
same once-per-window dedupe before notify.send, and a warning-free
success clears the stored row so the NEXT warning pushes again instead
of being deduped into silence forever. The bell keeps its per-cycle
behaviour; only the out-of-band push is gated.

Tests: fresh notice pushes once with kind=notice; a deduped notice stays
silent (control); a warning-free success clears the stored notice; a
success with no prior notice clears nothing (control).
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Collection runs now track platform notices separately from collector failures. New warnings create deduplicated "notice" alerts and send notifications. Warning-free successes clear stored notices. Tests cover notification, deduplication, and recovery behavior.

Changes

Platform Notice Tracking

Layer / File(s) Summary
Notice alert lifecycle
app/main.py
Collection runs track prior notice alerts separately. Successful collector warnings persist deduplicated "notice" alerts and send notifications. Warning-free successes clear the stored notice.
Notice handling validation
tests/test_beads_batch_25.py
Async tests verify new notice delivery, deduplication, and clearing behavior after warning-free success.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: collector notices now use the same deduplication and push flow as errors.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/notice-dedupe-push

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.51%. Comparing base (c065044) to head (b7ddaa5).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #326   +/-   ##
=======================================
  Coverage   95.50%   95.51%           
=======================================
  Files          51       51           
  Lines        6878     6883    +5     
=======================================
+ Hits         6569     6574    +5     
  Misses        309      309           
Files with missing lines Coverage Δ
app/main.py 97.67% <100.00%> (+<0.01%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@GeiserX

GeiserX commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/main.py`:
- Around line 643-645: Filter previously_alerting in app/main.py lines 643-645
to include only entries whose kind is "collector", while keeping
previously_noticing limited to "notice" entries. In tests/test_beads_batch_25.py
lines 243-248, replace the broad assert_any_await check with an exact assertion
that only clear_alerts("notice", "storj") is called.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e7e41746-0261-4b2a-b6ca-40f1475d9860

📥 Commits

Reviewing files that changed from the base of the PR and between 38f9be5 and b7ddaa5.

📒 Files selected for processing (2)
  • app/main.py
  • tests/test_beads_batch_25.py

Comment thread app/main.py
@GeiserX
GeiserX merged commit 42d373a into main Aug 10, 2026
8 checks passed
@GeiserX
GeiserX deleted the fix/notice-dedupe-push branch August 10, 2026 19:17
GeiserX added a commit that referenced this pull request Aug 13, 2026
* feat(alerts): a worker going offline now notifies, not just logs

The 42-hour incident had two halves. #330 fixed the worker's half —
docker ps now tells the truth. This is the UI's half: it KNEW within
3 minutes (it wrote 'offline' to its own database) and told nobody.
The only witness was the fleet page, and the whole incident happened
because nobody was looking at it.

The online->offline transition now records a durable alert and pushes
out-of-band with the same once-per-window dedupe collectors get, the
bell shows every currently offline worker (re-derived from the workers
table on each rebuild, so the hourly wholesale replacement of the list
cannot silently un-report a dead machine, and it survives a UI
restart), and the recovery heartbeat clears the stored alert so the
NEXT outage notifies again instead of being deduped into silence.

Also restores 'notice' bell entries across UI restarts: #326 made
notices durable, but the warm filter dropped them, so a recovery
inside the gap skipped clear_alerts and the stale row swallowed the
next genuine warning within the cooldown window.

The pre-heartbeat state read is best-effort by design: it failing must
never reject a heartbeat — the upsert one line below is the loud path.

10 new tests incl. negative controls; the transition mechanism is
mutation-verified (disabling the alert path turns the test red).
Full suite 4674 passed, coverage 95.64%.

* fix(alerts): worker alert identity, atomic transition, retryable lifecycle

Review round on #331, all four findings verified then fixed:

- Identity is client_id, not the display name: workers.name is the
  container hostname — cosmetic, mutable across recreates, shareable by
  two hosts — so keying alerts on it let one worker suppress or clear
  another's. Alert rows and pushes key on client_id; the name travels
  in the message/title; bell entries carry both; warm restoration
  resolves stored ids to current display names (a deleted worker keeps
  the raw id — still actionable, never wrong).
- The offline transition is atomic with its evidence: the sweep marks
  offline via a conditional UPDATE on the exact heartbeat it decided
  from, so a recovery heartbeat landing in the read-write gap wins
  instead of being alerted offline at its exact moment of coming back.
- The lifecycle is retryable end to end: a still-offline worker
  re-attempts the durable alert every sweep (the record_alert cooldown
  makes the successful case a no-op, so a failed insert or push is
  retried in 2 minutes instead of being lost to the already-offline
  state); the heartbeat route's recovery clear is best-effort (a DB
  hiccup must never fail a HEARTBEAT — and on failure the bell entry
  deliberately stays so memory and disk cannot diverge); the sweep
  reconciles any lingering alert it finds on an online worker.
- Tests read the fleet key from the environment instead of embedding a
  second copy of the literal.

15 tests in the lifecycle file (+ race, retry, reconciliation,
failed-clear-never-fails-heartbeat cases); two pre-existing sweep tests
updated to the conditional-mark call shape they now exercise. Full
suite 4679 passed, coverage 95.55%.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant