Files
worldmonitor/scripts/lib
Elie Habib e878baec52 fix(digest): DIGEST_ONLY_USER self-expiring (mandatory until= suffix, 48h cap) (#3271)
* fix(digest): DIGEST_ONLY_USER self-expiring (mandatory until= suffix, 48h cap)

Review finding on PR #3255: DIGEST_ONLY_USER was a sticky production
footgun. If an operator forgot to unset after a one-off validation,
the cron silently filtered every other user out indefinitely while
still completing normally (exit 0) — prolonged partial outage with
"green" runs.

Fix: mandatory `|until=<ISO8601>` suffix within 48h of now. Otherwise
the flag is IGNORED with a loud warn, fan-out proceeds normally.
Active filter emits a structured console.warn at run start listing
expiry + remaining minutes.

Valid:
  DIGEST_ONLY_USER=user_xxx|until=2026-04-22T18:00Z

Rejected (→ loud warn, normal fan-out):
- Legacy bare `user_xxx` (missing required suffix)
- Unparseable ISO
- Expiry > 48h (forever-test mistake)
- Expiry in past (auto-disable)

Parser extracted to `scripts/lib/digest-only-user.mjs` (testable
without importing seed-digest-notifications.mjs which has no isMain
guard).

Tests: 17 cases covering unset / reject / active branches, ISO
variants, boundaries, and the 48h cap. 6066 total pass. typecheck × 2
clean.

Breaking change on the flag's format, but it shipped 2h before this
finding with no prod usage — tightening now is cheaper than after
an incident.

* chore(digest): address /ce:review P2s on DIGEST_ONLY_USER parser

Two style fixes flagged by Greptile on PR #3271:

1. Misleading multi-pipe error message.
   `user_xxx|until=<iso>|extra` returned "missing mandatory suffix",
   which points the operator toward adding a suffix that is already
   present (confused operator might try `user_xxx|until=...|until=...`).
   Now distinguishes parts.length===1 ("missing suffix") from >2
   ("expected exactly one '|' separator, got N").

2. Date.parse is lenient — accepts RFC 2822, locale strings, "April 22".
   The documented contract is strict ISO 8601; the 48h cap catches
   accidental-valid dates but the documentation lied. Added a regex
   guard up-front that enforces the ISO 8601 shape
   (YYYY-MM-DD optionally followed by time + TZ). Rejects the 6
   Date-parseable-but-not-ISO fixtures before Date.parse runs.

Both regressions pinned in tests/digest-only-user.test.mjs (18 pass,
was 17). typecheck × 2 clean.
2026-04-21 22:36:30 +04:00
..