Commit Graph

57 Commits

Author SHA1 Message Date
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 1d3e18f417 feat(p9): propagate trace id into emitted events + structured relay/gateway logs
Task O.2: ai/calendar/route/ingest emit sites now stamp traceparent + insignia.
correlationId from interaction.traceId ?? currentTraceId() ?? generated (mirroring
message.send; inbox/inbound already carried correlationId). toTraceparent is hex-safe
(UUID used as-is, other ids hashed). OutboxRelay logs a structured relay.publish per
event (eventType/aggregateId/correlationId); MessageGateway logs ws.deliver. Tests:
an event's correlationId equals the active request trace, with a generated fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 01:04:24 +05:30
maaz519 0f923303d3 feat(p9): request-scoped trace context + middleware + structured logger
Task O.1: AsyncLocalStorage trace context (currentTraceId/runWithTrace/toTraceparent)
read by singleton services with no DI churn; Express traceMiddleware derives a trace
id per request (x-trace-id → traceparent → generated), echoes x-trace-id on the
response, runs the request in-context, and logs a structured http.request line on
finish. Minimal JSON structured logger stamps traceId on every line. Wired via
app.use in main.ts. 4 tests: scope, concurrency isolation, traceparent shape/parse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 00:50:15 +05:30
maaz519 ed53decd3e feat(p9): tenant isolation smoke + slice-3 verification green
Task T3.5: smoke-tenant.mjs — two dev tokens (org_A/org_B); tenant B reading A's
meeting → 403 (KG-02), B's list excludes A's, A reads its own → 200. Slice-3
verification: 114 tests green, pnpm -r build all packages, boundary clean, tenant
smoke + capability smoke + all prior smokes (realtime/inbox/support/adapter/route/
ai/calendar) pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:59:51 +05:30
maaz519 e7b8845c8a feat(p9): per-tenant outbound quota + cellId assignment
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>
2026-07-02 21:55:14 +05:30
maaz519 d002c8f521 test(p9): cross-tenant isolation suite (KG-10)
Task T3.3: isolation.spec.ts — two tenants (org_A/org_B) on the same app. Proves
tenant B is denied on approve/getArtifact/accept/getMeeting/summarize of A's
resources, B's lists (decisions/artifacts/meetings) show none of A's, the denied
calls neither mutate A's data nor create a scope for B, and each tenant sees only
its own lists. The mandated one-DB tenant-isolation test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:53:03 +05:30
maaz519 ed0d4dae80 feat(p9): fence AI + calendar + message by-id leaks (tenant scope)
Task T3.2: AiJobService.getArtifact/accept/reject assertOwns on artifact.job.scopeId;
listArtifacts scoped to caller. CalendarService.getMeeting/setConsent/generateTranscript/
summarize/listActionItems assertOwns on meeting.scopeId (optional principal → asserts
when supplied by a controller, skipped for internal calls). MessageService.getMessageById
gains the same optional fence. Controllers thread the principal through. All 46
ai/calendar/routing/messaging tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:51:10 +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 27103529e6 feat(p9): consent-at-egress smoke + slice-2 verification green
Task S2.3: capability smoke asserts the CMP consent gate is consulted at egress
(NOT_REQUIRED → send proceeds); the DENY→BLOCKED and ALLOW→receipt paths are
unit-covered. Slice-2 verification: 110 tests green, pnpm -r build all packages,
boundary clean, capability smoke PASS, all prior smokes (realtime/inbox/support/
adapter/route/ai/calendar) still pass through the consent-gated broker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:33:51 +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 2223d7a891 feat(p9): CMP consent gate in the Capability Broker
Task S2.1: the broker now checks consent (cmp.checkPurpose) after the OPA gate +
obligations and before the provider — a DENY blocks the send (BLOCKED/CONSENT_DENIED,
provider never called); ALLOW/NOT_REQUIRED proceed, ALLOW carrying the consent
receipt. Contracts gain CapabilityRequest.purpose + CapabilityResult.consentReceiptRef.
FakeCmp is now purpose-aware (denyPurpose/allowOnly). 5 new tests: allow+receipt,
deny→blocked, not-required, purpose-aware, OPA-before-CMP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:23:49 +05:30
maaz519 f7519d36f0 feat(p9): real-egress proof (env-gated HttpProvider) + /health bindings + smoke
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>
2026-07-01 20:38:47 +05:30
maaz519 a1f46646ee feat(p9): adapter certification checklist (certifyProvider)
Task 9.4: certifyProvider runs the P9 conformance checklist a provider must pass
before backing a channel — declares explicit capabilities, returns providerRef +
recognized outcome (never throws), repeatable/idempotent, tolerates empty payload.
2 tests: SandboxProvider passes; a throwing/no-capability provider fails with the
specific failing checks listed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:05:04 +05:30
maaz519 28e517fc1b feat(p9): route OutboundService through the Capability Broker
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>
2026-07-01 20:04:11 +05:30
maaz519 c50a501f32 feat(p9): Capability Broker — governed egress + fail-closed gate + obligations
Tasks 9.1+9.2: contracts (CapabilityRequest/Result/Provider, PolicyObligation) +
service CapabilityBroker that fails closed via opa (PolicyDeniedError, nothing sent),
enforces obligations before any provider call (DENY_OUTBOUND/REVIEW → BLOCKED;
MASK/REDACT → mask payload field), then routes to a CapabilityProviderRegistry
(SandboxProvider default; env-gated HttpProvider overrides per channel; unknown
channel fails closed). 5 tests: allow/deny/block/redact/unknown-channel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 19:58:25 +05:30
maaz519 e11964b8bc feat(p8): meeting-studio demo + smoke-calendar; P8 verification green
Task 8.7: apps/meeting-studio (Vite, port 5178) — schedule a meeting, invite
attendees, toggle per-attendee recording consent, generate transcript (BLOCKED
until unanimous consent — KG-14), summarize, see AI summary + action-item inbox.
scripts/smoke-calendar.mjs proves the P8 guarantees end to end: direct schedule,
consent gate BLOCKED→READY (KG-14), summary linked to a P7 ai_artifact, action
items, attendee-visibility exclusion (Scenario 6), meeting_request genesis,
simulated provider sync (idempotent).

