feat(iios): idempotency ledger on outbound egress (409 on key reuse) (P9)
OutboundService.send is now opt-in idempotent: an explicit idempotencyKey routes the send through IdempotencyService.run (Slice 6), so reusing a key with a different target/payload returns a 409 conflict and a same-key/same-request retry replays the cached command. The inner findUnique early-return stays as a backstop so a FAILED-then-retried send never collides on the command's unique key. Keyless sends keep the old behavior (no ledger row). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ 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 { IdempotencyService } from '../idempotency/idempotency.service';
|
||||
import { CapabilityBroker } from '../capability/capability.broker';
|
||||
import { CapabilityProviderRegistry } from '../capability/capability.registry';
|
||||
import { CalendarService } from './calendar.service';
|
||||
@@ -22,7 +23,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(), new OutboundService(asService, new CapabilityBroker(makeFakePorts(), new CapabilityProviderRegistry())));
|
||||
const cal = (ports?: FakePorts) => new CalendarService(asService, ports ?? makeFakePorts(), actors, ai(), new OutboundService(asService, new CapabilityBroker(makeFakePorts(), new CapabilityProviderRegistry()), new IdempotencyService(asService)));
|
||||
|
||||
async function scheduledMeeting(attendees: { userId: string; visibility?: 'FULL' | 'NONE' }[]) {
|
||||
const m = await cal().scheduleDirect(alice, { meetingType: 'INTERNAL', title: 'Board sync', startAt: START, attendees });
|
||||
|
||||
Reference in New Issue
Block a user