feat(profile): event-driven invalidation (#81 phase B.2)

Features now declare invalidatedBy subjects in the registry; the new
profile/subscriber.ts subscribes to each unique subject and drops
matching stored rows for the userId in the payload. Next getProfile
call recomputes from current data instead of waiting up to ttlSec.

Wiring:
  completion_rate_30d, dismiss_rate_30d, mean_dwell_ms_30d,
  preferred_hour  ← signals.tip.feedback
  tip_volume_30d  ← signals.tip.served

TTL stays as a safety net for clock drift and dropped events.
Registration validates each declared subject against KNOWN_SUBJECTS
(mirror of EventMap) so typos throw at startup, not silently.

ADR-0011 updated.

Refs #81.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 00:38:45 +00:00
parent 4a42a6aabf
commit ee4eb15022
5 changed files with 219 additions and 6 deletions

View File

@@ -20,6 +20,8 @@ import { requireAdmin } from './middleware/admin.js';
import type { Request, Response } from 'express';
import { connectNats } from './events/nats.js';
import { startTodoistSyncScheduler } from './signals/scheduler.js';
import { bus } from './events/bus.js';
import { registerProfileSubscriptions } from './profile/subscriber.js';
await mkdir(dirname(config.DATABASE_PATH), { recursive: true });
runMigrations();
@@ -96,3 +98,7 @@ if (config.NATS_URL) {
}
startTodoistSyncScheduler(config.TODOIST_SYNC_INTERVAL_MS);
// Profile features are invalidated on relevant signals (#81 phase B.2);
// TTL stays as a safety net for clock drift / dropped events.
registerProfileSubscriptions(bus);