@checkstack/notification-backend 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,171 @@
1
1
  # @checkstack/notification-backend
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f23f3c9: Add per-channel notification delivery-attempt tracking
8
+ (Phase 8 of the v1 polishing plan). The external dispatch loop now
9
+ persists one row per `strategy.send(...)` call into a new
10
+ `notification_delivery_attempts` table - both successes and
11
+ failures - so silent delivery breakage (misconfigured webhooks, dead
12
+ channels) becomes queryable instead of buried in logs.
13
+
14
+ - `@checkstack/notification-backend` adds the
15
+ `notification_delivery_attempts` table, the matching Drizzle
16
+ migration, and a new `dispatchWithAttempt` helper that wraps every
17
+ external `strategy.send(...)` with duration measurement and
18
+ best-effort row persistence. The insert is intentionally
19
+ fire-and-forget: if writing the attempt row itself errors, the
20
+ dispatch loop logs and continues so visibility tracking can never
21
+ introduce a _new_ silent failure.
22
+ - `@checkstack/notification-common` exports a new
23
+ `DeliveryAttemptSchema` zod schema, the
24
+ `ListDeliveryAttemptsInputSchema =
25
+ PaginationInput.extend({ notificationId })` input, and a new
26
+ `getDeliveryAttempts` procedure on the contract. The procedure is
27
+ gated by the existing `notificationAccess.admin`
28
+ (`notification:manage`) access rule - no new permission was
29
+ introduced.
30
+ - `@checkstack/notification-frontend` adds a minimal admin-only
31
+ `DeliveryAttemptsPage` (route id `notification.deliveryAttempts`,
32
+ path `/notifications/delivery-attempts`) and an "Open inspector"
33
+ link from the Notification Settings page for users with
34
+ `notification:manage`. No client-side `isAdmin` gate - the FORBIDDEN
35
+ case is rendered via the standard error-state branch on the page,
36
+ enforced by the contract.
37
+
38
+ Visibility only: there is no retry mechanism in this phase. A
39
+ `failure` row is a final outcome an admin actions manually
40
+ (re-trigger the source event, fix the misconfigured channel).
41
+ Automated retries are deferred to v1.1.
42
+
43
+ Strategy errors thrown during `send(...)` are persisted via
44
+ `extractErrorMessage(error)` so secrets potentially embedded in raw
45
+ error objects (webhook URLs, OAuth tokens reachable from the strategy
46
+ send context) are not stored verbatim.
47
+
48
+ See the new
49
+ `docs/src/content/docs/backend/notification-delivery.md` page for the
50
+ full surface description.
51
+
52
+ ### Patch Changes
53
+
54
+ - f23f3c9: Add `correlationMiddleware` to `@checkstack/backend-api` and apply it
55
+ to every plugin/core router so each request carries a stable
56
+ `x-correlation-id` (read from the inbound header, or freshly minted
57
+ via `crypto.randomUUID()` when absent) and an auto-injected child
58
+ logger bound with `{ correlationId, pluginId, userId? }`. The ID is
59
+ echoed back on the response header so the caller can correlate their
60
+ client-side trace to the server logs.
61
+
62
+ The `Logger` interface in `@checkstack/backend-api` now formally
63
+ documents the structured-metadata convention (`logger.info("msg",
64
+ { ...meta })`) alongside the long-standing varargs shape. Winston's
65
+ splat handling already routes both shapes through the same vararg
66
+ slot, so existing call sites are unaffected. A new optional
67
+ `Logger.child(meta)` method captures the metadata-binding contract the
68
+ new middleware relies on; production loggers always implement it,
69
+ minimal test mocks may omit it (the middleware falls back gracefully).
70
+
71
+ `RpcContext` grew two optional `Headers` bags, `requestHeaders` and
72
+ `responseHeaders`, populated by the outer Hono `/api/*` and `/rest/*`
73
+ handlers in `@checkstack/backend`. They are write-through observation
74
+ points for middleware; an `RpcContext` constructed without them (S2S
75
+ clients, tests) keeps working — the echo is a silent no-op and the ID
76
+ is still bound onto the child logger for server-side correlation.
77
+
78
+ The scaffolding template in `@checkstack/scripts` was updated so any
79
+ new plugin generated via `bun run create` wires the middleware in the
80
+ expected `.use(correlationMiddleware).use(autoAuthMiddleware)` order
81
+ out of the box.
82
+
83
+ - f23f3c9: Sweep every paginated `*-common` contract onto the canonical
84
+ `PaginationInput` / `PaginatedResult` from `@checkstack/common` and
85
+ remove the now-unused legacy exports.
86
+
87
+ **BREAKING CHANGE** - `@checkstack/common` drops the deprecated
88
+ `PaginationInputSchema`, `paginatedOutput`, and `PaginatedResponse`
89
+ symbols. Callers must consume `PaginationInput` (input) and
90
+ `PaginatedResult(itemSchema)` (output) instead. The canonical input is
91
+ `{ limit (1-100, default 20), offset (>= 0, default 0) }`; the
92
+ canonical output envelope is
93
+ `{ items, total, limit, offset }`.
94
+
95
+ **BREAKING CHANGE** - `@checkstack/notification-common` migrates
96
+ `getNotifications` off the legacy `PaginationInputSchema`
97
+ (`{ limit, offset, unreadOnly }` with output `{ notifications, total }`)
98
+ onto `ListNotificationsInputSchema =
99
+ PaginationInput.extend({ unreadOnly })` and
100
+ `PaginatedResult(NotificationSchema)`. The output key changes from
101
+ `notifications` to `items`, and `limit` / `offset` are now echoed on
102
+ the response. The `PaginationInput` type alias previously exported
103
+ from `notification-common` is removed - use `ListNotificationsInput`
104
+ or the canonical `PaginationInput` from `@checkstack/common`.
105
+
106
+ **BREAKING CHANGE** - `@checkstack/integration-common` migrates
107
+ `listSubscriptions` (inline `{ page, pageSize, ... }` -> output
108
+ `{ subscriptions, total }`) and `getDeliveryLogs` (via
109
+ `DeliveryLogQueryInputSchema` `{ subscriptionId?, eventType?, status?,
110
+ page, pageSize }` -> output `{ logs, total }`) onto the canonical
111
+ `PaginationInput.extend({...})` input and
112
+ `PaginatedResult(itemSchema)` output. External callers must switch
113
+ from `{ page, pageSize }` to `{ limit, offset }` and read response
114
+ items from `data.items` (no more `data.subscriptions` / `data.logs`).
115
+
116
+ The matching `*-backend` handlers were updated to consume the new
117
+ input shape (`offset` arithmetic in lieu of `(page - 1) * pageSize`)
118
+ and to echo `limit` / `offset` on the response. The `*-frontend` call
119
+ sites in `NotificationsPage`, `NotificationBell`, `IntegrationsPage`,
120
+ and `DeliveryLogsPage` were updated to send the new input shape and
121
+ read `data.items`.
122
+
123
+ - f23f3c9: Phase 9 of the v1 polishing plan: tighten the plugin loader's boot-time
124
+ hook policy and backfill notification-router test coverage.
125
+
126
+ `@checkstack/backend` adopts an explicit per-hook policy for the two
127
+ boot-time hooks the plugin loader emits. `pluginInitialized` now
128
+ **halts the boot** if a subscriber throws — a failing subscriber here
129
+ means a downstream never wired itself against the freshly initialised
130
+ plugin, and continuing past that would leave the platform serving
131
+ traffic in a half-wired state. `accessRulesRegistered` keeps its
132
+ log-and-continue behaviour but escalates to `error` level and emits a
133
+ summary count if any subscriber failed; boot-blocking this hook would
134
+ let one misbehaving plugin DOS every other plugin on the same
135
+ instance. The policy is documented inline at each emit site and in a
136
+ new `docs/src/content/docs/backend/plugin-hook-policy.md` page.
137
+ **BREAKING CHANGE**: subscribers to `pluginInitialized` that
138
+ previously threw silently (logged and swallowed) now halt platform
139
+ boot. Audit subscribers and ensure they handle their own internal
140
+ errors before throwing.
141
+
142
+ `@checkstack/notification-backend` ships a real
143
+ `core/notification-backend/src/router.test.ts` covering the dispatch
144
+ fan-out (`notifyForSubscription`: zero subscribers, multi-recipient
145
+ insert, `excludeUserIds`, plus NOT_FOUND/FORBIDDEN guard rails), the
146
+ canonical paginated read on `getNotifications` (envelope shape,
147
+ `unreadOnly` filter propagation, null→undefined column mapping), the
148
+ service-only `createGroup` upsert behaviour (happy path + idempotent
149
+ re-create), and the multi-strategy `sendTransactional` path with a
150
+ focused fallback-style assertion: when one strategy throws, the
151
+ dispatch loop continues to the next and surfaces the failure as a
152
+ per-strategy `success: false` row instead of short-circuiting. No
153
+ runtime changes to the notification router.
154
+
155
+ - Updated dependencies [f23f3c9]
156
+ - Updated dependencies [f23f3c9]
157
+ - Updated dependencies [f23f3c9]
158
+ - Updated dependencies [f23f3c9]
159
+ - @checkstack/common@0.11.0
160
+ - @checkstack/backend-api@0.17.0
161
+ - @checkstack/auth-backend@0.4.29
162
+ - @checkstack/notification-common@1.2.0
163
+ - @checkstack/auth-common@0.7.1
164
+ - @checkstack/signal-common@0.2.4
165
+ - @checkstack/cache-api@0.3.4
166
+ - @checkstack/queue-api@0.3.4
167
+ - @checkstack/cache-utils@0.2.9
168
+
3
169
  ## 1.1.0
4
170
 
5
171
  ### Minor Changes
@@ -0,0 +1,14 @@
1
+ CREATE TYPE "notification_delivery_status" AS ENUM('success', 'failure');--> statement-breakpoint
2
+ CREATE TABLE "notification_delivery_attempts" (
3
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
4
+ "notification_id" uuid NOT NULL,
5
+ "strategy_qualified_id" text NOT NULL,
6
+ "attempted_at" timestamp DEFAULT now() NOT NULL,
7
+ "status" "notification_delivery_status" NOT NULL,
8
+ "error_message" text,
9
+ "duration_ms" integer NOT NULL
10
+ );
11
+ --> statement-breakpoint
12
+ ALTER TABLE "notification_delivery_attempts" ADD CONSTRAINT "notification_delivery_attempts_notification_id_notifications_id_fk" FOREIGN KEY ("notification_id") REFERENCES "notifications"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
13
+ CREATE INDEX "notification_delivery_attempts_notification_idx" ON "notification_delivery_attempts" USING btree ("notification_id");--> statement-breakpoint
14
+ CREATE INDEX "notification_delivery_attempts_attempted_at_idx" ON "notification_delivery_attempts" USING btree ("attempted_at");