Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
17b405f
docs(notification): design durable reminder persistence
seonghobae Aug 4, 2026
b2ecd8e
docs(notification): plan durable reminder persistence
seonghobae Aug 4, 2026
d609461
test(notification): define durable reminder schema contract
seonghobae Aug 4, 2026
da0fe0d
feat(notification): add durable reminder schema
seonghobae Aug 4, 2026
71ffdf2
test(notification): make migration contract formatting-independent
seonghobae Aug 4, 2026
c8a7ee2
test(notification): define PostgreSQL repository contract
seonghobae Aug 4, 2026
8e39aea
feat(notification): add durable reminder repository
seonghobae Aug 4, 2026
d8ace55
feat(notification): export PostgreSQL reminder adapters
seonghobae Aug 4, 2026
0b08990
feat(notification): complete durable reminder repository contract
seonghobae Aug 4, 2026
de0fb28
feat(notification): export durable notification contracts
seonghobae Aug 4, 2026
b7ddc16
build(notification): add PostgreSQL runtime dependencies
seonghobae Aug 4, 2026
97bf1de
feat(notification): wire PostgreSQL reminder runtime
seonghobae Aug 4, 2026
9c3d56c
test(notification): verify PostgreSQL runtime ownership
seonghobae Aug 4, 2026
8957698
feat(notification): export production runtime
seonghobae Aug 4, 2026
5ad51d8
feat(notification): harden durable reminder schema
seonghobae Aug 4, 2026
6e5cd64
chore(notification): scope formatting to supported sources
seonghobae Aug 4, 2026
faa86e6
test(notification): verify PostgreSQL durability and concurrency
seonghobae Aug 4, 2026
1502f15
ci(notification): run durable reminder integration tests
seonghobae Aug 4, 2026
8ca4d86
test(notification): include PostgreSQL integration suite
seonghobae Aug 4, 2026
627aacb
docs(notification): document runtime configuration
seonghobae Aug 4, 2026
216f7cf
docs(notification): correct bounded policy horizon
seonghobae Aug 4, 2026
40e2c71
docs(notification): add persistence operations runbook
seonghobae Aug 4, 2026
2554c71
docs(notification): record durable inbox persistence
seonghobae Aug 4, 2026
38bbf65
chore(notification): verify all slice documentation
seonghobae Aug 4, 2026
1568a9a
docs(notification): register durable inbox evidence
seonghobae Aug 4, 2026
18bb03a
test(notification): complete durable transition evidence
seonghobae Aug 4, 2026
82c9461
test(notification): isolate transition scenarios deterministically
seonghobae Aug 4, 2026
fd60303
Merge branch 'main' into feat/notification-postgres-inbox
github-actions[bot] Aug 4, 2026
dcdd2a9
ci(notification): expose formatting diff for repair
seonghobae Aug 4, 2026
a89d44d
ci: format notification persistence slice
seonghobae Aug 4, 2026
bf5cf75
ci: format and harden notification persistence
seonghobae Aug 4, 2026
ebefcd8
ci(notification): restore side-effect-free formatting gate
seonghobae Aug 4, 2026
c1551c1
chore(ci): remove temporary notification formatter
seonghobae Aug 4, 2026
5a65474
chore(ci): replace temporary persistence formatter
seonghobae Aug 4, 2026
bdc00c8
ci: retry notification persistence formatting
seonghobae Aug 4, 2026
99be086
ci: fence notification claims and format persistence
seonghobae Aug 4, 2026
3498097
chore(ci): move notification repair into visible CI
seonghobae Aug 4, 2026
fdf62f8
ci: add temporary notification repair script
seonghobae Aug 4, 2026
00dcee5
ci: run reviewed notification repair
seonghobae Aug 4, 2026
dc663da
ci: make notification repair self-cleaning
seonghobae Aug 4, 2026
0d774ef
ci(notification): let reviewed repair update the feature branch
seonghobae Aug 4, 2026
ad3c534
fix(notification): fence expiring reminder claims
github-actions[bot] Aug 4, 2026
182233a
ci(notification): remove completed repair automation
seonghobae Aug 4, 2026
5df4da7
test(notification): enforce complete coverage
seonghobae Aug 4, 2026
0049c6a
test(notification): configure coverage thresholds
seonghobae Aug 4, 2026
40dd07b
test(notification): cover public package exports
seonghobae Aug 4, 2026
885e144
ci(notification): propagate PostgreSQL integration configuration
seonghobae Aug 4, 2026
915e888
test(notification): expand coverage diagnostics
seonghobae Aug 4, 2026
2d835c9
test(notification): require complete source documentation
seonghobae Aug 4, 2026
cb48382
test(notification): lint documentation contract
seonghobae Aug 4, 2026
08a5839
chore(notification): stage complete source documentation
seonghobae Aug 4, 2026
31d74ef
ci(notification): apply reviewed reliability repairs
seonghobae Aug 4, 2026
66b4bbf
ci(notification): apply reviewed documentation pass
seonghobae Aug 4, 2026
420af2b
test(notification): use explicit TypeScript namespace import
seonghobae Aug 4, 2026
1867224
test(notification): exercise production PostgreSQL runtime
seonghobae Aug 4, 2026
e3c31bb
test(notification): cover runtime configuration defenses
seonghobae Aug 4, 2026
dac572c
ci(notification): stage reliability repair script
seonghobae Aug 4, 2026
45fea9a
ci(notification): trigger reviewed reliability repair
seonghobae Aug 4, 2026
a4077a0
test(notification): cover scheduler defensive boundaries
seonghobae Aug 4, 2026
b833773
test(notification): include production runtime integration source
seonghobae Aug 4, 2026
f0334eb
ci(notification): retire superseded push repair
seonghobae Aug 4, 2026
c2a42db
ci(notification): preserve active reliability repair
seonghobae Aug 4, 2026
4253add
ci(notification): sequence reliability before documentation repair
seonghobae Aug 4, 2026
8adbe72
ci(notification): stage reliability docstring repair
seonghobae Aug 4, 2026
3bcbedb
ci(notification): validate reliability documentation
seonghobae Aug 4, 2026
64f0fe9
ci(notification): format the complete repair surface
seonghobae Aug 4, 2026
714ab5f
ci(notification): separate reliability and docstring gates
seonghobae Aug 4, 2026
3d02317
test(notification): keep policy errors outside persistence isolation
seonghobae Aug 4, 2026
9151e9b
ci(notification): refine persistence failure boundaries
seonghobae Aug 4, 2026
acdae97
fix(notification): isolate persistence and pool failures
github-actions[bot] Aug 4, 2026
fec8e7e
docs(notification): document every production declaration
github-actions[bot] Aug 4, 2026
abbb388
ci(notification): serialize documentation before validation
seonghobae Aug 4, 2026
97a2339
chore(ci): stage final notification review repair
seonghobae Aug 4, 2026
d92043d
chore(notification): queue final review repairs
seonghobae Aug 4, 2026
e2a6a91
chore(ci): validate repair before enforcing coverage
seonghobae Aug 4, 2026
a9e87ef
test(notification): claim the observed runtime row version
seonghobae Aug 4, 2026
66647b9
docs(notification): stage complete declaration documentation
seonghobae Aug 4, 2026
a5b561b
ci(notification): complete review and declaration documentation together
seonghobae Aug 4, 2026
ab8b96b
test(notification): enforce declaration documentation across all sources
seonghobae Aug 4, 2026
fc41596
docs(notification): document runtime pool factory fixture
seonghobae Aug 4, 2026
d86dbf3
fix(notification): recognize JSDoc on variable statements
seonghobae Aug 4, 2026
83bfae1
test(notification): remove unreachable coverage branches
seonghobae Aug 4, 2026
fec494f
test(notification): close defensive coverage gaps
seonghobae Aug 4, 2026
c0e28c2
ci: use preinstalled PostgreSQL for resilient validation
seonghobae Aug 4, 2026
06ff470
ci: commit generated source changes without workflow mutation
seonghobae Aug 4, 2026
9a21c78
fix(notification): close review and documentation gaps
github-actions[bot] Aug 4, 2026
77aa649
ci: remove temporary notification repair automation
seonghobae Aug 4, 2026
0833e36
docs(notification): remove duplicate idempotency comment
seonghobae Aug 4, 2026
23d1afd
fix(notification): isolate delivered count persistence failures
seonghobae Aug 4, 2026
d21dbb9
test(notification): stage final review regressions
seonghobae Aug 4, 2026
c14ba8a
test(notification): cover delivered count failure isolation
seonghobae Aug 4, 2026
db82f31
ci: run final notification review repair
seonghobae Aug 4, 2026
48fff3a
fix(ci): normalize the temporary review fixture
seonghobae Aug 4, 2026
035211b
ci(notification): apply reviewed repair without redundant red phase
seonghobae Aug 4, 2026
6160e2d
fix(notification): address final reliability review
github-actions[bot] Aug 4, 2026
95bc933
docs(notification): document failure isolation and shutdown retry
seonghobae Aug 4, 2026
ce2c037
docs(changelog): record final notification reliability fixes
seonghobae Aug 4, 2026
06e6828
ci: restore trusted base workflow for notification checks
seonghobae Aug 4, 2026
3018270
test(notification): reuse the existing CI PostgreSQL URL
seonghobae Aug 4, 2026
04677c2
ci(notification): restore notification database test boundary
seonghobae Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ REVIEW_SERVICE_PORT=4104
CALENDAR_SERVICE_PORT=4106
INTEGRATION_SERVICE_PORT=4107
DATABASE_URL=postgresql://lifeos:lifeos@postgres:5432/lifeos
NOTIFICATION_DATABASE_URL=postgresql://lifeos:lifeos@postgres:5432/lifeos
NOTIFICATION_DATABASE_POOL_MAX=10
NOTIFICATION_DATABASE_CONNECT_TIMEOUT_MS=5000
NOTIFICATION_DATABASE_IDLE_TIMEOUT_MS=30000
NOTIFICATION_CLAIM_LEASE_SECONDS=300
NOTIFICATION_REMINDER_BATCH_SIZE=50
NATS_URL=nats://nats:4222
CORS_ALLOWED_ORIGINS=http://localhost:3000
IDENTITY_SERVICE_ORIGIN=http://127.0.0.1:4101
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
IDENTITY_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/life_os_test
PLANNING_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/life_os_test
HABIT_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/life_os_test
NOTIFICATION_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/life_os_test
services:
postgres:
image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ All notable changes to LifeOS are documented in this file.
- An accessible quick-capture and search surface that keeps browser-local Today drafts visibly separate from durable workspace records.
- Complete English and Korean message catalogs, a persisted keyboard-operable language selector, localized live-region announcements, and accessibility browser journeys for the Today action loop.
- A bounded notification scheduler with IANA time-zone quiet hours, per-local-day fatigue limits, tenant-scoped atomic claims, idempotent delivery keys, and credential-free retry outcomes.
- Durable PostgreSQL reminder occurrences, expiring worker claims, immutable scheduler outcomes, and an idempotent in-app inbox in the independent `notification_service` schema.
- A bounded notification runtime that composes one PostgreSQL pool, the reminder repository, the in-app gateway, and the scheduler with exactly-once pool shutdown.

