Skip to content

Conversation

@jdoucett
Copy link
Contributor

@jdoucett Jarrod S Doucette (jdoucett) commented Jul 11, 2026

Why

archive.py had zero Teams/webhook code — every [FAILED] / [STALE] / [EXCLUSION-ABORT] went out only as email (send_alert_email() → smtplib → smtp.purdue.edu), which gets tuned out. This is the "backup didn't happen" T1 tier of the settled notification-triage strategy and the highest-value gap: what actually stops must-see failure signals being missed.

What

A small stdlib-only notify_bell() that POSTs a Teams Adaptive Card beside the durable audit email. The email stays as the record; the bell is the attention signal. Card title is prefixed ACT NOW:.

Delivered as a 1:1 Teams DM, not a channel @mention (routing decision 2026-07-11; mirrors merged florasense-tools PR #106 --dm mode). The card carries no <at> / no msteams.entities: a DM already notifies, and a Power-Automate flow-bot chat post renders <at> entities as literal text. DM-vs-channel is decided by the Power Automate flow behind EARLY_TEAMS_WEBHOOK, not the payload — flipping to a DM is a Power-Automate web-UI step done separately from this code.

  • notify_bell() / build_teams_card()urllib.request only, imports inside the body (engine invariant chore(reclaim): hold AAPF landing-zone raw until promote loop verified #1). Card shape reused from florasense-tools/fortress/reship_watch_alert.py.
  • Env-gated + DEFAULT-OFF on EARLY_TEAMS_WEBHOOK — silent no-op (no POST) until wired into the run env on Negishi. No mention env vars needed or read.
  • Never breaks a run — only HTTP {200,202} = delivered; any error (absent/failed webhook, network) degrades to email-only with a logged reason and never raises. The webhook is a secret (sig) and is never printed/logged.
  • Fired beside every LOCAL send_alert_email() failure site. The size-routing completion email ([SUCCESS]/[RESHIP-WATCH]) is deliberately not belled.
  • Inline twin in send_to_fortress (self-contained, closes over write_log only, imports inside) for the exclusion companion-guard abort — the one alert that continues the run (so it never reaches the local Step-2 wrapper) and can fire on a compute node under --globus. All other verification [FAILED]s raise → the Step-2 wrapper alerts and bells.

Tests

tests/test_notify_bell.py14 tests (POST mocked, os.environ fully controlled):

  • env-gated no-op (no webhook → no POST), DM-clean payload (no <at>/no msteams.entities, legacy mention env vars ignored), ACT NOW: prefix + idempotency, HTTP 200/202 success, never-raises on HTTPError / URLError / generic exception + non-2xx, and webhook-never-printed secret safety.

python3 -m unittest discover -s tests289 passed.

Deploy note (default-off)

Does nothing until EARLY_TEAMS_WEBHOOK is exported into the run environment on Negishi (archive_local.sbatch / cron), and the Power Automate flow behind it is set to post a DM. Until then notify_bell() is a silent no-op and email behavior is unchanged.

Scope guards

  • Does not touch the reship-watch alert (already correct).
  • No --dm / --task modes — triage design keeps everything on the one alert surface.

🤖 Generated with Claude Code

archive.py had zero Teams/webhook code — every [FAILED]/[STALE]/[EXCLUSION-ABORT]
went out only as email, which gets tuned out. Add a stdlib-only notify_bell() that
POSTs a Teams Adaptive Card beside the durable send_alert_email() audit copy (title
prefixed "ACT NOW:") — the "backup didn't happen" T1 tier of the settled
notification-triage strategy.

Delivered as a 1:1 Teams DM, NOT a channel @mention (Jarrod's routing decision,
2026-07-11; mirrors florasense-tools PR #106's --dm mode): the card carries no <at> /
no msteams.entities, because a DM already notifies and a Power-Automate flow-bot chat
post renders <at> as literal text. DM-vs-channel is decided by the flow behind
EARLY_TEAMS_WEBHOOK, not the payload — the switch is a Power-Automate web-UI step done
separately from this code.

- notify_bell()/build_teams_card(): urllib.request only, imports inside the body
  (invariant #1). Env-gated + DEFAULT-OFF on EARLY_TEAMS_WEBHOOK (silent no-op until
  wired on Negishi); no mention env vars needed/read. Only HTTP {200,202} = delivered;
  NEVER raises (degrades to email-only + logged reason). Webhook is a secret (sig) —
  never printed/logged. Card shape reused from florasense-tools reship_watch_alert.py.
- Fired beside every LOCAL send_alert_email() failure site; the size-routing
  completion email ([SUCCESS]/[RESHIP-WATCH]) is deliberately NOT belled.
- send_to_fortress carries a self-contained inline twin (closes over write_log only,
  imports inside) for the exclusion companion-guard abort — the one alert that
  continues the run (so it never reaches the local Step-2 wrapper) and can fire on a
  compute node under --globus. Other verification [FAILED]s raise → wrapper alerts+bells.
- tests/test_notify_bell.py (14 tests): env-gated no-op, DM-clean payload (no @mention,
  mention env vars ignored), ACT NOW prefix (+idempotency), 200/202 success,
  never-raises on HTTPError/URLError/generic + non-2xx, webhook-never-printed secret
  safety. Full suite 289 green.
- CLAUDE.md: new "Failure notifications (Teams DM bell)" section.

Does not touch the reship-watch alert; no --dm/--task modes (one alert surface).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jdoucett Jarrod S Doucette (jdoucett) changed the title feat(notify): Teams Activity-feed bell beside failure emails (T1) feat(notify): Teams DM bell beside failure emails (T1) Jul 11, 2026
@jdoucett Jarrod S Doucette (jdoucett) deleted the feat/notify-bell-teams branch July 11, 2026 15:26
Sign in to join this conversation on GitHub.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant