@de-otio/trellis 0.10.11 → 0.11.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.
Files changed (85) hide show
  1. package/dist/env.d.ts +64 -0
  2. package/dist/env.d.ts.map +1 -1
  3. package/dist/env.js +66 -0
  4. package/dist/env.js.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/app.d.ts.map +1 -1
  10. package/dist/lib/app.js +5 -0
  11. package/dist/lib/app.js.map +1 -1
  12. package/dist/lib/encrypted-settings/config.d.ts +13 -0
  13. package/dist/lib/encrypted-settings/config.d.ts.map +1 -0
  14. package/dist/lib/encrypted-settings/config.js +19 -0
  15. package/dist/lib/encrypted-settings/config.js.map +1 -0
  16. package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts +57 -0
  17. package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts.map +1 -0
  18. package/dist/lib/encrypted-settings/encrypted-settings-handler.js +178 -0
  19. package/dist/lib/encrypted-settings/encrypted-settings-handler.js.map +1 -0
  20. package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts +110 -0
  21. package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts.map +1 -0
  22. package/dist/lib/encrypted-settings/encrypted-settings-store.js +103 -0
  23. package/dist/lib/encrypted-settings/encrypted-settings-store.js.map +1 -0
  24. package/dist/lib/encrypted-settings/types.d.ts +26 -0
  25. package/dist/lib/encrypted-settings/types.d.ts.map +1 -0
  26. package/dist/lib/encrypted-settings/types.js +27 -0
  27. package/dist/lib/encrypted-settings/types.js.map +1 -0
  28. package/dist/lib/notification-handler.d.ts +11 -4
  29. package/dist/lib/notification-handler.d.ts.map +1 -1
  30. package/dist/lib/notification-handler.js +161 -29
  31. package/dist/lib/notification-handler.js.map +1 -1
  32. package/dist/lib/realtime/block-store.d.ts +61 -0
  33. package/dist/lib/realtime/block-store.d.ts.map +1 -0
  34. package/dist/lib/realtime/block-store.js +0 -0
  35. package/dist/lib/realtime/block-store.js.map +1 -0
  36. package/dist/lib/realtime/channel.d.ts +34 -0
  37. package/dist/lib/realtime/channel.d.ts.map +1 -0
  38. package/dist/lib/realtime/channel.js +100 -0
  39. package/dist/lib/realtime/channel.js.map +1 -0
  40. package/dist/lib/realtime/delivery-policy.d.ts +51 -0
  41. package/dist/lib/realtime/delivery-policy.d.ts.map +1 -0
  42. package/dist/lib/realtime/delivery-policy.js +98 -0
  43. package/dist/lib/realtime/delivery-policy.js.map +1 -0
  44. package/dist/lib/realtime/index.d.ts +21 -0
  45. package/dist/lib/realtime/index.d.ts.map +1 -0
  46. package/dist/lib/realtime/index.js +39 -0
  47. package/dist/lib/realtime/index.js.map +1 -0
  48. package/dist/lib/realtime/no-op-transport.d.ts +10 -0
  49. package/dist/lib/realtime/no-op-transport.d.ts.map +1 -0
  50. package/dist/lib/realtime/no-op-transport.js +44 -0
  51. package/dist/lib/realtime/no-op-transport.js.map +1 -0
  52. package/dist/lib/realtime/poll-transport.d.ts +11 -0
  53. package/dist/lib/realtime/poll-transport.d.ts.map +1 -0
  54. package/dist/lib/realtime/poll-transport.js +68 -0
  55. package/dist/lib/realtime/poll-transport.js.map +1 -0
  56. package/dist/lib/realtime/push-notifier.d.ts +39 -0
  57. package/dist/lib/realtime/push-notifier.d.ts.map +1 -0
  58. package/dist/lib/realtime/push-notifier.js +76 -0
  59. package/dist/lib/realtime/push-notifier.js.map +1 -0
  60. package/dist/lib/realtime/realtime-transport.d.ts +2 -0
  61. package/dist/lib/realtime/realtime-transport.d.ts.map +1 -0
  62. package/dist/lib/realtime/realtime-transport.js +23 -0
  63. package/dist/lib/realtime/realtime-transport.js.map +1 -0
  64. package/dist/lib/realtime/setting-store.d.ts +30 -0
  65. package/dist/lib/realtime/setting-store.d.ts.map +1 -0
  66. package/dist/lib/realtime/setting-store.js +0 -0
  67. package/dist/lib/realtime/setting-store.js.map +1 -0
  68. package/dist/lib/realtime/types.d.ts +200 -0
  69. package/dist/lib/realtime/types.d.ts.map +1 -0
  70. package/dist/lib/realtime/types.js +61 -0
  71. package/dist/lib/realtime/types.js.map +1 -0
  72. package/dist/lib/routes/index.d.ts.map +1 -1
  73. package/dist/lib/routes/index.js +3 -0
  74. package/dist/lib/routes/index.js.map +1 -1
  75. package/dist/lib/routes/settings.d.ts +17 -0
  76. package/dist/lib/routes/settings.d.ts.map +1 -0
  77. package/dist/lib/routes/settings.js +187 -0
  78. package/dist/lib/routes/settings.js.map +1 -0
  79. package/dist/lib/tenant-scope.d.ts.map +1 -1
  80. package/dist/lib/tenant-scope.js +2 -0
  81. package/dist/lib/tenant-scope.js.map +1 -1
  82. package/package.json +22 -22
  83. package/prisma/migrations/20260620051144_add_encrypted_user_settings/migration.sql +24 -0
  84. package/prisma/migrations/20260620120000_add_blocked_users/migration.sql +29 -0
  85. package/prisma/schema.prisma +40 -0
