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>
Task O.3: IiosAuditLink (traceId/correlationId/scopeId/actorRefId/action/
resourceType/resourceId) + recordAudit() helper (reads the current trace from ALS,
best-effort). Written at the decision/mutation points — route approve/deny, AI
accept/reject, meeting summarize, outbound send — so the trace appears in DB and a
decision can be replayed / traced across services (mandated iios_audit_link).
Migration audit-link; resetDb truncates it. 2 tests bind approve/accept to a trace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task T3.4: OutboundService gains a per-tenant egress cap (tenantLimit/tenantWindowMs,
IIOS_TENANT_OUTBOUND_*) counting a scope's commands in the window — a noisy tenant
is RATE_LIMITED while others keep sending (noisy-neighbor protection). Persists
scopeId on every command (fixes the previously-unscoped table). resolveScope assigns
IiosScope.cellId (IIOS_CELL_ID, default cell-default) — the cell-partition hook.
Tests: per-tenant cap isolates tenants; new scopes carry a cellId.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task S2.2: OutboundService.send gains an optional purpose, passes it to the broker,
and persists the returned consentReceiptRef on IiosOutboundCommand (new nullable
column, migration outbound-consent). RouteService.execute passes 'route_forward';
calendar reminders pass 'meeting_reminder'. CMP-DENY → command BLOCKED, no send.
2 tests: receipt recorded on allow, purpose-DENY blocks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 9.3: OutboundService now delegates the execute step to CapabilityBroker
(policy-gated + obligation-enforced) instead of calling a provider directly, while
keeping idempotency + rate-limit + the command/attempt ledger. BLOCKED obligations
→ command BLOCKED; a fail-closed throw marks the command FAILED then propagates.
AdaptersModule imports CapabilityModule; P4/P6/P8 egress now flows through the
broker unchanged. Updated 6 spec constructions; added a DENY_OUTBOUND test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>