Files
iios/CLAUDE.md
T
maaz519 5abee1b5b7 docs: add repo-local project memory under .claude/memory/
Versioned, per-topic project memory (index + entries): IIOS overview, the generic-safety
rule, run/test + the replay.spec flake workaround, recent features (Supabase auth,
reactions/pins/saves, mentions, media, notifications), the chat-web consumer, and workflow
conventions. CLAUDE.md points to it as the accumulating-notes companion to the canonical rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 19:55:38 +05:30

7.3 KiB
Raw Blame History

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 → OutboxBusprojectors (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

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://<ref>.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)

P0P8: 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 → Inboxmentions[] on send → MENTION inbox item (projector).
  • MediaStoragePort (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.