@@ -8,12 +8,113 @@
8
8
  */
9
9
  import { createPrisma } from "../db.js";
10
10
  import { getLogger } from "./logger.js";
11
- /** Notification types that always bypass preferences and quiet hours */
11
+ import { CalmDeliveryResolver } from "./realtime/index.js";
12
+ import { PushNotifier } from "./realtime/push-notifier.js";
13
+ import { PrismaBlockStore, } from "./realtime/block-store.js";
14
+ /**
15
+ * Notification types that always bypass preferences and quiet hours.
16
+ * Kept here for the preference-gate short-circuit (preference-off => no row),
17
+ * which is a DIFFERENT outcome from quiet-hours suppression. The deliver-time
18
+ * decision (deliveredAt) is owned by the shared `DeliveryPolicyResolver` (WS1
19
+ * `CalmDeliveryResolver`), which also carries the ALWAYS_DELIVER bypass.
20
+ */
12
21
  const ALWAYS_DELIVER_TYPES = [
13
22
  "SAFETY_ALERT",
14
23
  "PARENTAL_LINK",
15
24
  ];
25
+ /**
26
+ * Extract a candidate sender id from a notification's `data` payload, for the
27
+ * blocked-sender floor input (`DeliveryContext.senderUserId`). Notification
28
+ * producers conventionally stamp the actor under one of these keys; we read it
29
+ * defensively (the payload is untyped JSON) and return `undefined` when absent
30
+ * or non-string. This NEVER trusts the value as a security boundary — it is
31
+ * only an input to the floor's blocked-sender check.
32
+ */
33
+ function extractSenderUserId(data) {
34
+ if (typeof data !== "object" || data === null)
35
+ return undefined;
36
+ const obj = data;
37
+ for (const key of ["senderUserId", "senderId", "actorId", "fromUserId"]) {
38
+ const v = obj[key];
39
+ if (typeof v === "string" && v.length > 0)
40
+ return v;
41
+ }
42
+ return undefined;
43
+ }
44
+ /**
45
+ * Build the realtime `DeliveryContext` from the recipient row.
46
+ *
47
+ * The resolver's quiet-hours window uses `QuietHoursConfig.start/end` as
48
+ * decimal strings of minutes-since-midnight — the exact units the existing
49
+ * `User.quietHoursStart/End` integers use — and reads the current minute from
50
+ * `ctx.now`, so the decision is identical to the legacy inline check while
51
+ * remaining deterministic.
52
+ *
53
+ * Track D enriches the context with `recipientAgeTier` (minor-protection floor)
54
+ * and `senderUserId` (blocked-sender floor) so the non-configurable floor in
55
+ * `CalmDeliveryResolver` has the inputs it needs. The resolver is pure/sync, so
56
+ * the async block-set lookup happens in the caller: `blockedSenderUserId` is the
57
+ * sender id ONLY when the recipient has blocked that sender (else undefined),
58
+ * matching the resolver's "presence of senderUserId == in the block set"
59
+ * contract. For ALWAYS_DELIVER types the recipient row is not fetched (the
60
+ * bypass short-circuits before the user lookup), so `user` is null and the
61
+ * enrichment fields are omitted — those types bypass the floor regardless.
62
+ */
63
+ function buildDeliveryContext(type, recipientUserId, tenantId, now, user, blockedSenderUserId) {
64
+ let quietHours = null;
65
+ if (user?.quietHoursEnabled &&
66
+ user.quietHoursStart != null &&
67
+ user.quietHoursEnd != null) {
68
+ quietHours = {
69
+ enabled: true,
70
+ start: String(user.quietHoursStart),
71
+ end: String(user.quietHoursEnd),
72
+ };
73
+ }
74
+ const ctx = {
75
+ type,
76
+ recipientUserId,
77
+ tenantId,
78
+ now,
79
+ quietHours,
80
+ };
81
+ if (user)
82
+ ctx.recipientAgeTier = user.ageTier;
83
+ if (blockedSenderUserId !== undefined)
84
+ ctx.senderUserId = blockedSenderUserId;
85
+ return ctx;
86
+ }
16
87
  export class NotificationHandler {
88
+ // The delivery floor is migrated into this shared resolver (WS1). The SAME
89
+ // decision drives both the persistence `deliveredAt` choice and the
90
+ // (default-off) push hand-off, so polling and pushing can never diverge.
91
+ // Injectable so tests can exercise floor outcomes (blocked_sender, etc.)
92
+ // without reaching into WS1-owned floor logic; defaults to the WS1 floor.
93
+ // Injected resolver (tests) or null => build the default from env so the
94
+ // re-engagement denylist (runtime config) reaches the floor. The SAME
95
+ // decision drives both the persistence `deliveredAt`/drop choice and the
96
+ // (default-off) push hand-off, so polling and pushing can never diverge.
97
+ injectedResolver;
98
+ // Block-set port for the blocked-sender floor. Injectable so tests exercise
99
+ // the drop without a live DB; defaults to a Prisma-backed store bound to the
100
+ // per-call client. `null` = resolve from env's db (the production path).
101
+ blockStore;
102
+ constructor(deliveryResolver = null, blockStore = null) {
103
+ this.injectedResolver = deliveryResolver;
104
+ this.blockStore = blockStore;
105
+ }
106
+ /**
107
+ * The resolver for this call: the injected one (tests) or a default built
108
+ * from env's runtime re-engagement denylist (threshold-secrecy: the denylist
109
+ * is env-driven config, never a compiled-in constant).
110
+ */
111
+ resolverFor(env) {
112
+ if (this.injectedResolver)
113
+ return this.injectedResolver;
114
+ return new CalmDeliveryResolver({
115
+ reengagementTypes: env.REALTIME_REENGAGEMENT_TYPES,
116
+ });
117
+ }
17
118
  /**
18
119
  * Create a notification for a user.
19
120
  * Checks preferences (unless SAFETY_ALERT or PARENTAL_LINK) and quiet hours.
@@ -40,22 +141,58 @@ export class NotificationHandler {
40
141
  }
41
142
  }
42
143
  }
43
- // 3. Check quiet hours (skip for SAFETY_ALERT and PARENTAL_LINK)
44
- let deliveredAt = new Date();
45
- if (!bypassPreferences) {
46
- const user = await db.user.findUnique({
144
+ // 3. Resolve the delivery decision (quiet hours + ALWAYS_DELIVER bypass)
145
+ // via the shared policy resolver. Capture `now` once so the context
146
+ // clock and the persisted deliveredAt agree.
147
+ const now = new Date();
148
+ const user = bypassPreferences
149
+ ? null
150
+ : await db.user.findUnique({
47
151
  where: { id: userId },
48
152
  select: {
49
153
  quietHoursEnabled: true,
50
154
  quietHoursStart: true,
51
155
  quietHoursEnd: true,
156
+ ageTier: true,
52
157
  },
53
158
  });
54
- if (user?.quietHoursEnabled && this.isInQuietHours(user)) {
55
- // 4. In quiet hours: create with deliveredAt=null
56
- deliveredAt = null;
57
- }
159
+ // Enrich the floor inputs (Track D): blocked-sender comes from the
160
+ // notification payload's actor stamp, resolved against the block set;
161
+ // minor-protection comes from the recipient's ageTier loaded above.
162
+ // ALWAYS_DELIVER types bypass the floor, so we skip the block lookup for
163
+ // them (no row was fetched and the resolver short-circuits anyway).
164
+ const candidateSenderUserId = extractSenderUserId(data);
165
+ let blockedSenderUserId;
166
+ if (!bypassPreferences && candidateSenderUserId !== undefined) {
167
+ const blockStore = this.blockStore ?? new PrismaBlockStore(db);
168
+ // The recipient (userId) is the blocker; the notification's sender is
169
+ // the blocked candidate. Presence in the set => pass the id to the
170
+ // resolver as the deny signal.
171
+ const blocked = await blockStore.isBlocked(tenantId, userId, candidateSenderUserId);
172
+ if (blocked)
173
+ blockedSenderUserId = candidateSenderUserId;
174
+ }
175
+ const deliveryContext = buildDeliveryContext(type, userId, tenantId, now, user, blockedSenderUserId);
176
+ const decision = this.resolverFor(env).decide(deliveryContext);
177
+ // 4. Floor DROP vs DEFERRAL. A floor drop (blocked_sender / minor-floor)
178
+ // is a hard suppression — no row at all (same observable as a
179
+ // preference-off skip: `{ id: "" }`), so the dropped notification can
180
+ // never be read back via polling. A quiet-hours deferral keeps the row
181
+ // with deliveredAt=null (delivered on a later poll). This single
182
+ // decision also gates the push hand-off below, so polling and pushing
183
+ // can never diverge.
184
+ if (!decision.deliver &&
185
+ (decision.reason === "blocked_sender" || decision.reason === "floor")) {
186
+ logger.info("Notification dropped by delivery floor", {
187
+ userId,
188
+ type,
189
+ reason: decision.reason,
190
+ });
191
+ return { id: "" };
58
192
  }
193
+ // deliveredAt: now when the resolver says deliver, null when deferred
194
+ // (quiet hours). ALWAYS_DELIVER types resolve to deliver => now.
195
+ const deliveredAt = decision.deliver ? now : null;
59
196
  // 5. Create notification
60
197
  const notification = await db.notification.create({
61
198
  data: {
@@ -68,6 +205,21 @@ export class NotificationHandler {
68
205
  tenantId,
69
206
  },
70
207
  });
208
+ // 6. Content-free push hand-off (default OFF — gated by
209
+ // features.realtimePush; WS4 owns this). Best-effort and non-fatal: a
210
+ // transport failure never blocks or rolls back persistence (polling
211
+ // still delivers on the next poll). We gate here on the SAME decision
212
+ // that set deliveredAt, so polling and pushing can never diverge; the
213
+ // policy fence ALSO re-runs inside the transport's deliver().
214
+ // ALWAYS_DELIVER types route to the floor "safety" channel; everything
215
+ // else to "wakeup". The payload is content-free by construction — it is
216
+ // built only via encodeWakeup() inside PushNotifier, so no title/body/
217
+ // data can ever reach the wire.
218
+ if (env.features?.realtimePush && decision.deliver) {
219
+ const kind = bypassPreferences ? "safety" : "wakeup";
220
+ const pushNotifier = new PushNotifier(env.realtimeTransport, logger);
221
+ await pushNotifier.notify({ target: { userId, tenantId }, kind });
222
+ }
71
223
  return { id: notification.id };
72
224
  }
73
225
  catch (error) {
@@ -203,26 +355,6 @@ export class NotificationHandler {
203
355
  return true;
204
356
  }
205
357
  }
206
- /**
207
- * Check if current time falls within user's quiet hours.
208
- */
209
- isInQuietHours(user) {
210
- if (!user.quietHoursEnabled ||
211
- user.quietHoursStart == null ||
212
- user.quietHoursEnd == null) {
213
- return false;
214
- }
215
- const now = new Date();
216
- const minutesSinceMidnight = now.getHours() * 60 + now.getMinutes();
217
- const start = user.quietHoursStart;
218
- const end = user.quietHoursEnd;
219
- // Handle overnight quiet hours (e.g., 22:00 to 07:00)
220
- if (start > end) {
221
- return minutesSinceMidnight >= start || minutesSinceMidnight < end;
222
- }
223
- // Same-day quiet hours (e.g., 13:00 to 15:00)
224
- return minutesSinceMidnight >= start && minutesSinceMidnight < end;
225
- }
226
358
  }
