20 Commits

Author SHA1 Message Date
maaz519 0a8544b6fc feat(iios): notification engine — presence-gated Web Push (engine phase)
Deploy iios-service / build-deploy (push) Failing after 4m37s
CI / build (push) Successful in 5m25s
Generic notification engine: reacts to message.sent and runs three gates before
delivering — policy (DM always; group only on @mention or reply-to-you), presence
(skip if the recipient is focused on that thread), mute (per-thread). Delivery via a
swappable NotificationPort (Web Push/VAPID adapter); a 'gone' (404/410) prunes the sub.

- PresenceService + gateway `focus_thread` signal + disconnect cleanup (room membership
  != viewing, since the sidebar joins every thread room).
- IiosNotificationSubscription table + `muted` on IiosThreadParticipant (migration).
- Endpoints: GET vapid-public-key, POST/DELETE subscribe, POST threads/:id/mute|unmute;
  listThreads returns the caller's `muted`.
- DM-vs-group read from the opaque `membership` attribute in the notification POLICY only
  — kernel stays generic (grep-verified).

Tests: 13 new (3 gates + reply-to-you + prune + web-push adapter states); full suite 205
green. Verified live: module boots, subscribe stored, mute/unmute round-trips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 19:16:53 +05:30
maaz519 8c814d9b86 feat(iios): @mentions via inbox fan-out + my-annotations query (pins/saves)
Mentions ride the existing event-driven inbox — no chat parsing in the kernel:
- send() carries an OPAQUE mentions[] (userIds) into the message event; the kernel
  never parses "@". The app supplies the notify-list.
- InboxProjector fans out a MENTION inbox item (new generic inbox kind) to each
  mentioned *participant* (never the sender), idempotent per source message; reading
  the thread resolves the reader's MENTION + NEEDS_REPLY items to DONE.
- New MENTION value in the generic IiosInboxItemKind taxonomy (migration).

Pins/saves reuse the annotation primitive; new generic query powers a Saved list:
- MessageService.listMyAnnotated(principal, type) + GET /v1/threads/my-annotations
  returns the caller's annotated messages (type "save") with thread context.

Tests: 182 pass (+4: mention fan-out, resolve-on-read, saved query, opaque mentions).
Verified live: mention→inbox delivery, read→resolve, pin/save persistence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 01:35:30 +05:30
maaz519 f3c4ba72b5 feat(iios): generic interaction-annotation primitive (backs emoji reactions)
Adds IiosInteractionAnnotation — an actor attaches an OPAQUE (annotationType, value)
label to an interaction. The kernel stores + aggregates them but never interprets the
strings (the chat app writes type "reaction" / value = emoji); the same primitive backs
pins/saves/flags/tags later. No chat vocabulary in kernel code — verified by grep.

- MessageService.toggleAnnotation(): governed (participant-only via new OPA rule
  iios.interaction.annotate), idempotent toggle keyed on
  (scope, interaction, actor, type, value); history DTO carries aggregated
  { type, value, users[] } groups.
- Gateway: `annotate` event → broadcasts `annotation` (refreshed user list) to the room.
- DevOpaPort: annotate allowed only for thread members (real OPA can tighten later).
- Migration add_interaction_annotations (additive; dev data untouched).