### Fixed

- Planning search now normalizes browser query text and prevents stale or unmounted requests from replacing the latest visible result state.
- Reminder fatigue deferral now crosses long IANA offset fallbacks and next-day quiet hours without abandoning the claimed occurrence.
- Notification workers now recover expired claims and exact delivery replays without creating duplicate inbox messages.
- Notification batches now isolate delivery-count persistence failures, issue a distinct token for each claim attempt, share concurrent shutdown work, and emit bounded credential-free PostgreSQL failure classifications.

### Security

- Planning-search upstream responses are stopped at a fixed byte limit before they can be fully buffered by the web boundary.
- Notification persistence stores SHA-256 idempotency digests instead of raw delivery keys, validates every untrusted row, and keeps all SQL tenant-scoped and parameterized.
269 changes: 269 additions & 0 deletions apps/notification-service/migrations/0001_durable_reminder_inbox.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
CREATE SCHEMA IF NOT EXISTS notification_service;

CREATE TABLE IF NOT EXISTS notification_service.reminder_occurrences (
reminder_id uuid NOT NULL,
workspace_id uuid NOT NULL,
reminder_title text NOT NULL,
due_instant timestamptz NOT NULL,
time_zone text NOT NULL,
quiet_start_minute smallint,
quiet_end_minute smallint,
daily_delivery_limit smallint NOT NULL,
delivery_attempt_count smallint NOT NULL DEFAULT 0,
occurrence_status text NOT NULL DEFAULT 'pending',
claim_key_hash bytea,
claim_expires_at timestamptz,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
CONSTRAINT reminder_occurrences_primary_key PRIMARY KEY (reminder_id),
CONSTRAINT reminder_occurrences_workspace_reminder_unique
UNIQUE (workspace_id, reminder_id),
CONSTRAINT reminder_occurrences_id_uuid_v4 CHECK (
get_byte(uuid_send(reminder_id), 6) >> 4 = 4
AND get_byte(uuid_send(reminder_id), 8) >> 6 = 2
),
CONSTRAINT reminder_occurrences_workspace_uuid_v4 CHECK (
get_byte(uuid_send(workspace_id), 6) >> 4 = 4
AND get_byte(uuid_send(workspace_id), 8) >> 6 = 2
),
CONSTRAINT reminder_occurrences_title_bounds CHECK (
char_length(reminder_title) BETWEEN 1 AND 160
AND octet_length(reminder_title) <= 1024
AND reminder_title = btrim(reminder_title)
AND reminder_title !~ '[[:cntrl:]]'
),
CONSTRAINT reminder_occurrences_timezone_bounds CHECK (
char_length(time_zone) BETWEEN 1 AND 64
AND octet_length(time_zone) <= 256
AND time_zone = btrim(time_zone)
AND time_zone !~ '[[:cntrl:]]'
),
CONSTRAINT reminder_occurrences_quiet_pair CHECK (
(quiet_start_minute IS NULL AND quiet_end_minute IS NULL)
OR (
quiet_start_minute BETWEEN 0 AND 1439
AND quiet_end_minute BETWEEN 0 AND 1439
AND quiet_start_minute <> quiet_end_minute
)
),
CONSTRAINT reminder_occurrences_daily_limit CHECK (
daily_delivery_limit BETWEEN 1 AND 20
),
CONSTRAINT reminder_occurrences_attempt_limit CHECK (
delivery_attempt_count BETWEEN 0 AND 3
),
CONSTRAINT reminder_occurrences_status_values CHECK (
occurrence_status IN ('pending', 'delivered', 'failed')
),
CONSTRAINT reminder_occurrences_claim_pair CHECK (
(claim_key_hash IS NULL AND claim_expires_at IS NULL)
OR (
claim_key_hash IS NOT NULL
AND claim_expires_at IS NOT NULL
AND octet_length(claim_key_hash) = 32
)
),
CONSTRAINT reminder_occurrences_terminal_claim CHECK (
occurrence_status = 'pending'
OR (claim_key_hash IS NOT NULL AND claim_expires_at IS NOT NULL)
),
CONSTRAINT reminder_occurrences_timestamp_order CHECK (
updated_at >= created_at
)
);

