diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md new file mode 100644 index 0000000..ae82af3 --- /dev/null +++ b/.claude/memory/MEMORY.md @@ -0,0 +1,12 @@ +# IIOS Project Memory — Index + +Repo-local, versioned project memory. The full canonical rules are in the repo root +**`CLAUDE.md`**; these files are granular, per-topic memories that accumulate across sessions. +Read the relevant ones before working in that area; add/update files as the project evolves. + +- [project_iios_overview.md](project_iios_overview.md) — what IIOS is (generic interaction OS; kernel + specializations + platform ports) +- [feedback_generic_safety.md](feedback_generic_safety.md) — THE #1 rule: no chat/domain vocabulary in the kernel +- [reference_run_and_test.md](reference_run_and_test.md) — run command, isolated `iios_test` DB, and the `replay.spec` flake workaround +- [project_recent_features.md](project_recent_features.md) — the "real-providers" era: Supabase auth, reactions/pins/saves, mentions→inbox, media, notifications +- [reference_chat_web_consumer.md](reference_chat_web_consumer.md) — chat-web is the reference app driving IIOS feature work +- [feedback_workflow.md](feedback_workflow.md) — commits (email + co-author), IIOS_DEV_TOKENS off in prod, TDD, build-before-restart diff --git a/.claude/memory/feedback_generic_safety.md b/.claude/memory/feedback_generic_safety.md new file mode 100644 index 0000000..e0f0662 --- /dev/null +++ b/.claude/memory/feedback_generic_safety.md @@ -0,0 +1,28 @@ +--- +name: feedback_generic_safety +description: THE #1 locked rule — the kernel must never hardcode chat/domain vocabulary +metadata: + type: feedback +--- + +**The kernel must stay generic.** 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**. + +**Why:** IIOS is a generic interaction OS; chat is one consumer. Baking chat meaning into the +kernel destroys reuse (support/community/meetings share the same core). + +**How to apply:** domain meaning lives in exactly three places, never the kernel — +1. **OPA policy** (`DevOpaPort` → real OPA): DM-cap, group-admin, governed-join, media limits, + notification triggers. +2. **Opaque attributes** the kernel stores but never interprets: `thread.metadata.membership` + (`dm`/`group`), interaction annotations (opaque `annotationType`+`value` → app writes + `reaction`/`pin`/`save`), `mentions[]` (opaque userId notify-list; kernel never parses `@`). +3. **The app** (chat-web): rendering + product semantics. + +Reading an opaque attr inside a **policy/notification gate** (`membership === 'dm'` in +`DevOpaPort` or `notification.projector.ts`) is OK — that file IS the policy plane. Everywhere +else, keep generic. Verify before committing: +`grep -rniE "'dm'|'group'|reaction|emoji" packages/iios-service/src | grep -v spec` — hits only +in policy/notification/app-facing layers or comments. Also run `pnpm boundary`. +Related: [[project_iios_overview]]. diff --git a/.claude/memory/feedback_workflow.md b/.claude/memory/feedback_workflow.md new file mode 100644 index 0000000..603c938 --- /dev/null +++ b/.claude/memory/feedback_workflow.md @@ -0,0 +1,19 @@ +--- +name: feedback_workflow +description: Working conventions — commits, prod flags, TDD, rebuild-before-restart +metadata: + type: feedback +--- + +- **Commits:** conventional (`feat:`/`fix:`/`docs:`/`chore:`), git email + **`maaz@insigniaconsultancy.com`**, co-author every commit with Claude. The user commits + directly to `main` in this project (fine); branch only if asked. +- **⚠ `IIOS_DEV_TOKENS` MUST be `0`/unset in production** — it exposes `/v1/dev/*` (unauth token + minting, chaos, retention sweep). The single most important prod-hardening flag. +- **TDD** — write the failing spec first; verify it fails; implement; verify it passes. +- **Rebuild before restart** — after backend changes, `nest build` then restart from `dist`; + `lsof -ti :3200 | xargs kill -9` first or the old build keeps serving (stale-dist bites). +- **New kernel capability = a generic primitive only** — add domain meaning in OPA policy + the + app, never the kernel. See [[feedback_generic_safety]]. +- The user prefers **direct, fast iteration** (implement → verify end-to-end → commit), not + heavyweight multi-agent/spec ceremony. Include "how to test" in summaries; report failures honestly. diff --git a/.claude/memory/project_iios_overview.md b/.claude/memory/project_iios_overview.md new file mode 100644 index 0000000..1ff1020 --- /dev/null +++ b/.claude/memory/project_iios_overview.md @@ -0,0 +1,23 @@ +--- +name: project_iios_overview +description: What IIOS is — a generic multi-tenant interaction OS; kernel + specializations + swappable platform ports +metadata: + type: project +--- + +IIOS (Insignia Interaction OS) = one NestJS service (`@insignia/iios-service`) + SDKs, in a +pnpm monorepo (`packages/*`). Every interaction (chat message, ticket, routed post, AI +suggestion, meeting) is the **same kernel object** inside a **tenant scope** (`IiosScope`: +org/app/tenant/…), behind the **same fail-closed gates**, emitting the **same audit trail**. + +Products (messaging, inbox, support, routing, AI, calendar, media, notifications) are thin +**specializations** on top of a tiny kernel — never the reverse (`pnpm boundary` enforces it). + +Platform seams are **ports** (`IiosPlatformPorts`, DI token `PLATFORM_PORTS`; dev = permissive +`LocalDevPorts`): session, opa, cmp (consent), mdm, sas, capability, plus `StoragePort` (media) +and `NotificationPort` (push). **Dev stubs swap to real adapters with zero consumer changes.** +Every op passes `decideOrThrow(ports, {action,…})` fail-closed. Events go through a +transactional outbox → `OutboxBus` → idempotent projectors (inbox, notifications). + +See the repo `CLAUDE.md` and `docs/IIOS_API_AND_SDK_GUIDE.md` (as-built reference) for detail. +Related: [[feedback_generic_safety]]. diff --git a/.claude/memory/project_recent_features.md b/.claude/memory/project_recent_features.md new file mode 100644 index 0000000..bb04db5 --- /dev/null +++ b/.claude/memory/project_recent_features.md @@ -0,0 +1,31 @@ +--- +name: project_recent_features +description: The "real-providers" era — Supabase auth, reactions/pins/saves, mentions→inbox, media, notifications +metadata: + type: project +--- + +Beyond P0–P8 (kernel → messaging → inbox → support → adapters → routing → AI → +calendar/meetings), recent work (mostly driven by the chat app, the start of P9 "real +providers"): + +- **Real Supabase auth** — `SessionVerifier` verifies real OIDC tokens against issuer JWKS + (ES256, no secret), a **multi-issuer registry** routed by the `iss` claim → per-issuer `appId` + scope (`AUTH_ISSUERS` JSON, or `SUPABASE_URL` single-issuer shorthand). `userId = email`. + Legacy HS256 app-token path (`APP_SECRETS`) stays for dev/tests. +- **Reactions / pins / saves** — one generic `IiosInteractionAnnotation` primitive (opaque + `annotationType`+`value`); socket `annotate` event → `annotation` broadcast; + `GET /v1/threads/my-annotations?type=save`. +- **@mentions → Inbox** — `send(... mentions[])` (opaque userId list) → `InboxProjector` fans out + a `MENTION` inbox item to mentioned participants; reading resolves it. +- **Media** — `StoragePort` (dev = local disk `MEDIA_DIR`; prod swap to S3/Supabase), presigned + upload/download (signed HS256 tokens, OPA-gated size/type, tenant-fenced), `attachment` on + `MessageDto`; parts use generic `MEDIA_REF/VOICE_REF/FILE_REF`. +- **Notifications** — presence-gated Web Push: `NotificationProjector` runs 3 gates + (policy=DM/mention/reply-to-you · presence=`focus_thread` signal · per-thread `muted`) → + swappable `NotificationPort` (Web Push/VAPID); dead sub (410) pruned. Presence is in-memory + (single-instance) → Redis for multi-replica. +- `senderId` (stable externalId) on `MessageDto` for reliable "is this mine?". + +~205 tests. Keep `docs/IIOS_API_AND_SDK_GUIDE.md` current when adding endpoints. +Related: [[reference_chat_web_consumer]], [[feedback_generic_safety]]. diff --git a/.claude/memory/reference_chat_web_consumer.md b/.claude/memory/reference_chat_web_consumer.md new file mode 100644 index 0000000..687c55b --- /dev/null +++ b/.claude/memory/reference_chat_web_consumer.md @@ -0,0 +1,20 @@ +--- +name: reference_chat_web_consumer +description: chat-web is the reference consumer app that drives IIOS feature work +metadata: + type: reference +--- + +**chat-web** (separate repo, `~/Documents/insignia-work/chat-web`) is the reference app on IIOS — +a 1:1 + group chat UI (Vite + React + TanStack Router/Query + socket.io-client + supabase-js). +It's frontend-only; IIOS provides identity, threads, messages, realtime, reactions, mentions, +media, notifications. + +Feature work usually spans **both repos**: a generic primitive/port in iios + the app UI in +chat-web. The layer split we follow: **service** owns storage/auth/governance, the **SDK layer** +(`chat-web/src/lib/*`, mirrors `@insignia/iios-kernel-client`) owns client plumbing +(e.g. `uploadMedia`/`mediaUrl`, `registerPush`), the **app** owns rendering. + +chat-web uses real Supabase login (`VITE_SUPABASE_URL` + anon key in its `.env`); identity = +email. Run it with `pnpm dev`. Commit both repos with git email `maaz@insigniaconsultancy.com`. +Related: [[project_recent_features]]. diff --git a/.claude/memory/reference_run_and_test.md b/.claude/memory/reference_run_and_test.md new file mode 100644 index 0000000..3285aee --- /dev/null +++ b/.claude/memory/reference_run_and_test.md @@ -0,0 +1,30 @@ +--- +name: reference_run_and_test +description: How to run the service + the test-DB isolation and the replay.spec flake workaround +metadata: + type: reference +--- + +**Infra:** Postgres in docker `iios-db` on **:5434** (db `iios`), Redis on :6379. If Docker is +down: `open -a OrbStack` then `docker start iios-db`. + +**Run** (dev auth via Supabase; media + push enabled): +``` +pnpm --filter @insignia/iios-service exec nest build # rebuild after backend changes +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 +``` +Restarting: `lsof -ti :3200 | xargs kill -9` first (stale instance → EADDRINUSE / old build served). + +**Tests:** `pnpm test` (Vitest) runs against an **isolated `iios_test` DB** (globalSetup creates ++ migrates it; `DATABASE_URL` overridden) — it **never wipes the dev `iios` DB**. TDD: spec next +to code; DB specs use `resetDb()`. + +**⚠ Known flake — `outbox/replay.spec`:** clock/ordering-sensitive, pre-existing. If it fails in a +full run, it's stale `iios_test` state, NOT a regression. Fix: +`docker exec iios-db psql -U iios -d postgres -c "DROP DATABASE IF EXISTS iios_test WITH (FORCE)"` +then re-run. Prove it's not yours by stashing changes and re-running. + +`pnpm boundary` = import-boundary check (must stay OK). Smokes: `packages/iios-service/scripts/smoke-*.mjs`. diff --git a/CLAUDE.md b/CLAUDE.md index 02559dc..b1f0e5b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,10 @@ # 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