Skip to content

feat: persistent deduplication keys for scheduled notifications - #899

Open
Vvictor-commits wants to merge 2 commits into
Core-Foundry:mainfrom
Vvictor-commits:feature/notification-deduplication-key
Open

Vvictor-commits wants to merge 2 commits into
Core-Foundry:mainfrom
Vvictor-commits:feature/notification-deduplication-key

Conversation

@Vvictor-commits

@Vvictor-commits Vvictor-commits commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #838


Summary

Introduces explicit deduplication keys for notifications so the same logical notification is never delivered more than once, even under concurrent requests or process restarts.

Changes

Core feature (4 files)

listener/src/migrations/003-notification-deduplication-key.ts (new)
Adds deduplication_key TEXT to scheduled_notifications via ALTER TABLE, with a UNIQUE partial index scoped to non-null values. The partial index means null keys (notifications without dedup) are unaffected.

listener/src/database/schema.sql
Column + index added to the CREATE TABLE statement so fresh installs get the same schema without running the migration.

listener/src/types/scheduled-notification.ts

  • deduplicationKey?: string | null added to ScheduledNotification
  • deduplicationKey?: string added to CreateScheduledNotificationInput
  • deduplication_key: string | null added to ScheduledNotificationRow

listener/src/services/scheduled-notification-repository.ts

  • create() includes deduplication_key in the INSERT
  • UNIQUE constraint violation is caught; the existing row's id is returned instead of throwing — safe under concurrent inserts (two racing requests both get the same id back)
  • rowToNotification() maps deduplication_key → deduplicationKey

Acceptance criteria

Criterion How it's satisfied
Deduplication keys are persisted SQLite column with UNIQUE partial index
Duplicate delivery attempts are rejected/skipped create() catches constraint violation and returns existing id
Concurrent requests are handled safely DB unique index is the single authority; race resolved atomically at SQLite layer

Bug fixes (pre-existing type errors)

File Fix
migration-system.ts Cast db.all() result through unknown before mapping; stringify error in logger.error
discord-notification.ts Use local variable instead of re-accessing embed.title/embed.footer after optional narrowing
notification-retry-queue.ts Annotate entry param type in .map()
schema/compatibility-check.ts Use contractEvent.dataFields instead of undeclared dataFields; guard expectedTopics?.length comparison
benchmark-utils.ts ReturnType<typeof setInterval> instead of NodeJS.Timer
notification-stats-cache.ts Replace cache.has() with cache.get() !== undefined (NodeCache typings don't expose has())

…EOUT_MS)

- Add webhookTimeoutMs field to RetrySchedulerOptions and RetrySchedulerConfig
- Read WEBHOOK_DELIVERY_TIMEOUT_MS env var in loadRetrySchedulerConfig() (default 10 000 ms)
- Pass timeout to WebhookDeliveryService constructor in RetryScheduler
- Document new env var in .env.example

Fix pre-existing merge artifacts:
- request-id.ts: restore missing closing brace on generateCorrelationId()
- security-headers.ts: fix invalid 'http.ServerResponse' import type syntax
- discord-notification.ts: remove dead unreachable return in scvString case; restore missing closing brace on sanitizeForDiscord()
- index.ts: collapse duplicate healthMonitor construction, remove duplicate subscriber declaration, fix broken shutdown try/catch block
- events-server.ts: remove duplicate TemplateService/handleTemplateRoutes imports; add missing handleApiError and applyRequestIdMiddleware imports
- config.ts: remove duplicate types import line; add missing validateSecrets import
- event-subscriber.ts: remove duplicate processableEvents declaration; collapse duplicate getContractEvents request block; add missing backfillStartLedger property
- Add migration 003: ALTER TABLE adds deduplication_key TEXT with a
  UNIQUE partial index on scheduled_notifications
- Update schema.sql with the new column and index for fresh installs
- Extend ScheduledNotification, ScheduledNotificationRow, and
  CreateScheduledNotificationInput types with deduplicationKey field
- Update repository.create(): inserts deduplication_key; on UNIQUE
  constraint violation returns the existing notification id so callers
  are idempotent without throwing — covers concurrent requests at the
  DB layer

Fix pre-existing type errors surfaced by the workflow check:
- migration-system.ts: cast db.all() result through unknown before
  mapping; stringify error in logger.error call
- discord-notification.ts: use local variable instead of accessing
  embed.title / embed.footer after optional narrowing
- notification-retry-queue.ts: annotate entry param type in .map()
- schema/compatibility-check.ts: use contractEvent.dataFields instead
  of undeclared dataFields; guard expectedTopics?.length comparison
- benchmark-utils.ts: use ReturnType<typeof setInterval> instead of
  NodeJS.Timer
- notification-stats-cache.ts: replace cache.has() with cache.get()
  check (NodeCache typings do not expose has())
@drips-wave

drips-wave Bot commented Sep 30, 2026

Copy link
Copy Markdown

@Vvictor-commits 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

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.

Implement Notification Deduplication Keys

1 participant