Skip to content

feat: add notification persistence architecture and contract tests - #2

Open
johngallagher wants to merge 10 commits into
mainfrom
feature/notification-persistence
Open

feat: add notification persistence architecture and contract tests#2
johngallagher wants to merge 10 commits into
mainfrom
feature/notification-persistence

Conversation

@johngallagher

Copy link
Copy Markdown
Contributor

Overview

Add comprehensive architecture for notification persistence across browser refreshes and client disconnections.

Changes

  • Define notification-delivery.v1 schema (SLOW layer)
  • Add Invariants 7-9 for persistence and per-location read status
  • Document Approach A (per-location, selected) vs Approach B (global, alternative) read status designs
  • Create durable evaluations for persistence contracts
  • Add contract_persistence_test.go with 5 new contract tests

Design Highlights

✅ Per-location read status (simple schema, matches multi-device workflows)
✅ Idempotent Record and MarkRead operations (safe retries, no duplicates)
✅ No breaking changes to existing APIs (new endpoints are additive)
✅ Forward-compatible with future requirements (global sync optional later)
✅ Fully specified slow/mid layer with durable evaluations

Architecture Files Updated

  • notifications_app_architecture.md — Added Invariants 7-9, new schemas, updated pace layers
  • PERSISTENCE_ARCHITECTURE.md — New comprehensive design document
  • evaluations/contract_persistence_test.go — New durable evaluations

Next Steps

  1. Implement notification-history service (SQLite schema + queries)
  2. Update delivery-service to persist notifications and handle mark-read
  3. Update web frontend to load unread notifications on page load
  4. Run contract tests to verify implementation

Schema Preview

CREATE TABLE notifications_delivered (
  user_id TEXT NOT NULL,
  location TEXT NOT NULL,
  notification_id TEXT NOT NULL,
  delivered_at TEXT NOT NULL,
  read_at TEXT,  -- NULL = unread
  source_app TEXT,
  title TEXT,
  body TEXT,
  
  UNIQUE(user_id, location, notification_id),
  INDEX(user_id, location, read_at, delivered_at)
);

Related Issue

Requirement: Notifications should persist across browser refresh. Users should only see notifications disappear when they explicitly mark them as read.

codescene-access[bot]

This comment was marked as outdated.

- Update notifications_app_architecture.md with persistence requirements
  * Add Invariants 7-9 for persistence and per-location read status
  * Add notification-delivery.v1 schema definition
  * Update component specs for delivery and notification-history services
  * Add contract and property tests for persistence
  * Document per-location vs global read status design (Approach A selected)
  * Update pace layers to include new slow/mid layer concerns
  * Add provenance record for persistence design decisions

- Add PERSISTENCE_ARCHITECTURE.md comprehensive design document
  * Three implementation approaches with trade-offs analysis
  * Recommended Approach A: per-location read status
  * Schema definition and implementation details
  * New durable evaluations for persistence contracts
  * Risk mitigation and non-goals
  * Implementation phases and backward compatibility

- Add contract_persistence_test.go durable evaluations
  * Test delivered notifications persist across browser refresh
  * Test mark-read removes from unread list
  * Test per-location read status independence
  * Test idempotent mark-read operation
  * Test idempotent record (no duplicates)
  * Helper functions for persistence API testing

Relates to: Notification persistence requirement for multi-session visibility
@johngallagher
johngallagher force-pushed the feature/notification-persistence branch from 074a5d9 to 8173200 Compare August 5, 2026 23:19
codescene-access[bot]

This comment was marked as outdated.

- Add notification-history navigation link
- Add full notification-history component pane with behavioral examples
- Add INV-7, INV-8, INV-9 for persistence contracts
- Include SQL schema, contract tests, and provenance record
- Integrated into existing component documentation structure
codescene-access[bot]

This comment was marked as outdated.

