# IIOS — Claude Code Rules > **Project memory:** granular, versioned per-topic notes live in **`.claude/memory/`** — read > `.claude/memory/MEMORY.md` (the index) and the relevant entries before working in an area, and > add/update memories as the project evolves. This file is the canonical rules; those are the > accumulating notes. ## What IIOS is **IIOS (Insignia Interaction OS)** is a **generic, multi-tenant "interaction OS"** — one NestJS service (`@insignia/iios-service`) + a family of SDKs. Every interaction (a chat message, a support ticket, a routed post, an AI suggestion, a meeting) is the **same kernel object** inside a **tenant scope**, behind the **same fail-closed gates** (policy/consent), emitting the **same audit trail**. Products (messaging, support, community, AI, meetings) are thin **specializations** on top of the kernel — never the other way around. `chat-web` (separate repo) is the reference consumer app. ## THE #1 LOCKED RULE — generic-safety (read this before touching the kernel) **The kernel must never hardcode chat/domain vocabulary.** No `'dm'` / `'group'` / `'reaction'` / `'emoji'` / `'mention'` literals in kernel or messaging *logic*. If a reviewer asked *"is this a chat backend now?"* the answer must stay **no**. Domain meaning lives in **three** places, never the kernel: 1. **OPA policy** (the policy plane — `DevOpaPort` now, real OPA later). The DM-cap, group-admin, governed-join, media-limit, and notification-trigger rules live here. 2. **Opaque thread/interaction attributes** the kernel stores but never interprets: `thread.metadata.membership` (`'dm'|'group'`), interaction annotations (opaque `annotationType` + `value` → the app writes `reaction`/`pin`/`save`), `mentions[]` (an opaque userId notify-list the kernel fans out; it never parses `@`). 3. **The app** (chat-web) — rendering + product semantics. Reading an opaque attribute inside a **policy/notification gate** (e.g. `membership === 'dm'` in `DevOpaPort` or `notification.projector.ts`) is allowed — that file *is* the policy plane, not the kernel. Everywhere else, keep it generic. Verify with a grep before committing: `grep -rniE "'dm'|'group'|reaction|emoji" packages/iios-service/src | grep -v spec` — hits should only be in the policy/notification/app-facing layers or comments. `pnpm boundary` enforces the layer dependency law (specializations import the kernel, never the reverse). Run it; don't break it. ## Architecture - **Kernel primitives** (generic): `IiosScope` (six-vector: org/app/tenant/bu/…), `IiosSourceHandle` (externalId = userId; stays `UNVERIFIED` until MDM resolves → `canonicalEntityId`), `IiosActorRef`, `IiosThread` (subject/metadata), `IiosThreadParticipant`, `IiosInteraction` (+ `parentInteractionId` reply link), `IiosMessagePart` (media as `contentRef`), `IiosInteractionAnnotation` (generic). - **Platform ports** (`IiosPlatformPorts`, DI token `PLATFORM_PORTS`; dev = `LocalDevPorts`): session, opa, cmp (consent), mdm, sas, capability — plus a `StoragePort` (media) and `NotificationPort` (push). **Dev stubs → real adapters with zero consumer changes.** Every op passes `decideOrThrow(ports, {action,…})` **fail-closed**. - **Session (auth):** `SessionVerifier` verifies (a) real OIDC tokens (Supabase/`AUTH_ISSUERS`) against the issuer JWKS (ES256, no secret), routed by `iss` → per-issuer `appId` scope; or (b) legacy dev HS256 app tokens (`APP_SECRETS`, keyed by `appId`). `userId = email` for OIDC. - **Events:** transactional outbox → `OutboxBus` → **projectors** (inbox, notifications). Projectors are **idempotent** (`claim()` on `IiosProcessedEvent` + projection cursor). Delivery is at-least-once → clients dedupe by message `id`. - **Scope isolation:** every row is tagged by `scopeId` (org+app+tenant). By-id ops call `assertOwns` (tenant fence → 403). Always `select`/scope Prisma queries. ## Tech stack NestJS 11 · Prisma 6 / PostgreSQL 16 (docker `iios-db` on **:5434**, db `iios`) · Redis (socket.io adapter, multi-replica) · socket.io (`/message` namespace) · Vitest · pnpm monorepo (`packages/*`). `iios-service` is a **modular monolith** (HTTP + WS + relay + projectors in one process). ## Running & testing ```bash docker start iios-db # Postgres :5434 (OrbStack; `open -a OrbStack` if down) pnpm --filter @insignia/iios-service exec nest build # run (dev auth via Supabase; media + notifications enabled): SUPABASE_URL=https://.supabase.co REDIS_URL=redis://localhost:6379 PORT=3200 \ APP_SECRETS='{"portal-demo":"dev-secret"}' MEDIA_DIR=/tmp/iios-media \ VAPID_PUBLIC_KEY=… VAPID_PRIVATE_KEY=… VAPID_SUBJECT=mailto:dev@insignia \ node packages/iios-service/dist/main.js # → :3200 ; GET /health ``` - **`pnpm test`** — Vitest. Runs against an **isolated `iios_test` DB** (globalSetup creates + migrates it; `DATABASE_URL` overridden). **It never wipes the dev `iios` DB.** ~205 tests. - **TDD**: write the failing spec first (see `*.spec.ts` next to the code). DB specs use `resetDb()` + real Postgres. - **Flaky `outbox/replay.spec`**: it's clock/ordering-sensitive and pre-existing. If it fails in a full run, `docker exec iios-db psql -U iios -d postgres -c "DROP DATABASE IF EXISTS iios_test WITH (FORCE)"` then re-run — it's stale test-DB state, not a regression. - **`pnpm boundary`** — import-boundary check (must stay OK). - Smokes: `packages/iios-service/scripts/smoke-*.mjs` (run against a live service). ## Conventions - **Commits:** conventional (`feat:`/`fix:`/`docs:`/`chore:`), git email **`maaz@insigniaconsultancy.com`**, and co-author every commit with Claude. Branch off `main` before committing if asked; otherwise the session has committed directly to `main`. - **⚠️ `IIOS_DEV_TOKENS` MUST be `0`/unset in production** — it exposes `/v1/dev/*` (unauth token minting). Single most important prod flag. - New kernel capability = a **generic primitive** only (see the #1 rule). Add domain meaning in policy + app. - Prisma: always `select` to avoid leaking `passwordHash`/PII; org/tenant scope every `where`. ## What's built (state) P0–P8: kernel → messaging → inbox → support → adapters → routing → AI → calendar/meetings. Recent (the "P9 real-providers" era, mostly driven by the chat app): - **Real Supabase auth** — multi-issuer JWKS verification (`SessionVerifier`); first stubbed port turned real. - **Reactions / pins / saves** — one generic `IiosInteractionAnnotation` primitive (opaque type/value), `annotate` socket event, `GET /v1/threads/my-annotations`. - **@mentions → Inbox** — `mentions[]` on send → `MENTION` inbox item (projector). - **Media** — `StoragePort` (dev local disk → prod S3/Supabase), presigned upload/download, attachment on `MessageDto`. - **Notifications** — presence-gated Web Push: `NotificationProjector` (policy/presence/mute gates) + swappable `NotificationPort`, `focus_thread` presence signal, per-thread mute. - `senderId` (stable externalId) on messages for reliable "is this mine?". ## Docs (as-built) - `docs/IIOS_API_AND_SDK_GUIDE.md` — the **as-built REST/socket/SDK reference** (endpoints, shapes, env, vocab). Keep it current when adding endpoints. - `docs/DEPLOYMENT.md` — deploy/topology/env/scaling. - `docs/IIOS_OVERVIEW_FOR_CEO.md` — plain-language capability tour.