227
359
  export class NotificationNotFoundError extends Error {
228
360
  constructor(notificationId) {
@@ -1 +1 @@
1
- {"version":3,"file":"notification-handler.js","sourceRoot":"","sources":["../../src/lib/notification-handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"notification-handler.js","sourceRoot":"","sources":["../../src/lib/notification-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Minimal block-list port the delivery floor consults. Mirrors the
3
+ * `SettingStore` port style: one small interface, an in-memory default for
4
+ * zero-infra tests, and a structural Prisma-backed production implementation.
5
+ *
6
+ * `isBlocked(tenantId, blockerId, blockedId)` answers "within `tenantId`, has
7
+ * `blockerId` blocked `blockedId`?" — for the floor, the recipient is the
8
+ * blocker and the notification's sender is the blocked candidate.
9
+ */
10
+ export interface BlockStore {
11
+ isBlocked(tenantId: string, blockerId: string, blockedId: string): Promise<boolean>;
12
+ }
13
+ /**
14
+ * In-memory `BlockStore` for zero-infra tests and the core default. Holds a set
15
+ * of `tenantId \0 blockerId \0 blockedId` keys. Mirrors `InMemorySettingStore`.
16
+ */
17
+ export declare class InMemoryBlockStore implements BlockStore {
18
+ private readonly blocks;
19
+ private key;
20
+ /** Test/seed helper: record a directed block edge. */
21
+ block(tenantId: string, blockerId: string, blockedId: string): void;
22
+ isBlocked(tenantId: string, blockerId: string, blockedId: string): Promise<boolean>;
23
+ }
24
+ /** The row shape (id only) this store reads back from Postgres. */
25
+ interface BlockedUserRow {
26
+ id: string;
27
+ }
28
+ /**
29
+ * The minimal Prisma surface this store needs. Declared structurally (like
30
+ * `EncryptedUserSettingDelegate`) so the store is unit-testable against a mock
31
+ * without importing the generated client, and is decoupled from Prisma version
32
+ * drift.
33
+ */
34
+ export interface BlockedUserDelegate {
35
+ findUnique(args: {
36
+ where: {
37
+ tenantId_blockerId_blockedId: {
38
+ tenantId: string;
39
+ blockerId: string;
40
+ blockedId: string;
41
+ };
42
+ };
43
+ select: {
44
+ id: true;
45
+ };
46
+ }): Promise<BlockedUserRow | null>;
47
+ }
48
+ export interface PrismaWithBlockedUser {
49
+ blockedUser: BlockedUserDelegate;
50
+ }
51
+ /**
52
+ * Production `BlockStore` backed by the `blocked_users` table. A single indexed
53
+ * unique lookup on `[tenantId, blockerId, blockedId]` — `null` row ⇒ not blocked.
54
+ */
55
+ export declare class PrismaBlockStore implements BlockStore {
56
+ private readonly db;
57
+ constructor(db: PrismaWithBlockedUser);
58
+ isBlocked(tenantId: string, blockerId: string, blockedId: string): Promise<boolean>;
59
+ }
60
+ export {};
61
+ //# sourceMappingURL=block-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-store.d.ts","sourceRoot":"","sources":["../../../src/lib/realtime/block-store.ts"],"names":[],"mappings":"AAYA;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,OAAO,CAAC,GAAG;IAIX,sDAAsD;IACtD,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAI7D,SAAS,CACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;CAGpB;AAED,mEAAmE;AACnE,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,IAAI,EAAE;QACf,KAAK,EAAE;YACL,4BAA4B,EAAE;gBAC5B,QAAQ,EAAE,MAAM,CAAC;gBACjB,SAAS,EAAE,MAAM,CAAC;gBAClB,SAAS,EAAE,MAAM,CAAC;aACnB,CAAC;SACH,CAAC;QACF,MAAM,EAAE;YAAE,EAAE,EAAE,IAAI,CAAA;SAAE,CAAC;KACtB,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,UAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,qBAAqB;IAEhD,SAAS,CACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;CASpB"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-store.js","sourceRoot":"","sources":["../../../src/lib/realtime/block-store.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,0EAA0E;AAC1E,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,kEAAkE;AAClE,EAAE;AACF,gFAAgF;AAChF,yBAAyB;AAmBzB;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACZ,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,GAAG,CAAC,QAAgB,EAAE,SAAiB,EAAE,SAAiB;QAChE,OAAO,GAAG,QAAQ,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;IACjD,CAAC;IAED,sDAAsD;IACtD,KAAK,CAAC,QAAgB,EAAE,SAAiB,EAAE,SAAiB;QAC1D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,SAAS,CACb,QAAgB,EAChB,SAAiB,EACjB,SAAiB;QAEjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IACnE,CAAC;CACF;AA8BD;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IACE;IAA7B,YAA6B,EAAyB;QAAzB,OAAE,GAAF,EAAE,CAAuB;IAAG,CAAC;IAE1D,KAAK,CAAC,SAAS,CACb,QAAgB,EAChB,SAAiB,EACjB,SAAiB;QAEjB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC;YAC/C,KAAK,EAAE;gBACL,4BAA4B,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE;aACjE;YACD,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE;SACrB,CAAC,CAAC;QACH,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ import type { Channel, ChannelKind, VerifiedIdentity } from "./types.js";
2
+ /**
3
+ * Produce the canonical channel path. This is the ONLY way a channel string is
4
+ * minted server-side; the Skybber `@skybber/realtime-channels` parser must
5
+ * round-trip this output.
6
+ */
7
+ export declare function channelName(c: Channel): string;
8
+ /**
9
+ * Parse a canonical channel path. Returns `null` on ANY malformed input — a
10
+ * leading-slash-less path, wrong arity, unknown kind/scopeType, or an empty
11
+ * segment.
12
+ */
13
+ export declare function parseChannel(path: string): Channel | null;
14
+ /**
15
+ * Build a user-scoped channel (v1: the only scope). `kind` is constrained to
16
+ * the v1 user-scoped kinds; `message`/`thread` are deferred and excluded.
17
+ */
18
+ export declare function channelFor(kind: Exclude<ChannelKind, "message" | "thread">, scope: {
19
+ tenantId: string;
20
+ userId: string;
21
+ }): Channel;
22
+ /**
23
+ * THE security boundary. The channel is a client *assertion* this checks
24
+ * against a server-verified identity.
25
+ *
26
+ * ALLOW iff `c.tenantId === id.tenantId` AND
27
+ * scopeType "user" -> `c.scopeId === id.userId`
28
+ * scopeType conversation/thread -> membership(id.userId, c.scopeId) [v1: false]
29
+ *
30
+ * End-user PUBLISH is ALWAYS denied elsewhere (publish is server-only via IAM);
31
+ * this function governs SUBSCRIBE only.
32
+ */
33
+ export declare function authorizeSubscription(id: VerifiedIdentity, c: Channel): boolean;
34
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/lib/realtime/channel.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,OAAO,EACP,WAAW,EAEX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAsBpB;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAW9C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAezD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE,SAAS,GAAG,QAAQ,CAAC,EAChD,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAOT;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,gBAAgB,EACpB,CAAC,EAAE,OAAO,GACT,OAAO,CAOT"}
@@ -0,0 +1,100 @@
1
+ // CONTRACT: stable — coordinate changes. See types.ts banner.
2
+ //
3
+ // Channel naming + the subscription authorization boundary. Per §2.1/§2.2 of
4
+ // the frozen contract, the canonical path is produced ONLY by `channelName()`
5
+ // and parsed ONLY by `parseChannel()`; `authorizeSubscription()` is THE
6
+ // security boundary — identity comes from a VerifiedIdentity (Cognito claims),
7
+ // never an ambient Session and never a client-asserted path.
8
+ /** Canonical path grammar: /{kind}/{tenantId}/{scopeType}/{scopeId}. */
9
+ const VALID_KINDS = new Set([
10
+ "wakeup",
11
+ "setting_sync",
12
+ "safety",
13
+ "message",
14
+ "thread",
15
+ ]);
16
+ const VALID_SCOPE_TYPES = new Set([
17
+ "user",
18
+ "conversation",
19
+ "thread",
20
+ ]);
21
+ /** A path segment may not be empty and may not contain a path separator. */
22
+ function isValidSegment(s) {
23
+ return s.length > 0 && !s.includes("/");
24
+ }
25
+ /**
26
+ * Produce the canonical channel path. This is the ONLY way a channel string is
27
+ * minted server-side; the Skybber `@skybber/realtime-channels` parser must
28
+ * round-trip this output.
29
+ */
30
+ export function channelName(c) {
31
+ if (!VALID_KINDS.has(c.kind)) {
32
+ throw new Error(`channelName: invalid kind "${String(c.kind)}"`);
33
+ }
34
+ if (!VALID_SCOPE_TYPES.has(c.scopeType)) {
35
+ throw new Error(`channelName: invalid scopeType "${String(c.scopeType)}"`);
36
+ }
37
+ if (!isValidSegment(c.tenantId) || !isValidSegment(c.scopeId)) {
38
+ throw new Error("channelName: tenantId/scopeId must be non-empty, no slash");
39
+ }
40
+ return `/${c.kind}/${c.tenantId}/${c.scopeType}/${c.scopeId}`;
41
+ }
42
+ /**
43
+ * Parse a canonical channel path. Returns `null` on ANY malformed input — a
44
+ * leading-slash-less path, wrong arity, unknown kind/scopeType, or an empty
45
+ * segment.
46
+ */
47
+ export function parseChannel(path) {
48
+ if (typeof path !== "string" || !path.startsWith("/"))
49
+ return null;
50
+ // Drop the leading "" produced by the leading slash; require exactly 4 parts.
51
+ const parts = path.split("/");
52
+ if (parts.length !== 5 || parts[0] !== "")
53
+ return null;
54
+ const [, kind, tenantId, scopeType, scopeId] = parts;
55
+ if (!VALID_KINDS.has(kind))
56
+ return null;
57
+ if (!VALID_SCOPE_TYPES.has(scopeType))
58
+ return null;
59
+ if (!isValidSegment(tenantId) || !isValidSegment(scopeId))
60
+ return null;
61
+ return {
62
+ kind: kind,
63
+ tenantId,
64
+ scopeType: scopeType,
65
+ scopeId,
66
+ };
67
+ }
68
+ /**
69
+ * Build a user-scoped channel (v1: the only scope). `kind` is constrained to
70
+ * the v1 user-scoped kinds; `message`/`thread` are deferred and excluded.
71
+ */
72
+ export function channelFor(kind, scope) {
73
+ return {
74
+ kind,
75
+ tenantId: scope.tenantId,
76
+ scopeType: "user",
77
+ scopeId: scope.userId,
78
+ };
79
+ }
80
+ /**
81
+ * THE security boundary. The channel is a client *assertion* this checks
82
+ * against a server-verified identity.
83
+ *
84
+ * ALLOW iff `c.tenantId === id.tenantId` AND
85
+ * scopeType "user" -> `c.scopeId === id.userId`
86
+ * scopeType conversation/thread -> membership(id.userId, c.scopeId) [v1: false]
87
+ *
88
+ * End-user PUBLISH is ALWAYS denied elsewhere (publish is server-only via IAM);
89
+ * this function governs SUBSCRIBE only.
90
+ */
91
+ export function authorizeSubscription(id, c) {
92
+ if (c.tenantId !== id.tenantId)
93
+ return false;
94
+ if (c.scopeType === "user") {
95
+ return c.scopeId === id.userId;
96
+ }
97
+ // conversation / thread membership — DEFERRED in v1, deny.
98
+ return false;
99
+ }
100
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../../../src/lib/realtime/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { DeliveryContext, DeliveryDecision, DeliveryPolicyResolver } from "./types.js";
2
+ import type { NotificationType } from "@prisma/client";
3
+ /**
4
+ * Notification types that ALWAYS deliver — they bypass user preference and
5
+ * quiet hours. Migrated verbatim from `notification-handler.ts`'s
6
+ * `ALWAYS_DELIVER_TYPES`. This is the critical-always floor.
7
+ */
8
+ export declare const ALWAYS_DELIVER_TYPES: ReadonlySet<NotificationType>;
9
+ /**
10
+ * Track D runtime config for the floor. The minor-protection rule needs the set
11
+ * of "manipulative re-engagement" NotificationTypes to deny to non-adults. Per
12
+ * the threshold-secrecy invariant (CLAUDE.md rule 8) this list is RUNTIME CONFIG
13
+ * — passed in here, never a compiled-in constant sprinkled at a call site.
14
+ * v1 ships it EMPTY (no such type exists yet); a deployment populates it via env.
15
+ */
16
+ export interface DeliveryFloorConfig {
17
+ /** NotificationTypes that re-engage and are denied to non-adult recipients. */
18
+ reengagementTypes?: ReadonlySet<NotificationType>;
19
+ }
20
+ /**
21
+ * The WS1 + Track D default resolver. Pure and synchronous over `DeliveryContext`
22
+ * (the caller does any async lookups and passes resolved signals in):
23
+ *
24
+ * 1. ALWAYS_DELIVER_TYPES (SAFETY_ALERT, PARENTAL_LINK) bypass everything ->
25
+ * `{ deliver: true }`. Safety must NOT be over-blocked: this wins over the
26
+ * blocked-sender and minor-protection floor below.
27
+ * 2. Non-configurable FLOOR (checked only when the caller supplies the input):
28
+ * - blocked sender: the caller resolves block-set membership async (via
29
+ * BlockStore) and populates `ctx.senderUserId` ONLY when the sender is
30
+ * blocked — so presence of `senderUserId` == "in the recipient's block
31
+ * set". The decision is a hard drop no preference can override.
32
+ * - minor-protection: a non-adult recipient (`recipientAgeTier` CHILD/TEEN)
33
+ * targeted by a configured re-engagement type is dropped.
34
+ * 3. Else honor preference (caller pre-resolves `deliver:false`/`preference`)
35
+ * and quiet hours.
36
+ *
37
+ * Note on preference: in the existing handler the type-preference check happens
38
+ * BEFORE creating the row (preference-off => no row at all), which is a
39
+ * different outcome from quiet-hours (row created with deliveredAt=null). The
40
+ * caller maps the decision reasons accordingly. To keep the resolver pure and
41
+ * total it accepts the preference outcome via `ctx` indirectly: the caller only
42
+ * invokes the resolver's quiet-hours path once preference has passed. For a
43
+ * single source of truth the resolver still exposes the full decision so a
44
+ * push-only caller (WS4) can gate solely on it.
45
+ */
46
+ export declare class CalmDeliveryResolver implements DeliveryPolicyResolver {
47
+ private readonly reengagementTypes;
48
+ constructor(config?: DeliveryFloorConfig);
49
+ decide(ctx: DeliveryContext): DeliveryDecision;
50
+ }
51
+ //# sourceMappingURL=delivery-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-policy.d.ts","sourceRoot":"","sources":["../../../src/lib/realtime/delivery-policy.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,gBAAgB,CAE3B,CAAC;AA8BrC;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,WAAW,CAAC,gBAAgB,CAAC,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,oBAAqB,YAAW,sBAAsB;IACjE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgC;gBAEtD,MAAM,GAAE,mBAAwB;IAK5C,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,gBAAgB;CAgC/C"}
@@ -0,0 +1,98 @@
1
+ // CONTRACT: stable — coordinate changes. See types.ts banner.
2
+ //
3
+ // The DeliveryPolicyResolver port and the CalmDeliveryResolver default. Per §3
4
+ // of the frozen contract, WS1 (not WS4) owns migrating the existing hardcoded
5
+ // notification floor into CalmDeliveryResolver, under a golden test that pins
6
+ // byte-identical behavior. The same resolver decision drives BOTH the
7
+ // persistence `deliveredAt` choice AND the (default-off) push hand-off.
8
+ /**
9
+ * Notification types that ALWAYS deliver — they bypass user preference and
10
+ * quiet hours. Migrated verbatim from `notification-handler.ts`'s
11
+ * `ALWAYS_DELIVER_TYPES`. This is the critical-always floor.
12
+ */
13
+ export const ALWAYS_DELIVER_TYPES = new Set(["SAFETY_ALERT", "PARENTAL_LINK"]);
14
+ /**
15
+ * Reproduce the existing `notification-handler.ts` quiet-hours check exactly.
16
+ *
17
+ * The core encodes `User.quietHoursStart/End` (minutes-since-midnight integers)
18
+ * as decimal strings in `QuietHoursConfig.start/end`, and derives the current
19
+ * minute from `ctx.now` (rather than an ambient `new Date()`), so the decision
20
+ * is deterministic. The wrap-around / same-day arithmetic mirrors
21
+ * `NotificationHandler.isInQuietHours`.
22
+ */
23
+ function isInQuietHours(quietHours, now) {
24
+ if (!quietHours || !quietHours.enabled)
25
+ return false;
26
+ const start = Number.parseInt(quietHours.start, 10);
27
+ const end = Number.parseInt(quietHours.end, 10);
28
+ if (!Number.isFinite(start) || !Number.isFinite(end))
29
+ return false;
30
+ const minutesSinceMidnight = now.getHours() * 60 + now.getMinutes();
31
+ // Overnight window (e.g. 22:00 -> 07:00).
32
+ if (start > end) {
33
+ return minutesSinceMidnight >= start || minutesSinceMidnight < end;
34
+ }
35
+ // Same-day window (e.g. 13:00 -> 15:00).
36
+ return minutesSinceMidnight >= start && minutesSinceMidnight < end;
37
+ }
38
+ /**
39
+ * The WS1 + Track D default resolver. Pure and synchronous over `DeliveryContext`
40
+ * (the caller does any async lookups and passes resolved signals in):
41
+ *
42
+ * 1. ALWAYS_DELIVER_TYPES (SAFETY_ALERT, PARENTAL_LINK) bypass everything ->
43
+ * `{ deliver: true }`. Safety must NOT be over-blocked: this wins over the
44
+ * blocked-sender and minor-protection floor below.
45
+ * 2. Non-configurable FLOOR (checked only when the caller supplies the input):
46
+ * - blocked sender: the caller resolves block-set membership async (via
47
+ * BlockStore) and populates `ctx.senderUserId` ONLY when the sender is
48
+ * blocked — so presence of `senderUserId` == "in the recipient's block
49
+ * set". The decision is a hard drop no preference can override.
50
+ * - minor-protection: a non-adult recipient (`recipientAgeTier` CHILD/TEEN)
51
+ * targeted by a configured re-engagement type is dropped.
52
+ * 3. Else honor preference (caller pre-resolves `deliver:false`/`preference`)
53
+ * and quiet hours.
54
+ *
55
+ * Note on preference: in the existing handler the type-preference check happens
56
+ * BEFORE creating the row (preference-off => no row at all), which is a
57
+ * different outcome from quiet-hours (row created with deliveredAt=null). The
58
+ * caller maps the decision reasons accordingly. To keep the resolver pure and
59
+ * total it accepts the preference outcome via `ctx` indirectly: the caller only
60
+ * invokes the resolver's quiet-hours path once preference has passed. For a
61
+ * single source of truth the resolver still exposes the full decision so a
62
+ * push-only caller (WS4) can gate solely on it.
63
+ */
64
+ export class CalmDeliveryResolver {
65
+ reengagementTypes;
66
+ constructor(config = {}) {
67
+ this.reengagementTypes =
68
+ config.reengagementTypes ?? new Set();
69
+ }
70
+ decide(ctx) {
71
+ // 1. Critical-always bypass — wins over the floor so safety is never
72
+ // over-blocked (a blocked sender or a minor still gets SAFETY_ALERT /
73
+ // PARENTAL_LINK).
74
+ if (ALWAYS_DELIVER_TYPES.has(ctx.type)) {
75
+ return { deliver: true };
76
+ }
77
+ // 2. Non-configurable floor.
78
+ // 2a. Blocked sender. The caller has already resolved block-set
79
+ // membership and only sets `senderUserId` when the sender IS blocked,
80
+ // so its presence is the deny signal. Hard drop, no override.
81
+ if (ctx.senderUserId !== undefined) {
82
+ return { deliver: false, reason: "blocked_sender" };
83
+ }
84
+ // 2b. Minor protection. A non-adult recipient targeted by a configured
85
+ // manipulative re-engagement type is dropped (FLOOR, not preference).
86
+ if (ctx.recipientAgeTier !== undefined &&
87
+ ctx.recipientAgeTier !== "ADULT" &&
88
+ this.reengagementTypes.has(ctx.type)) {
89
+ return { deliver: false, reason: "floor" };
90
+ }
91
+ // 3. Quiet hours (preference is handled by the caller; see class doc).
92
+ if (isInQuietHours(ctx.quietHours, ctx.now)) {
93
+ return { deliver: false, reason: "quiet_hours" };
94
+ }
95
+ return { deliver: true };
96
+ }
97
+ }
98
+ //# sourceMappingURL=delivery-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-policy.js","sourceRoot":"","sources":["../../../src/lib/realtime/delivery-policy.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,8EAA8E;AAC9E,sEAAsE;AACtE,wEAAwE;AASxE;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAkC,IAAI,GAAG,CAExE,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC;AAErC;;;;;;;;GAQG;AACH,SAAS,cAAc,CACrB,UAAyC,EACzC,GAAS;IAET,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnE,MAAM,oBAAoB,GAAG,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;IAEpE,0CAA0C;IAC1C,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QAChB,OAAO,oBAAoB,IAAI,KAAK,IAAI,oBAAoB,GAAG,GAAG,CAAC;IACrE,CAAC;IACD,yCAAyC;IACzC,OAAO,oBAAoB,IAAI,KAAK,IAAI,oBAAoB,GAAG,GAAG,CAAC;AACrE,CAAC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,OAAO,oBAAoB;IACd,iBAAiB,CAAgC;IAElE,YAAY,SAA8B,EAAE;QAC1C,IAAI,CAAC,iBAAiB;YACpB,MAAM,CAAC,iBAAiB,IAAI,IAAI,GAAG,EAAoB,CAAC;IAC5D,CAAC;IAED,MAAM,CAAC,GAAoB;QACzB,qEAAqE;QACrE,yEAAyE;QACzE,qBAAqB;QACrB,IAAI,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QAED,6BAA6B;QAC7B,mEAAmE;QACnE,6EAA6E;QAC7E,qEAAqE;QACrE,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;QACtD,CAAC;QACD,0EAA0E;QAC1E,6EAA6E;QAC7E,IACE,GAAG,CAAC,gBAAgB,KAAK,SAAS;YAClC,GAAG,CAAC,gBAAgB,KAAK,OAAO;YAChC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EACpC,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAC7C,CAAC;QAED,uEAAuE;QACvE,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QACnD,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;CACF"}