P8 verification: 95 tests green, pnpm -r build all packages+demos, boundary
passes and fails on meeting-web->service, smoke-calendar PASS, prior smokes
(realtime/inbox/support/adapter/route/ai) PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:22:24 +05:30
maaz519 ca9498ce83 feat(p8): /v1/calendar REST + kernel-client methods + @insignia/iios-meeting-web
Task 8.6: CalendarController exposes schedule/request(+fromCallback/fromClaim)/
list/get/consent/transcript/summarize/action-items/providers(+sync) (session-auth).
kernel-client RestClient gains scheduleMeeting/requestMeeting/listMeetings/
getMeeting/setConsent/generateTranscript/summarizeMeeting/listActionItems/
connect+syncCalendarProvider + Meeting/MeetingParticipant/MeetingActionItem types.
New @insignia/iios-meeting-web (MeetingProvider + useMeetings/useMeeting/useSchedule/
useConsent/useSummarize/useActionItems). Boundary: meeting-web -> contracts +
kernel-client (fails on meeting-web->service).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:09:18 +05:30
maaz519 83369f2055 feat(p8): simulated calendar adapter (provider · sync cursor · availability)
Task 8.5: CalendarAdapter connects a SIMULATED provider account + sync cursor,
deterministically pulls stable external events (idempotent by externalEventId,
advancing the cursor), records availability windows, and computes free/busy from
overlapping calendar events. No network. Real provider via Capability Broker = P9.
3 tests: connect, sync idempotent + cursor advance, availability busy/free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:02:27 +05:30
maaz519 2ec427cecc 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>
2026-07-01 18:00:53 +05:30
maaz519 55acc3c187 feat(p8): consent gate → transcript → summary → action items (KG-14/Scenario 6)
Task 8.3: checkMeetingConsent requires unanimous attendee GRANTED consent + CMP
non-DENY before any recording artifact. generateTranscript → BLOCKED (no segments)
unless the gate passes, else READY + speaker segments. summarize re-checks consent
live (revoke blocks re-summary), reuses P7 AiJobService over a synthetic transcript
interaction → IiosMeetingSummary(aiArtifactId) + action items + per-attendee
MEETING_FOLLOWUP inbox items, excluding visibility=NONE. 6 tests cover the spine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:57:36 +05:30
maaz519 6edb0cc0cc feat(p8): CalendarService — 3 genesis paths converge on schedule
Task 8.2: requestMeeting + fromCallback (bridge) + fromEventClaim (accepted P7
AI EVENT claim, human-confirmed) + direct scheduleDirect. schedule() fails closed
via opa, creates meeting + organizer/attendee participants + calendar_event,
closes the request, and populates IiosCallbackRequest.meetingRef when bridged.
5 tests: direct/callback/AI genesis, un-accepted claim refused, opa-deny fail-closed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:54:03 +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 37f8ceeaf3 feat(p7): ai-studio demo + smoke-ai; P7 verification green
Task 7.6: apps/ai-studio (Vite, port 5177) — ingest a message, run CLASSIFY/
SUMMARIZE/EXTRACT, see proposal cards with confidence, evidence citations,
abstention, model/cost + a budget meter, and accept/reject; built on the ai-web
SDK via AiProvider. scripts/smoke-ai.mjs proves the P7 guarantees end to end:
auto-CLASSIFY → AI flag → routing REVIEW/DENY never ALLOW (KG-05), summary cites
evidence, EXTRACT stays DISCUSSION (Scenario 7), replay-once, accept.