CREATE INDEX IF NOT EXISTS reminder_occurrences_due_index
ON notification_service.reminder_occurrences (
due_instant ASC,
reminder_id ASC
)
WHERE occurrence_status = 'pending';

CREATE INDEX IF NOT EXISTS reminder_occurrences_claim_expiry_index
ON notification_service.reminder_occurrences (
claim_expires_at ASC,
workspace_id ASC,
reminder_id ASC
)
WHERE occurrence_status = 'pending' AND claim_key_hash IS NOT NULL;

CREATE INDEX IF NOT EXISTS reminder_occurrences_workspace_index
ON notification_service.reminder_occurrences (
workspace_id,
created_at DESC,
reminder_id ASC
);

CREATE TABLE IF NOT EXISTS notification_service.reminder_outcomes (
outcome_id uuid NOT NULL,
workspace_id uuid NOT NULL,
reminder_id uuid NOT NULL,
outcome_kind text NOT NULL,
occurred_at timestamptz NOT NULL,
next_attempt_at timestamptz,
outcome_reason text,
idempotency_key_hash bytea NOT NULL,
delivery_local_date date,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
CONSTRAINT reminder_outcomes_primary_key PRIMARY KEY (outcome_id),
CONSTRAINT reminder_outcomes_id_uuid_v4 CHECK (
get_byte(uuid_send(outcome_id), 6) >> 4 = 4
AND get_byte(uuid_send(outcome_id), 8) >> 6 = 2
),
CONSTRAINT reminder_outcomes_workspace_uuid_v4 CHECK (
get_byte(uuid_send(workspace_id), 6) >> 4 = 4
AND get_byte(uuid_send(workspace_id), 8) >> 6 = 2
),
CONSTRAINT reminder_outcomes_reminder_uuid_v4 CHECK (
get_byte(uuid_send(reminder_id), 6) >> 4 = 4
AND get_byte(uuid_send(reminder_id), 8) >> 6 = 2
),
CONSTRAINT reminder_outcomes_kind_values CHECK (
outcome_kind IN ('delivered', 'deferred', 'failed')
),
CONSTRAINT reminder_outcomes_reason_values CHECK (
outcome_reason IS NULL
OR outcome_reason IN (
'quiet_hours',
'daily_limit',
'delivery_failed',
'attempt_limit'
)
),
CONSTRAINT reminder_outcomes_hash_length CHECK (
octet_length(idempotency_key_hash) = 32
),
CONSTRAINT reminder_outcomes_state_consistency CHECK (
(
outcome_kind = 'delivered'
AND outcome_reason IS NULL
AND next_attempt_at IS NULL
AND delivery_local_date IS NOT NULL
)
OR (
outcome_kind = 'deferred'
AND outcome_reason IN ('quiet_hours', 'daily_limit')
AND next_attempt_at IS NOT NULL
AND delivery_local_date IS NULL
)
OR (
outcome_kind = 'failed'
AND delivery_local_date IS NULL
AND (
(
outcome_reason = 'delivery_failed'
AND next_attempt_at IS NOT NULL
)
OR (
outcome_reason = 'attempt_limit'
AND next_attempt_at IS NULL
)
)
)
),
CONSTRAINT reminder_outcomes_occurrence_foreign_key
FOREIGN KEY (workspace_id, reminder_id)
REFERENCES notification_service.reminder_occurrences (workspace_id, reminder_id)
ON DELETE RESTRICT,
CONSTRAINT reminder_outcomes_idempotency_unique
UNIQUE (workspace_id, idempotency_key_hash, outcome_kind)
);

