Skip to content

Commit 88ada0c

Browse files
arpandhakalclaude
andcommitted
chore(notifications): ship IU notifications ungated for now
Stop attaching notificationSettingId (set undefined; resolve calls commented) so IUs receive all email + in-product notifications without platform gating. Per-IU gating is blocked on Copilot exposing a preference-read endpoint; re-enable by restoring the commented resolve calls (and implementing flush-time preference filtering). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b64baa5 commit 88ada0c

3 files changed

Lines changed: 20 additions & 41 deletions

File tree

‎src/app/api/notification/notification.service.test.ts‎

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -432,20 +432,21 @@ describe('guard: IU wiring boundaries', () => {
432432
})
433433
})
434434

435-
describe('guard: IU notification setting gating', () => {
436-
it('resolves the id by action category and attaches it to both the buffered email and the in-product dispatch', async () => {
435+
describe('guard: IU notifications ship ungated (settingId gating disabled)', () => {
436+
it('does not attach notificationSettingId to the in-product dispatch or the buffered email', async () => {
437437
const task = makeTask({ assigneeType: AssigneeType.internalUser, clientId: null })
438438
await buildService().create(NotificationTaskActions.Assigned, task, { disableEmail: false })
439439

440-
expect(mockGroupedCreateMany.mock.calls[0][0].data[0].individualEmail.notificationSettingId).toBe('setting_assigned')
441-
expect(mockCreateNotification.mock.calls[0][0].notificationSettingId).toBe('setting_assigned')
440+
expect(mockCreateNotification.mock.calls[0][0].notificationSettingId).toBeUndefined()
441+
expect(mockGroupedCreateMany.mock.calls[0][0].data[0].individualEmail.notificationSettingId).toBeUndefined()
442442
})
443443

444-
it('resolves a different id per category (COMPLETED vs ASSIGNED)', async () => {
444+
it('still buffers the IU email and fires the in-product notification', async () => {
445445
const task = makeTask({ assigneeType: AssigneeType.internalUser, clientId: null })
446-
await buildService().create(NotificationTaskActions.CompletedByIU, task, { disableEmail: false })
446+
await buildService().create(NotificationTaskActions.Assigned, task, { disableEmail: false })
447447

448-
expect(mockCreateNotification.mock.calls[0][0].notificationSettingId).toBe('setting_completed')
448+
expect(mockGroupedCreateMany).toHaveBeenCalledTimes(1)
449+
expect(deliveryTargetsOf(0).inProduct).toBeDefined()
449450
})
450451

451452
it('keeps IU grouped windows cross-category (window key is not scoped by event type)', async () => {
@@ -455,17 +456,6 @@ describe('guard: IU notification setting gating', () => {
455456
expect(mockGroupedCreateMany.mock.calls[0][0].data[0].windowKey).toMatch(new RegExp(`^${task.assigneeId}:iu:[^:]+$`))
456457
})
457458

458-
it('still buffers the IU email but attaches no id when the category is not declared (gating deferred to flush)', async () => {
459-
mockGetNotificationSettings.mockResolvedValue({ notifications: [] })
460-
const task = makeTask({ assigneeType: AssigneeType.internalUser, clientId: null })
461-
await buildService().create(NotificationTaskActions.Assigned, task, { disableEmail: false })
462-
463-
expect(mockGroupedCreateMany).toHaveBeenCalledTimes(1)
464-
expect(mockGroupedCreateMany.mock.calls[0][0].data[0].individualEmail.notificationSettingId).toBeUndefined()
465-
expect(mockCreateNotification.mock.calls[0][0].notificationSettingId).toBeUndefined()
466-
expect(deliveryTargetsOf(0).inProduct).toBeDefined()
467-
})
468-
469459
it('treats a suppressed (null) createNotification response as a no-op — no throw, no save', async () => {
470460
// Platform dropped the only requested surface for this IU (preference off) → no created object.
471461
mockCreateNotification.mockResolvedValue(null)

‎src/app/api/notification/notification.service.ts‎

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import APIError from '@api/core/exceptions/api'
1414
import { BaseService } from '@api/core/services/base.service'
1515
import { NotificationTaskActions } from '@api/core/types/tasks'
1616
import { getEmailDetails, getInProductNotificationDetails, mergeEmailOverride } from '@api/notification/notification.helpers'
17-
import { resolveIuNotificationSettingId } from '@api/notification/resolveNotificationSettingId'
17+
// import { resolveIuNotificationSettingId } from '@api/notification/resolveNotificationSettingId'
1818
import { AssigneeType, ClientNotification, GroupedEmailEventType, Prisma, Task } from '@prisma/client'
1919
import { randomUUID } from 'crypto'
2020
import { enqueueGroupedEmailFlush } from '@/jobs/notifications/flush-grouped-email'
@@ -74,13 +74,9 @@ export class NotificationService extends BaseService {
7474
const email = baseEmail ? mergeEmailOverride({ base: baseEmail, override: opts.emailOverride }) : baseEmail
7575

7676
const category = this.groupedEventTypeFor(action)
77-
// IU sends carry the category's setting id. It gates the in-product surface per the IU's
78-
// preference immediately, and rides along on the buffered email so a single-category grouped
79-
// flush can gate the email too (see flush-grouped-email).
80-
const notificationSettingId =
81-
isRecipientIu && category
82-
? await resolveIuNotificationSettingId({ copilot: this.copilot, workspaceId: task.workspaceId, category })
83-
: undefined
77+
// Gating disabled until Copilot exposes a per-IU preference read endpoint — ship IUs ungated.
78+
const notificationSettingId = undefined
79+
// const notificationSettingId = isRecipientIu && category ? await resolveIuNotificationSettingId({ copilot: this.copilot, workspaceId: task.workspaceId, category }) : undefined
8480

8581
const groupedType = email && recipientId ? category : null
8682
if (groupedType) {
@@ -210,12 +206,9 @@ export class NotificationService extends BaseService {
210206
const association = AssociationsSchema.parse(task.associations)?.[0]
211207
const category = this.groupedEventTypeFor(action)
212208
const isRecipientIu = opts.isRecipientIu
213-
// Resolve once per batch (not per recipient). The id gates the in-product surface per IU and
214-
// rides along on the buffered email for the flush-time single-category gate (see create()).
215-
const notificationSettingId =
216-
isRecipientIu && category
217-
? await resolveIuNotificationSettingId({ copilot: this.copilot, workspaceId: task.workspaceId, category })
218-
: undefined
209+
// Gating disabled until Copilot exposes a per-IU preference read endpoint — ship IUs ungated.
210+
const notificationSettingId = undefined
211+
// const notificationSettingId = isRecipientIu && category ? await resolveIuNotificationSettingId({ copilot: this.copilot, workspaceId: task.workspaceId, category }) : undefined
219212
// Non-null only when these emails should be diverted into the grouped buffer.
220213
const groupedType = email ? category : null
221214

‎src/jobs/notifications/send-reply-create-notifications.ts‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { isMessagableError } from '@/utils/copilotError'
66
import { CommentRepository } from '@/app/api/comments/comment.repository'
77
import { CommentService } from '@/app/api/comments/comment.service'
88
import { NotificationService } from '@/app/api/notification/notification.service'
9-
import { resolveIuNotificationSettingId } from '@/app/api/notification/resolveNotificationSettingId'
9+
// import { resolveIuNotificationSettingId } from '@/app/api/notification/resolveNotificationSettingId'
1010
import User from '@api/core/models/User.model'
1111
import { TasksService } from '@api/tasks/tasks.service'
1212
import { Comment, CommentInitiator, GroupedEmailEventType, Task } from '@prisma/client'
@@ -49,14 +49,10 @@ export const sendReplyCreateNotifications = task({
4949

5050
const deliveryTargets = await getNotificationDetails(copilot, user, comment)
5151

52-
// Replies are the COMMENT category. Reply emails are buffered as COMMENT grouped events (like
53-
// top-level comments), and IU sends carry this setting id so the platform gates each surface per
54-
// the recipient IU's preference.
55-
const notificationSettingId = await resolveIuNotificationSettingId({
56-
copilot,
57-
workspaceId: user.workspaceId,
58-
category: GroupedEmailEventType.COMMENT,
59-
})
52+
// Replies are buffered as COMMENT grouped events like top-level comments.
53+
// Gating disabled until Copilot exposes a per-IU preference read endpoint — ship IUs ungated.
54+
const notificationSettingId = undefined
55+
// const notificationSettingId = await resolveIuNotificationSettingId({ copilot, workspaceId: user.workspaceId, category: GroupedEmailEventType.COMMENT })
6056

6157
const notificationPromises: Promise<unknown>[] = []
6258
const queueNotificationPromise = (promise: Promise<unknown>): void => {

0 commit comments

Comments
 (0)