P7 verification: 79 tests green (3x stable), pnpm -r build all packages+demos,
boundary passes and fails on ai-web->service, smoke-ai PASS, prior smokes
(realtime/inbox/support/adapter/route) PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:53:40 +05:30
maaz519 881b37afd0 feat(p7): /v1/ai REST + kernel-client AI methods + @insignia/iios-ai-web SDK
Task 7.5: AiController exposes POST /v1/ai/jobs, GET /v1/ai/artifacts(+/:id),
POST accept/reject (session-auth). kernel-client RestClient gains runAiJob/
listArtifacts/getArtifact/acceptArtifact/rejectArtifact + AiArtifact/AiClaim/
AiEvidenceLink/AiModelRun types. New @insignia/iios-ai-web (AiProvider, useRunJob,
useAiProposals, useArtifact). Boundary allowlist: ai-web -> contracts +
kernel-client (fails on ai-web->service).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:42:07 +05:30
maaz519 68d922c248 feat(p7): wire AI into P6 routing + AiProjector (KG-05 structural proof)
Task 7.4: route-simulator now unions RULE flags with persisted AI moderation
flags before decide() — so AI flags influence routing exactly like rule flags,
only ever REVIEW/DENY, never ALLOW. SUMMARY/TRANSCRIPT/DIGEST previews render a
proposed AI summary (aiSourced flag) instead of the P6 stub, still preview-only.
AiProjector auto-runs CLASSIFY on interaction.normalized (channel-bearing only),
idempotent via claim(). 4 tests prove KG-05: AI flag forces REVIEW on an
auto-ALLOW binding, DENY on CHILD, AI summary in preview with 0 sends, projector
idempotency. Full suite 79 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:38:00 +05:30
maaz519 c6dc1cf112 feat(p7): AiJobService + AiBudgetGuard + local inference (gate→budget→propose)
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>
2026-07-01 16:34:32 +05:30
maaz519 e03f2f72e4 feat(p7): FakeInference deterministic provider + golden eval registry
Task 7.2: FakeInference implements the InferencePort with keyword vocabulary
mirroring the P6 RuleEngine; deterministic (no clock/random) so AI artifacts are
replayable. Golden fixtures (AI_GOLDEN) + inference.spec.ts assert byte-identical
replay, abstention, and Scenario-7 (EVENT never exceeds DISCUSSION certainty).
Controls: abstain()/deny()/setCost().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:29:51 +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 54c47dd133 feat(p6): route-admin demo + route smoke; P6 verification green
Task 6.6: apps/route-admin (Vite, port 5176) — create bindings, simulate a
message (preview-first, no send), see color-coded per-destination decisions
with reason codes + preview, approve/deny REVIEW rows; built on the
community-web SDK via CommunityProvider.

scripts/smoke-route.mjs: 3 MANUAL bindings → 'party at 9 PM' → children(CHILD)
DENY / adults+seniors REVIEW / 0 sends on simulate; approve → exactly one
sandbox forward; AUTOMATIC safe binding auto-forwards via RouteProjector.

