Skip to content

feat(listener): expire scheduled notifications past a configured deadline - #893

Merged
Abd-Standard merged 12 commits into
Core-Foundry:mainfrom
najeebullahii:feat/840-notification-expiration
Oct 3, 2026
Merged

Abd-Standard merged 12 commits into
Core-Foundry:mainfrom
najeebullahii:feat/840-notification-expiration

Conversation

@najeebullahii

Copy link
Copy Markdown
Contributor

Closes #840.

Changes:

  • expires_at (nullable DATETIME) on scheduled_notifications: explicit per-notification expiration normalized at the API boundary (ISO-8601 or epoch), else NOTIFICATION_DEFAULT_TTL_SECONDS (absent/0 = never expire), else NULL; carried into archived rows.
  • EXPIRED terminal status: persisted with an execution-log record, added to the code enum and every terminal-state enumeration (archive queries, active cleanup, repository retention), never re-picked by scheduler or retry loop.
  • Expiry evaluated BEFORE any provider call in both the scheduled pickup path and the retry dispatcher; a notification expiring while queued transitions to EXPIRED with zero provider calls.
  • Migration 005 rebuilds scheduled_notifications and the archive table in one idempotent, fail-closed transaction: adds the column, extends both status CHECKs with EXPIRED, preserves every 004 constraint/index/trigger; mirrored in schema.sql and archive-schema.sql for fresh installs; legacy rows survive with NULL expiration.
  • Operator docs: expiration precedence, terminal semantics, queries.

Testing: API validation tests for explicit/derived/absent expiry; scheduler and retry tests asserting zero provider calls for expired rows and normal delivery for non-expired/NULL rows (distinct recipients per batch); EXPIRED terminal + archived; migration 005 test asserting legacy survival, extended CHECK accept/reject, 004 constraints intact, idempotent repeat, fail-closed abort on bad legacy data.

Stacking note: based on #892 (migration 004) because 005 extends 004's CHECKs and must preserve its rebuilds (including the archive table); stacking makes merge order irrelevant — when #892 merges, this diff collapses to the 005 delta.

Pre-existing baseline (untouched): full npm test red on main (~55 failing suites: request-id.ts syntax error, Stellar XDR test setup, config secret validation); npm run lint fails on index.ts/request-id.ts/security-headers.ts/discord-notification.ts; one scheduler assertion (retryCount expected 3, actual 2) is pre-existing on main and left as-is; lockfile out of sync with package.json (this PR modifies neither).

…Closes Core-Foundry#844.

CHECK constraints on all closed status/state enums (scheduled notifications,
execution attempts, processed events, idempotency, backpressure, rate-limit
client types, notification archive); FK actions preserved deliberately
(CASCADE for cleanup children, RESTRICT for template audit); PRAGMA
foreign_keys verified at connect and in the migration runner.

Migration 004 rebuilds tables in a single idempotent transaction with
preflight audits that abort fail-closed on invalid legacy rows; valid legacy
data passes through byte-identical (legacy-shape test asserts survival,
clean foreign_key_check, and orphan/status/duplicate rejection). Constraints
mirrored in schema.sql and archive-schema.sql for fresh-install parity.

Also fixes a latent migration-runner defect: callback-based sqlite3 run/all
were awaited as promises, undermining transaction/rollback guarantees.

Deliberately NOT added: UNIQUE(notification_id, attempt) — the dead-letter
retry path resets retry_count, so attempts legitimately repeat.
…line. Closes Core-Foundry#840. Adds expires_at (explicit override > NOTIFICATION_DEFAULT_TTL_SECONDS > never), EXPIRED terminal status persisted and enforced via extended CHECKs in migration 005 (scheduled + archive tables, preserving 004 constraints), expiry gates before provider dispatch in both scheduled and retry loops, terminal/archive/cleanup handling, API-boundary normalization of ISO/epoch expiry, focused tests, operator docs. Stacked on Core-Foundry#892 so merge order is irrelevant.
@drips-wave

drips-wave Bot commented Sep 29, 2026

Copy link
Copy Markdown

@najeebullahii Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

…schema refactor, dead-letter isolation + expiration wiring)
…cations rebuild. The bootstrap schema gained deduplication_key via merged 003-notification-deduplication-key after 005 was written; the rebuild omitted it, which would have dropped the column on existing DBs. Column now copied verbatim into the _v005 CREATE and the INSERT/SELECT lists, with a legacy-survival assertion and a bootstrap-vs-rebuild column-parity test to catch future drift. Also fixed a pre-existing missing parenthesis in getRows callback that blocked Prettier formatting.
…ough 004 rebuild (same drift class as 005 fix 93e25eb) with legacy-survival + bootstrap parity assertions; fix latent Database.connect bootstrap hang (handle assigned after first dereference) exposed by merge with main
…ource, not final bootstrap. Migration 005 owns expires_at, so the intermediate post-004 state legitimately lacks it; asserting against bootstrap would go red on main once Core-Foundry#893 merges. 004's invariant is exact preservation of its source column set.
@najeebullahii
najeebullahii force-pushed the feat/840-notification-expiration branch from 18767a0 to 8cca369 Compare October 3, 2026 10:39
…PC/circuit-breaker config, retry rework; DEAD_LETTERED added to 004 CHECKs for bootstrap/rebuild parity)
@najeebullahii
najeebullahii force-pushed the feat/840-notification-expiration branch from 9ad19f8 to 51d6d7f Compare October 3, 2026 11:02
@najeebullahii

Copy link
Copy Markdown
Contributor Author

Union scope note (push 51d6d7f): main has since merged a DEAD_LETTERED terminal status, RPC-fallback/circuit-breaker config, and a retry rework. The merge unions restore main's config loaders and de-duplicate the retry path; migration CHECK enums include DEAD_LETTERED wherever the repository writes it (bootstrap/rebuild parity, with explicit test coverage), while EXPIRED remains owned by 005. Focused suites green (004: 4/4, 005: 3/3, config: 58/58, scheduler+api+retry+dead-letter: 136/136).

@Abd-Standard
Abd-Standard merged commit 053c692 into Core-Foundry:main Oct 3, 2026
1 of 5 checks passed
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.

Add Notification Expiration

2 participants