CREATE INDEX IF NOT EXISTS reminder_outcomes_workspace_index
ON notification_service.reminder_outcomes (
workspace_id,
occurred_at DESC,
outcome_id ASC
);

CREATE INDEX IF NOT EXISTS reminder_outcomes_delivery_date_index
ON notification_service.reminder_outcomes (
workspace_id,
delivery_local_date
)
WHERE outcome_kind = 'delivered';

CREATE TABLE IF NOT EXISTS notification_service.inbox_messages (
message_id uuid NOT NULL,
workspace_id uuid NOT NULL,
reminder_id uuid NOT NULL,
message_title text NOT NULL,
due_instant timestamptz NOT NULL,
time_zone text NOT NULL,
idempotency_key_hash bytea NOT NULL,
delivered_at timestamptz NOT NULL,
read_at timestamptz,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
CONSTRAINT inbox_messages_primary_key PRIMARY KEY (message_id),
CONSTRAINT inbox_messages_id_uuid_v4 CHECK (
get_byte(uuid_send(message_id), 6) >> 4 = 4
AND get_byte(uuid_send(message_id), 8) >> 6 = 2
),
CONSTRAINT inbox_messages_workspace_uuid_v4 CHECK (
get_byte(uuid_send(workspace_id), 6) >> 4 = 4
AND get_byte(uuid_send(workspace_id), 8) >> 6 = 2
),
CONSTRAINT inbox_messages_reminder_uuid_v4 CHECK (
get_byte(uuid_send(reminder_id), 6) >> 4 = 4
AND get_byte(uuid_send(reminder_id), 8) >> 6 = 2
),
CONSTRAINT inbox_messages_title_bounds CHECK (
char_length(message_title) BETWEEN 1 AND 160
AND octet_length(message_title) <= 1024
AND message_title = btrim(message_title)
AND message_title !~ '[[:cntrl:]]'
),
CONSTRAINT inbox_messages_timezone_bounds CHECK (
char_length(time_zone) BETWEEN 1 AND 64
AND octet_length(time_zone) <= 256
AND time_zone = btrim(time_zone)
AND time_zone !~ '[[:cntrl:]]'
),
CONSTRAINT inbox_messages_hash_length CHECK (
octet_length(idempotency_key_hash) = 32
),
CONSTRAINT inbox_messages_read_order CHECK (
read_at IS NULL OR read_at >= delivered_at
),
CONSTRAINT inbox_messages_timestamp_order CHECK (
updated_at >= created_at
),
CONSTRAINT inbox_messages_occurrence_foreign_key
FOREIGN KEY (workspace_id, reminder_id)
REFERENCES notification_service.reminder_occurrences (workspace_id, reminder_id)
ON DELETE RESTRICT,
CONSTRAINT inbox_messages_idempotency_unique
UNIQUE (workspace_id, idempotency_key_hash)
);

