Email can now carry attachments (e.g. an invoice PDF). The kernel already stored
attachments as message parts + the media StoragePort holds the bytes; the gap was
the email envelope.
- EMAIL payload carries attachment REFS ({filename, contentRef, mimeType}), not
bytes — the ledger + T8 PII redaction stay small; bytes are fetched at send time.
- SmtpProvider takes an AttachmentResolver; resolves each ref via storage and attaches
(nodemailer). FAILS CLOSED if a declared attachment can't be resolved (or no resolver
is wired) — never send a receipt/invoice missing its file; a FAILED command retries.
- CapabilityProviderRegistry injects the resolver from STORAGE_PORT (@Optional);
MediaModule exports STORAGE_PORT, CapabilityModule imports MediaModule. No cycle.
- TemplatedSender + MailService + the /v1/mail/send DTO pass attachments through.
Verified: 6 new unit tests (attach, fail-closed x2, plain-unaffected, resolver,
pass-through) + a REAL Ethereal SMTP send WITH a PDF attachment (SENT). Full suite
294/294, boundary + build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design + 8-task TDD plan for the IIOS template module. Self-reviewed:
provenance-write path, nullable-scope unique index, PII minimization.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .npmrc routes the @insignia scope to https://git.lynkedup.cloud/api/packages/insignia/npm/
(auth via ${GITEA_TOKEN} env — no secret committed).
- The 9 frontend SDK packages (contracts, kernel-client, adapter-sdk, *-web) are now
publishable: private dropped, version 0.1.0, publishConfig pinned to Gitea. iios-service
and iios-testkit stay private (pnpm publish skips them).
- Root `release` / `release:dry` scripts; a Gitea Actions workflow publishes on a v* tag.
- PUBLISHING.md documents publish + consumer (.npmrc) setup.
Verified: dry-run packs cleanly and workspace:* deps resolve to 0.1.0 in the tarball.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
API & SDK guide:
- §5.3 Threads & Messaging fully filled in: GET/POST /v1/threads, participants,
my-annotations; the Message shape (senderId, attachment, annotations); the full
socket event set (open_thread/send_message/add_participant/annotate/read/typing +
server message/receipt/typing/annotation); reactions/pins/saves as the generic
interaction-annotation primitive; mentions → MENTION inbox item.
- §5.4 inbox MENTION kind; SDK reactions/pins/saves + listThreads/createThread;
vocab adds MENTION, message-part kinds, attachment kind, annotation types.
DEPLOYMENT:
- Real-IdP auth env (SUPABASE_URL/AUTH_ISSUERS via JWKS, no secret) + MEDIA_SECRET.
- Media StoragePort in topology: dev local disk → prod object storage; ⚠ local disk is
single-instance/non-durable; bytes flow client↔storage direct (scales independently).
CEO overview: refreshed counts (192 tests, 53 tables / 19 migrations) and noted P9 has
begun — real Supabase auth (first stubbed port turned real) + generic reactions/pins/
saves/mentions/media on the kernel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New §5.10 Media: presign-upload/download + upload/blob endpoints, attachment shape,
governance notes (25MB + type allowlist via OPA, signed tokens, tenant fence), and a
sequence diagram of the presign→upload→send→signed-download flow with the OPA gate.
- SDK: uploadMedia/mediaUrl; send() attachment/mentions.
- Auth §3: real OIDC (Supabase/AUTH_ISSUERS) JWKS verification alongside dev HS256.
- Env: MEDIA_DIR/MEDIA_SECRET/PUBLIC_URL/SUPABASE_URL/AUTH_ISSUERS; test count 192.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an opt-in RedisIoAdapter (wired in main.ts when REDIS_URL is set) so
socket.io room emits fan out across instances via Redis pub/sub — a client on
replica B now receives messages emitted by replica A. Without REDIS_URL the
in-memory adapter is kept (single-instance dev unchanged). Adds redis to
docker-compose, REDIS_URL to the env contract, and smoke-realtime-cluster.mjs
which proves cross-instance delivery fails without Redis and passes with it.
Delivery is at-least-once at N>1; clients dedupe by message id (documented).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a multi-stage Dockerfile for iios-service (build → pnpm deploy prune →
slim non-root runtime), a docker-entrypoint that runs `prisma migrate deploy`
then starts the server as PID 1, a .dockerignore, a fully-commented
.env.example config contract, and docs/DEPLOYMENT.md (topology, scaling,
release strategy, prod-readiness gaps). Moves prisma to dependencies so the
CLI ships in the prod bundle. Image builds, migrates, and serves /health 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Establishes a docs/adr convention and records the naming-migration decision:
interaction-centric Iios-prefixed vocabulary as canonical, support as a
namespaced specialization (KG-16), @insignia/iios-* package scope, and a
greenfield-and-deprecate (strangler) migration off the legacy support-service.
Includes the legacy→IIOS naming map.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full as-built reference: run/auth (dev tokens), conventions (idempotency, tenancy,
error codes), every REST endpoint across health/dev/interactions/threads/inbox/
support/adapters/routes/ai/calendar with request/response shapes, the kernel-client
+ 6 React hook SDKs, a curl QA quickstart + the 8 smoke scripts, env var reference,
and enum vocabularies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>