Adds IiosRetentionPolicySnapshot (per-interaction frozen lifecycle timestamps)
+ IiosInteraction.dataClass, and a RetentionService whose sweep captures
snapshots then archives (status change) and, past delete_after, redacts content
in place (reusing the Slice-8 tombstone) — never a hard delete. An active
compliance hold blocks both; every action is audited and tenant-scopeable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds IiosComplianceHold + IiosSourceHandle.redactedAt and a DsrService whose
eraseSubject redacts the caller's PII in place (message bodies, identity
display, raw payloads, support/inbox/transcript text) while preserving
externalId (source preservation), structure, and the audit trail. An active
compliance hold blocks erasure until released; every op is audited and
tenant-fenced. Erasure is idempotent (KG-03).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the doc-mandated IiosProjectionCursor (ordered high-water-mark +
rolling checksum per projection/topic/partition) and a reusable
ProjectionCursorService.advance(). The checksum is a deterministic fold over
the ordered event stream, so replaying the same events reproduces it — the
KG-06 proof that replay reproduces projection outcomes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the doc-mandated IiosIdempotencyCommand table + a reusable
IdempotencyService.run() that writes IN_FLIGHT at command start, replays the
cached response on a same-key/same-body retry, rejects a same-key/different-body
reuse (409), and retries a FAILED command. Tenant-scoped via
@@unique([scopeId, commandName, idempotencyKey]) (KG-02/KG-06).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task O.4: GET /metrics (session-auth) returns the caller tenant's counters
(interactions by kind/status, outbound by status, AI jobs + cost, tickets,
meetings, inbox, audit-link count) via findScope + Prisma groupBy — no cross-tenant
totals — plus a global relay-lag section. smoke-observability proves the mandated
gate end to end: x-trace-id response header, event correlationId == request trace,
audit link in DB, /metrics counters. Slice-4 verification: 122 tests green, builds,
boundary clean, observability smoke + all prior smokes pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 9.5: /health now reports the egress provider backing each channel (sandbox
vs http). scripts/smoke-capability.mjs proves the slice end to end: sandbox egress
SENT through the broker; EMAIL routed to the env-gated HttpProvider and actually
delivered to a LOCAL echo server (real egress, no external network, no credentials);
idempotent replay; /health bindings. App wires CapabilityModule.
P9 slice-1 verification: 103 tests green, pnpm -r build all packages+demos,
boundary clean, capability smoke PASS, all prior smokes (realtime/inbox/support/
adapter/route/ai/calendar) still PASS through the broker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 7.3: AiJobService runs scoped/purposed AI jobs that only propose — fails
closed via OPA (KG-03), degrades on budget (AiBudgetGuard sums costUnits, KG-12),
then persists job+modelRun+artifact(PROPOSED)+claims+evidence with full
provenance. CLASSIFY also proposes IiosModerationFlag(proposedBy:'AI'). accept/
reject record human feedback + emit events. Idempotent per job key. INFERENCE_PORT
binds LocalDeterministicInference for nest start; specs inject testkit FakeInference.
8 tests: classify/summarize/extract, fail-closed, budget-degrade, idempotent, accept.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /v1/adapters/:type/webhook verifies HMAC (fail-closed) -> stores raw ->
emits raw.received -> 202. RawEventProjector consumes it off the relay, normalizes
via the adapter, reuses IngestService (traceId threaded raw->interaction).
Deduped on (channelType, externalEventId). rawBody enabled. 48 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Event-driven projector creates/collapses NEEDS_REPLY items from message.sent
(traceId propagated; inbox.item.created emitted) and resolves them to DONE on
message.read; idempotent via processed-event ledger. markRead now emits a
message.read outbox event (atomic). OutboxRelay runs on a 500ms timer in the
app (off in tests). Shared resetDb (TRUNCATE CASCADE) fixes cross-spec FK
isolation. 32 tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
apps/message-demo: Alice creates a thread, Bob joins; live two-way chat, typing,
read receipts (useMessages now exposes reads[]). Dev-only /v1/dev/token endpoint
(gated by IIOS_DEV_TOKENS) so the browser can auth. .env autoloaded (dotenv).
Realtime smoke script passes end-to-end (message + unread + receipt).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reuses support-service bootstrap/health/prisma patterns. Kernel-only Prisma
schema (scope/source_handle/actor/channel/thread/participant/interaction/
message_part/outbox/processed_event) with orgId/appId NOT NULL and
@@unique([scopeId, idempotencyKey]). Health verified: {status:ok,db:true}.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>