Tests: 178 pass (+3: toggle/aggregate, coexisting values, non-member denied).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 00:20:47 +05:30
maaz519 035315ef73 feat(iios): retention_policy_snapshot + sweep (archive→redact, hold-aware) (P9)
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>
2026-07-03 12:50:49 +05:30
maaz519 611ca74cea feat(iios): DSR right-to-erasure (redact-in-place) + compliance hold (P9)
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>
2026-07-03 03:24:43 +05:30
maaz519 1997fe48bb feat(iios): projection-cursor ledger + ProjectionCursorService (P9)
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>
2026-07-03 02:39:31 +05:30
maaz519 b8b7e0d3e8 feat(iios): idempotency-command ledger + IdempotencyService (P9)
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>
2026-07-03 02:19:03 +05:30
maaz519 208fb0b4f0 feat(p9): IiosDlqItem + DlqService (dead-letter + deterministic replay)
Task D.1: mandated dlq_item table (sourceType/sourceId/failureStage/errorCode/
retryCount/payloadRef/consumerName/scopeId/status). DlqService.record (idempotent
per consumer+sourceId, bumps retryCount), onConsumerFailure (quarantine + clear the
IiosProcessedEvent claim so it's replayable — fixes claim-then-fail), registerHandler
+ replay (clears claim, awaits the registered consumer handler → RESOLVED on success,
QUARANTINED+retry on repeat throw; outbox-sourced items reset to PENDING). Wired into
OutboxModule; resetDb truncates it. 3 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 01:28:48 +05:30
maaz519 6548b40f17 feat(p9): iios_audit_link table + audit binding at decision points
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>
2026-07-03 01:08:55 +05:30
maaz519 063049b724 feat(p9): scope-ownership assert + fence routing approve/deny/listDecisions
Task T3.1: ActorResolver gains findScope (no-create) + assertOwns(principal,
resourceScopeId) → ForbiddenException on mismatch (KG-02). RouteService.approve/
deny now assertOwns on the decision's binding scope before mutating; listDecisions
is scoped to the caller. Fixes the critical cross-tenant mutation (tenant A could
approve tenant B's route). Also adds IiosScope.cellId + IiosOutboundCommand.scopeId
columns (migration tenant-isolation) + cell assignment on scope create. Test:
another tenant approving → Forbidden, decision unchanged, no scope created, empty list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:46:40 +05:30
maaz519 cdc59be481 feat(p9): thread purpose + persist consent receipt through OutboundService
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>
2026-07-02 21:26:01 +05:30
maaz519 21772e7611 feat(p8): calendar/meeting schema + enums + events + migration
Task 8.1: 12 Calendar/Meeting tables (meeting_request, meeting, participant,
transcript, segment, summary, generic action_item + meeting link, provider
account, calendar_event, availability_window, sync_cursor) + 13 enums; migration
calendar-init. Adds calendar.meeting.requested/scheduled/reminder.queued/
summarized events. resetDb truncates the new tables. Kernel already had the
MEETING_REQUEST / MEETING_FOLLOWUP / callback.meetingRef stubs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:51:35 +05:30
maaz519 42bb99c09f feat(p7): AI tables + enums + migration + events + InferencePort contract
Task 7.1: 7 AI-proposal-layer tables (IiosAiJob, IiosAiModelRun, IiosAiArtifact,
IiosAiClaim, IiosAiEvidenceLink, IiosAiToolCall, IiosEmbeddingRef) + enums;
migration ai-init. Adds ai.job.created / ai.artifact.proposed / ai.artifact.accepted
events and a standalone InferencePort contract (not part of sealed IiosPlatformPorts).
resetDb truncates the new tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:26:21 +05:30
maaz519 dfd87cdde7 feat(service): P6.1 route tables (route_binding, route_decision, moderation_flag) + enums + events
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:24:53 +05:30
maaz519 b4a1832a5c feat(service): P5.2 adapter tables (raw_event, outbound_command, delivery_attempt, rate_limit_bucket) + resetDb
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:39:22 +05:30
maaz519 eb04becc57 feat(service): P4 doc-faithfulness — SLA placeholder columns, issueSubclass, traceId on state.changed
Adds ticket.slaDueAt + ticket.issueSubclass + queue.slaPolicyRef (placeholder
columns, no timer logic until P7) and propagates ticket.traceId onto
ticket.state.changed events (assignment + transition). 45 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:25:11 +05:30
maaz519 2141cea8dc feat(service): P4.1 support tables (queue/team_member/ticket/ticket_thread_link/state_history/callback) + events
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:51:19 +05:30
maaz519 7b174e24fa feat(service): P3.1 inbox tables + extract ActorResolver
IiosInboxItem + IiosInboxItemStateHistory (migration inbox-init). Extracted
resolveScope/resolveActor/ensureParticipant into a shared ActorResolver
(IdentityModule) so message + inbox layers share identity resolution; MessageService
delegates (P2 tests unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 02:41:50 +05:30
maaz519 7197157058 feat(service): P2.1 messaging tables — IiosMessageReceipt + IiosUnreadCounter
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 01:04:26 +05:30
maaz519 9cfd1ab1ab feat(service): NestJS kernel skeleton + 10-table kernel schema (P1.1+1.2)
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>
2026-06-30 21:16:31 +05:30