5abee1b5b7
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>
123 lines
7.3 KiB
Markdown
123 lines
7.3 KiB
Markdown
# 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://<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)
|
||
|
||
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.
|