Files
iios/CLAUDE.md
T
maaz519 58ebaf1982 docs: add CLAUDE.md — project rules for future Claude Code sessions
Captures what IIOS is, the #1 generic-safety guardrail (no chat vocabulary in the
kernel; meaning lives in OPA policy + opaque attributes + the app), the architecture
(kernel + platform ports + fail-closed + outbox/projectors), tech stack, run/test
commands (isolated iios_test DB, the replay.spec flake note), conventions (commit
email, IIOS_DEV_TOKENS off in prod), and current build state.

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

118 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IIOS — Claude Code Rules
## 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://<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 → 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.