feat(p8): reminders (sandbox + inbox) + CalendarProjector (AI-event auto-bridge)

Task 8.4: schedule queues one sandbox reminder (OutboundService) + a SYSTEM_ALERT
inbox item per participant, idempotent (outbound key + per-meeting marker) +
meetingReminderQueued event. CalendarProjector consumes aiArtifactAccepted → for
each EVENT claim proposes a meeting_request (PROPOSED, sourceClaimId), idempotent
via claim() + per-claim dedup. 2 tests: reminder replay-safe, AI-event→request once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:00:53 +05:30
parent 55acc3c187
commit 2ec427cecc
6 changed files with 167 additions and 3 deletions
@@ -6,6 +6,8 @@ import { type MessagePrincipal } from '../messaging/message.service';
import { ActorResolver } from '../identity/actor.resolver';
import { AiJobService } from '../ai/ai.service';
import { AiBudgetGuard } from '../ai/ai-budget.guard';
import { OutboundService } from '../adapters/outbound.service';
import { AdapterRegistry } from '../adapters/adapter.registry';
import { CalendarService } from './calendar.service';
import type { PrismaService } from '../prisma/prisma.service';
import type { FakePorts } from '@insignia/iios-testkit';
@@ -19,7 +21,7 @@ const alice: MessagePrincipal = { userId: 'alice', orgId: 'org_demo', appId: 'po
const START = '2026-07-02T10:00:00.000Z';
const ai = () => new AiJobService(asService, makeFakePorts(), makeFakeInference(), new AiBudgetGuard(asService), actors);
const cal = (ports?: FakePorts) => new CalendarService(asService, ports ?? makeFakePorts(), actors, ai());
const cal = (ports?: FakePorts) => new CalendarService(asService, ports ?? makeFakePorts(), actors, ai(), new OutboundService(asService, new AdapterRegistry()));
async function scheduledMeeting(attendees: { userId: string; visibility?: 'FULL' | 'NONE' }[]) {
const m = await cal().scheduleDirect(alice, { meetingType: 'INTERNAL', title: 'Board sync', startAt: START, attendees });