CREATE INDEX IF NOT EXISTS inbox_messages_workspace_index
ON notification_service.inbox_messages (
workspace_id,
delivered_at DESC,
message_id ASC
);

CREATE OR REPLACE FUNCTION notification_service.reject_reminder_outcome_mutation()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
RAISE EXCEPTION 'reminder outcomes are immutable'
USING ERRCODE = '55000';
END;
$$;

DROP TRIGGER IF EXISTS reminder_outcomes_row_mutation_guard
ON notification_service.reminder_outcomes;
CREATE TRIGGER reminder_outcomes_row_mutation_guard
BEFORE UPDATE OR DELETE ON notification_service.reminder_outcomes
FOR EACH ROW
EXECUTE FUNCTION notification_service.reject_reminder_outcome_mutation();

DROP TRIGGER IF EXISTS reminder_outcomes_truncate_guard
ON notification_service.reminder_outcomes;
CREATE TRIGGER reminder_outcomes_truncate_guard
BEFORE TRUNCATE ON notification_service.reminder_outcomes
FOR EACH STATEMENT
EXECUTE FUNCTION notification_service.reject_reminder_outcome_mutation();
11 changes: 9 additions & 2 deletions apps/notification-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
"scripts": {
"build": "tsc -p tsconfig.json",
"dev": "tsc -p tsconfig.json --watch",
"lint": "tsc --noEmit && prettier --single-quote --check package.json tsconfig.json src/main.ts src/reminder-scheduler.ts src/reminder-scheduler.test.ts src/reminder-scheduler.integration.test.ts ../../docs/superpowers/plans/2026-08-04-bounded-reminder-scheduler-slice.md",
"test": "vitest run --no-file-parallelism",
"lint": "tsc --noEmit && prettier --single-quote --check package.json tsconfig.json vitest.config.ts \"src/**/*.ts\" ../../CHANGELOG.md ../../docs/operations/notification-persistence.md ../../docs/superpowers/specs/2026-08-04-notification-postgres-inbox-design.md \"../../docs/superpowers/plans/2026-08-04-*.md\"",
"test": "vitest run --no-file-parallelism --coverage",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@nestjs/common": "^11.1.6",
"pg": "^8.22.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
"@types/pg": "^8.20.0",
"@vitest/coverage-v8": "^3.2.4",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
}
Expand Down
Loading
Loading