- Add Invariants 9-10: mark-read publishes events, read events broadcast to all locations
- Define notification-read.v1 event schema (event_id, user_id, notification_id, location, read_at)
- Add notifications.read Pub/Sub topic for cross-device event broadcasting
- Update notification-history to publish read events via Pub/Sub
- Update delivery-service to subscribe to notifications.read and broadcast to SSE clients
- Add contract test: TestContract_ReadEventBroadcasts
- Document event-driven cross-device synchronization without requiring global read status
- Update PERSISTENCE_ARCHITECTURE.md with event publishing contract and client-side handling
- Add Invariant 10: read events broadcast to all locations
- Document notification-read event schema
- Add cross-device synchronization explanation
- Update navigation to reference read events
- Explain why per-location storage + event broadcast works
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

- history pane now properly integrated into navigation system
- Added to ORDER array (between delivery and system-properties)
- Added to LABELS mapping for breadcrumb navigation
codescene-access[bot]

This comment was marked as outdated.

Key fixes:
- Updated Approach A description to explain that events broadcast read changes
  (eliminating 'extra clicks' language that was outdated)
- Clarified that storage is per-location but UX is synchronized via events
- Fixed INV-8 description to acknowledge Invariant 10 event broadcasting
- Updated Approach B disadvantages to note schema complexity vs event sync
- Updated provenance record to explain why per-location + events is better
  than global read status in database
- Fixed Phase 3 implementation notes to acknowledge that cross-device sync
  is already provided by Invariant 10 (read events)
- Clarified in PERSISTENCE_ARCHITECTURE.md that Approach A rationale includes
  real-time UX via events and eventual consistency via per-location storage

These changes ensure consistency between:
- The new Invariants 9-10 (mark-read + event broadcasting)
- The Approach A selection (per-location storage)
- The documented user experience (immediate hiding on all connected devices)
- The provenance and design rationale
Additional consistency fixes:

- Updated PERSISTENCE_ARCHITECTURE.md contract item 5 to explain that:
  * Storage is per-location (independent read_at per device)
  * But read events broadcast to connected clients (Invariant 10)
  * Offline devices see correct state when reconnected

- Updated Approach A title to include '+ Event Broadcasting'

- Updated Philosophy statement for Approach A to mention
  real-time broadcast via events

- Updated test comments to clarify 'per-location in storage'
  vs 'events broadcast to connected clients' distinction

These changes ensure that nowhere in the documentation does it suggest
that marking read on one device leaves other connected devices unaffected.
The events (Invariant 10) guarantee real-time synchronization for
connected clients, while per-location storage ensures eventual
consistency for reconnected devices.
codescene-access[bot]

This comment was marked as outdated.

- Add notification-delivery.v1 schema (persistence layer)
- Add notification-read.v1 schema (cross-device sync events)
- Create unified Pub/Sub topics and events table
- Add publishing constraints to clarify ownership
- Document semantics for all event types
- Update table to show all 5 topics and their message types
- Reference Invariant 10 from notification-read event description
codescene-access[bot]

This comment was marked as outdated.

- Convert RuleChangedEvent from Go code to JSON schema (rule-changed.v1)
- Show enum values: CREATED | UPDATED | DELETED
- Document all 5 event schemas in consistent JSON format:
  * notification.v1 (notifications on all topics)
  * rule-changed.v1 (rules.changed topic)
  * notification-delivery.v1 (persistence)
  * notification-read.v1 (cross-device sync)
- Update table header 'Message Type' → 'Message Schema'
- Update table reference 'RuleChangedEvent' → 'rule-changed.v1'
- Add schema versioning semantics section
- Clarify backward compatibility rules for all schemas
codescene-access[bot]

This comment was marked as outdated.

Schema heading format is now: schema-name.v1 (Description)

Changes:
- notification.v1 (Notification Message)
- rule.v1 (Rule Schema)  [was: Rule schema]
- rule-changed.v1 (Rule Lifecycle)
- notification-delivery.v1 (Delivery Status & Persistence)
- notification-read.v1 (Cross-Device Sync)

Also:
- Removed duplicate notification-read event documentation from history pane
- Kept single authoritative version in contracts pane
- Updated history pane to reference contracts section for full schema
- Updated navigation label: 'notification-history & read events' → 'notification-history'
- Added callout explaining per-location storage + event-driven broadcast pattern

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Passed
6 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

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.

1 participant