Conversation
Greptile SummaryThis PR wires a complete durable notification foundation into the lifecycle loop: a SQLite-backed
Confidence Score: 4/5The new notification pipeline is well-structured and the idempotency/dedupe design is sound, but the filter switch in ListNotifications silently discards the session/project dimension whenever UnreadOnly is set, which will produce wrong results the first time an API handler uses both together. The overall design is solid — migration, triggers, renderer, dedupe, and enqueuer all hold together, and concurrent enqueue is protected by the write mutex with correct ErrNoRows handling. The one behaviour gap is ListNotifications: the switch gives UnreadOnly unconditional priority over SessionID / ProjectID, so a caller expecting unread notifications for session X silently gets all unread notifications. No current external endpoint exercises the combined filter, but the NotificationFilter struct invites it and there is no guard or documentation warning against it. backend/internal/storage/sqlite/notification_store.go (ListNotifications filter precedence); backend/internal/notification/renderer.go (IsBehind field never populated) Important Files Changed
Sequence DiagramsequenceDiagram
participant LCM as lifecycle.Manager
participant Enqueuer as notification.Enqueuer
participant Renderer as notification.Renderer
participant Store as sqlite.Store
participant CDC as change_log (trigger)
LCM->>Enqueuer: "Notify(ctx, ports.Event{Reaction, Escalation, CauseKey})"
Enqueuer->>Renderer: Render(ctx, event)
Renderer->>Store: GetSession(ctx, sessionID)
Store-->>Renderer: SessionRecord
Renderer->>Store: PRFactsForSession(ctx, sessionID)
Store-->>Renderer: PRFacts
Renderer-->>Enqueuer: domain.Notification (with DedupeKey, Payload JSON)
Enqueuer->>Store: EnqueueNotification(ctx, row)
Store->>Store: INSERT … ON CONFLICT (dedupe_key) DO NOTHING
alt new row inserted
Store-->>Enqueuer: "(row, created=true, nil)"
Store->>CDC: AFTER INSERT trigger → notification_created
else duplicate dedupe_key
Store->>Store: GetNotificationByDedupeKey(dedupe_key)
Store-->>Enqueuer: "(existing row, created=false, nil)"
end
Enqueuer-->>LCM: nil
|
Closes #54\n\n## Summary\n- Add provider-neutral notification domain, renderer, dedupe, and store-backed enqueuer\n- Add SQLite notifications table, idempotent enqueue/read/archive APIs, CDC triggers, and sqlc-generated queries\n- Wire lifecycle notifications to durable persistence and add lifecycle/storage/renderer/dedupe tests\n\n## Validation\n- cd backend && go test ./...\n- cd backend && go vet ./...