P6 verification: 63 tests green, pnpm -r build all packages+demos, boundary
passes and fails on community-web→service, route smoke PASS, prior smokes
(realtime/inbox/support/adapter) PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:33:42 +05:30
maaz519 095ea869aa feat(service): P6.4 RouteProjector — AUTOMATIC forwarding on interaction.normalized
Reacts to channel-bearing (ingested/adapter) interactions, not native DMs;
simulates per binding; AUTOMATIC+ALLOW executes to sandbox, MANUAL/REVIEW/DENY
stay pending; idempotent. 8 route tests; 63 total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:43:24 +05:30
maaz519 968a90bdb9 feat(service): P6.3 route REST + approve/deny → execute to sandbox
RouteService (createBinding/listBindings/simulate/listDecisions/approve/deny/
execute/flushDigest); approve REVIEW→ALLOW + route.approved event → execute via
P5 OutboundService (sandbox, idempotent per decision); deny never executes.
7 route endpoints; PORTAL adapter registered. 5 route tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:40:08 +05:30
maaz519 7ed3c11891 feat(service): P6.2 RuleEngine + RouteSimulator (deterministic, preview-first)
Keyword moderation flags; deny-by-default for protected (CHILD) destinations,
REVIEW for other flags/requiresReview, ALLOW otherwise; versioned rulepack for
replay. Simulate renders per-format previews + persists decisions, NEVER sends.
BDD (party@9pm → children DENY / adult+seniors REVIEW / 0 sends) + replay green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:29:01 +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 b879c9eba2 feat: P5.5 dev inject + adapter-inspector demo + adapter smoke (P5 complete)
Dev-only POST /v1/dev/webhook/:type signs+injects a sample payload. apps/adapter-
inspector (Vite): inbound raw events + outbound commands/attempts, simulate-inbound
+ test-send buttons. smoke-adapter proves signed->normalized->interaction, bad-sig
401, sandboxed outbound (no network). 55 tests; all 4 smokes pass; boundary enforced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:54:18 +05:30
maaz519 4f661f4c81 feat(service): P5.4 outbound sandbox sink + rate limit + inspector read endpoints
OutboundService: idempotent commands, per-(channelType,target) rate-limit bucket,
sandbox send (no network) + delivery attempts. POST /v1/adapters/:type/send +
GET inbound/outbound (session-auth). 55 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 13:46:56 +05:30
maaz519 29b64c3c4e feat(service): P5.3 inbound adapter pipeline (ACK-fast webhook + RawEventProjector) + traceId
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>
2026-07-01 13:44:23 +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 9c6e2c76e8 fix(support): agent-demo self-seeds (join default queue + go online); tickets always get a queue
Root cause of 'waiting for escalations': no queue/membership meant tickets were
never assigned. Now: createTicket find-or-creates a default queue; new joinDefault
endpoint + useGoOnline hook; agent-demo joins+goes-AVAILABLE on load (assignPending
picks up any waiting ticket). smoke-support self-seeds (no seed script needed).
Also: vitest singleFork to end cross-file DB races (45 tests deterministic).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:39:14 +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 7441d0ec36 feat: P4.6 agent demo + escalate button + support seed/smoke (P4 complete)
apps/agent-demo (Vite): agent sets AVAILABLE, sees assigned tickets, opens the
thread, replies. message-demo customer pane gains 'escalate to support' (via
SupportProvider/useEscalate). seed-support + smoke-support prove the loop:
escalate -> auto-assign -> agent reply -> customer receives. 45 tests; realtime/
inbox/support smokes all pass; both demos build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:09:29 +05:30
maaz519 e9e61b52bc feat: P4.4 support REST (tickets/escalate/callbacks/queue admin) + kernel-client methods
8 routes under /v1/support (session-auth). RestClient gains createTicket/escalate/
listTickets/patchTicket/requestCallback/createQueue/joinQueue/setAvailability +
Ticket/CallbackRequest types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:58:52 +05:30
maaz519 859124914c feat(service): P4.3 AssignmentService — event-driven least-loaded assignment
Consumes support.ticket.created -> picks least-loaded available agent within
capacity -> ticket OPEN, activeCount++, agent joins linked thread(s), base
thread untouched; idempotent (processed-event + assignedActorId guard). Queue/
team admin (createQueue/addMember/setAvailability, re-scan on available). 45 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:55:31 +05:30
maaz519 2695f0ffee feat(service): P4.2 SupportService — tickets, M:N thread links, transitions, callback
createTicket (NEW + history + ticket.created event, default queue), escalate,
linkThread/unlinkThread (M:N both directions), transition state machine +
ticket.state.changed, requestCallback placeholder, listTickets (mine/assigned),
fail-closed. 6 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:53:13 +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 942f40292e feat(demo): P3.5 inbox sidebar + inbox smoke (P3 complete)
message-demo gains a per-person Inbox rail (useInbox, done/snooze); sending
surfaces a NEEDS_REPLY item in the other pane; reading auto-resolves it.
smoke-inbox.mjs proves the event-driven flow end-to-end (send -> relay ->
projector -> inbox -> read -> DONE). Realtime smoke still passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 07:24:52 +05:30
maaz519 20c8f15dff feat(service): P3.3 inbox API + state machine (list/transition, fail-closed, STALE)
InboxService.list (fail-closed) + transition (OPEN/SNOOZED/DONE/STALE/ARCHIVED
state machine, owner-only, state-history). GET /v1/inbox/items + PATCH
/v1/inbox/items/:id. Moved SessionVerifier to global IdentityModule (shared by
message + inbox, no circular imports). 35 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 03:54:11 +05:30
maaz519 a2b95afaf0 feat(service): P3.2 InboxProjector + message.read event + scheduled relay
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>
2026-07-01 02:48:44 +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 4d05f7c1b1 feat(demo): P2.7 Vite React two-pane realtime demo + dev token endpoint (P2 complete)
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>
2026-07-01 01:36:14 +05:30
maaz519 3165a5bb59 feat(service): P2.4 REST fallback POST /v1/threads/:id/messages (session-auth)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 01:18:59 +05:30