# J — v2 → v4 port map ## Goal For every AI feature already shipped in v2 (`~/Code/@projects/@lilith/lilith-platform.live/codebase/@features/quinn-ai/` and adjacents), declare the v4 verdict — **port**, **redesign**, **replace**, or **skip** — and name the v4 brief that consumes it. The other briefs (A–I) are written greenfield; this one is the bridge to the working machinery so implementation can mine v2 instead of reinventing. ## Constraints - v2 source is **read-only** (CLAUDE.md hard rule + plum/apricot divergence hook). Code is mined via `.archive/platform.2/` tarballs or `remote-run apricot` reads, never edited in-place. - v4 authoring location is `@cocottetech/@platform/codebase/`. Ports of v2 logic land here as new code that *resembles* v2 but lives under the v4 naming (`platform.*` services, `@cocottetech/*` packages, NestJS 11 + Turbo + pnpm). No `quinn-*` package names in v4. - The Bun + Hono stack of v2 does **not** carry forward — v4 is NestJS-on-TypeORM (matches `@ai/services/ai-core`). Algorithmic logic ports; HTTP framework adapters get rewritten. - Port verdicts assume the v4 plan's tenancy + naming axes (person-first + org-overlay, `platform.api` data plane, fleet of `@ai` specialists fronted by `ai-copilot`). ## Verdict legend - **PORT** — v2 logic is working, shape is right; rewrite minimally for NestJS + v4 schema. Code is the mining seed. - **REDESIGN** — v2 product is right; v2 UX is web admin, v4 needs iOS-first / chat-first. Logic ports, UX is greenfield from briefs A–I. - **REPLACE** — v2 implementation has known limits (single-tenant, iMessage-only, etc.); v4 needs a categorically different design. Read v2 for lessons, not seed. - **SKIP** — not pulling into v4 P0/P1; revisit later or never. ## Map ### Engine pipeline (the load-bearing piece) | v2 component | v2 path | Verdict | v4 destination | Notes | |---|---|---|---|---| | **Inbound listener** (pg LISTEN/NOTIFY on `macsync.messages`) | `quinn-ai/engine/src/inbound-listener.ts` | **PORT** | `engagement-ingestor` worker | Same pattern; listener watches platform.db + macsync mirror. Notify channel naming follows v4 cache.invalidate convention. | | **5-check eligibility gate** (C1 spam / C2 floor / C3 wrong-id / C4 prior / C5 lookup) | `quinn-ai/engine/src/eligibility.ts` | **PORT** | `@cocottetech/eligibility` package consumed by `engagement-ingestor` + `ai-copilot` | Pure functions; carry over verbatim with `user_id`/`org_id` parameters added. Brief I §counter-actions references the gate decisions. | | **6-step CoT draft pipeline** (`THREAD_FACTS → QUINN_STATE → THREAD_POSITION → CHASE_CHECK → SANITY_CHECK → VOICE_REGISTER`) | `quinn-ai/engine/src/agents/outreach/` + `prompts.py` | **PORT** | `ai-copilot/ai-core` + per-specialist forks | The CoT scaffold is the single most valuable piece of v2 IP. Brief I's "trust drawer with feedback affordances" depends on these step labels surfacing in `agent_actions.draft_brief`. | | **Template selection + tier classification + fire-at scheduling** | `quinn-ai/engine/src/agents/outreach/templates.ts`, `classifyTier`, `calculateFireAt` | **PORT** | `engagement-ingestor` worker | Same logic; v4 stores templates in platform.db under `engagement_templates` (new table — add to migration 0002). | | **Pre-fire gate chain** (block-list, jargon rules, botness signals, thread state) | `quinn-ai/engine/src/pre-fire-checks.ts` | **PORT** | `engagement-ingestor` worker | The README calls this "the load-bearing piece". Brief H's policy-card "currently bumping / paused" status reads this gate's decisions. | | **Scheduled-send-worker** | `quinn-ai/engine/src/workers/scheduled-send/` | **PORT** | `scheduler` worker (v4 P0 plan) | Adapter swap: v2 dispatches via mac-sync iMessage; v4 dispatches via `platform-{directory}/actions/*` for directory chores + `mac-sync` for iMessage. | | **booking-worker** (expired-deposit + cover-story draft) | `quinn-ai/engine/src/workers/booking/` | **REDESIGN** | `bookings-{tryst,ts4r,...}/ai-core` per-directory specialist | Logic ports per-directory; UI moves from iMessage self-notify to chat-surface approval card (brief A) with cover-story diff (brief H4-style). | | **Mail workers** (`mail-autoresponder`, `mail-digest`, `mail-notifier`, `mail-sender`) | `quinn-ai/engine/src/workers/mail-*/` | **PORT** | `notifier` worker + `engagement-ingestor` (for inbound mail) | Brief C-notifications already specifies the digest/notification matrix; v2's mail workers feed the email channel. | | **photo-intake worker** | `quinn-ai/engine/src/workers/photo-intake/` | **PORT** | `content-{surface}` specialists' asset-library import path | Brief B's "asset library drawer" consumes whatever v2's intake produces. | | **vigil-detector** | `quinn-ai/engine/src/workers/vigil-detector.ts` | **REDESIGN** | passive context provider injected into `ai-copilot` chat sessions | Conceptually equivalent to v4's "passive context cards" (Quinn-state injection). Drop the v1 "vigil phase" terminology; carry the signal. | ### Quinn-ai dashboard pages (web admin) v2 ships a 14-page admin dashboard at `quinn.ai.transquinnftw.com`. v4's iOS-first IA collapses most of these into chat affordances + drawers. Verdicts: | v2 page | Verdict | v4 destination | |---|---|---| | `/` — Conversation (streaming chat + tools + context panel + voice + prior-session pane) | **REDESIGN** | Brief A — chat surface (iOS-native, same conceptual shape: streaming, tool-use, multimodal, voice). The 8 tools (`add_recurring_cost`, `analyze_image`, `describe_image`, `generate_image`, `get_platform_stats`, `save_content_task`, `list_calendar_events`, `list_tour_legs`, `list_tour_stops`) all port — they become Cocotte-internal MCP tools (some platform-internal at `@cocottetech/@platform/codebase/@features/platform-api/`; the surface-action tools live at `@features/{bookings,content}-*/adapter/`) consumed by `ai-copilot`. | | `/messages` — iMessage thread mirror | **REDESIGN** | Brief B — messages drawer (read access through `mac-sync`'s iMessage thread API; no new sync infra) | | `/drafts` — engine-draft ↔ actual-Quinn-reply with correction recording | **REDESIGN** | Brief I — audit/trust drawer. The correction-recording form, the 6 CoT step selectors, the `record_correction → list_correction_patterns → propose_prompt_revision → apply_prompt_revision` MCP loop all port; UI moves to in-app trust drawer with feedback affordances. | | `/engine` — Facts DB + Agent Model Table | **REDESIGN** | Settings overlay (brief A §top-bar settings); Facts DB becomes `platform.api` `facts` table read by `ai-copilot` context provider; Agent Model Table becomes per-specialist config in `platform.db`. | | `/engine-mode` — auto-fire / draft-only / monitor-only / off | **REDESIGN** | Brief H1 — per-surface policy cards. Each surface has its own gradient (not one global mode), surfaced in chat AND settings. Reason-string requirement carries. | | `/outreach` — raw event log | **REDESIGN** | Brief I — audit drawer (filterable). v2 event types (`inbound_received`, `auto_respond_eligible/blocked`, `auto_respond_scheduled`, `auto_respond_pre_fire`, `reply_proposed`, `self_notify_sent/failed`) port to `agent_actions` rows with matching action_type values. | | `/templates` — auto-respond template library | **REDESIGN** | Drawer (likely B — specialist drawer, per-specialist template view). Editable inline matching v2 affordance. | | `/timers` — scheduled/recurring actions | **REDESIGN** | Subsumed by Brief H1 policy cards (recurring) + Brief A (one-shot scheduled actions surface as approval cards with `scheduled_for`). | | `/nudges` — proactive outbound rules | **REDESIGN** | Same surface as `/timers`; nudges = scheduler-worker decisions, configured per-specialist. | | `/personality` — per-agent system prompt editor | **PORT** | Specialist drawer (B). Each specialist has an editable system prompt; changes commit to platform.db, take effect next session. Same UX as v2 with iOS-adapted editor. | | `/block-list` — phone numbers excluded from auto-respond | **PORT** | Settings overlay. Same affordance. Block list extends per-surface (block on Tryst but not iMessage). | | `/tour-planner` — upcoming city visits | **PORT** | Brief H3 — tour calendar drawer + multi-surface tour fan-out card. v2's `quinn_state.planned_locations_next_14d` JSON shape carries forward as platform.db `tours` table. | | `/experiments` — A/B variant testing | **PORT** | `engagement-portal` web FE secondary surface (brief G). Conversion-rate dashboard is web-natural, not iOS-natural. The `prospect_experiments` schema ports. | | `/prospects` — `prospect-classifier-claude` HOT/WARM/COLD scoring | **PORT** | `engagement-portal` web FE + `prospect-resolver` worker. Brief G covers the web view; classifier itself is a P4 worker. | | `/approvals` — iMessage self-notify → web approval flow | **REPLACE** | Brief A — in-app approval cards (the entire trust contract). v2's self-notify-to-iMessage workaround disappears; approval is in-band in the iOS chat. Keep v2's `"1 [ref:XXXX]"` parser around for backward-compat with already-pending iMessage approvals during cutover. | ### Personas | v2 file | Verdict | v4 destination | |---|---|---| | `quinn-ai/engine/src/personas/{cocotte,cocotte-book,sansonnet,transquinnftw-booking,transquinnftw-contact}.md` | **PORT** | `platform.db` `personas` table (already in v4 migration 0001) + per-specialist prompt fragments. **No rename**: `cocotte*` personas are the public Cocotte umbrella-brand voice and stay as `cocotte*`. There is no `demimonde*` persona — Demimonde is back-office and never speaks to customers (see [[brand-family]] memory). `sansonnet` stays (separately-held brand). `transquinnftw-*` stays (Quinn's talent voice). v4 multi-tenancy scopes personas by `user_id` + optional `org_id`. | ### MCP services | v2 MCP | Verdict | v4 destination | |---|---|---| | `quinn-messenger/mcp/` (`client.ts`, `index.ts`, `scheduling.ts`) | **PORT** | New `@cocottetech/messenger-mcp` package, called from `ai-copilot` as `mcp__messenger__*`. Drop `quinn-` naming. | | `quinn-drafts` MCP (correction recording, prompt revision) | **PORT** | New `@cocottetech/drafts-mcp` package. Brief I's feedback affordances trigger these tools. | | `quinn-prospector` MCP (correction patterns, drafts, prospect classification) — currently consumed by Claude sessions on apricot | **PORT** | Stays an MCP server; renamed `@cocottetech/prospector-mcp`. v4 surface agents (P1+) consume via `mcp__prospector__*`. | | `speech-synthesis` MCP | **PORT** | Same MCP, no rename. Brief A's voice mode + brief C's voice notifications both consume it. | ### Adjacent features (investigated 2026-05-17 — formerly low-confidence) | v2 feature | Purpose | Verdict | v4 destination | Notes | |---|---|---|---|---| | **`client-intel`** | Provider-coop prospect safety scoring (ratings, safety flags `UNSAFE`/`AGGRESSIVE`/`BOUNDARY_VIOLATION`, would-work-again, `COOP_ONLY` visibility) | **PORT** | `prospect-resolver` worker (P4) + [brief N](./N-provider-coop.brief.md) (coop UX) + [brief I](./I-audit-trust-replay.brief.md) trust cards + [brief G](./G-web-surfaces.brief.md) engagement-portal prospect cards | NestJS service + entity port cleanly; `intel_reports` table ports with v4 tenancy columns (`user_id`, `org_id`, `coop_id`). No AI/LLM involvement in v2 — pure data layer. Light v2 activity (4 commits) but conceptually load-bearing: this is *inter-tenant* data sharing, not per-tenant — brief N designs the multi-coop UX (opt-in, attribution, anonymous, dispute/moderation, PII hashing). | | **`comm-newsletter`** | Email newsletter CMS — admin compose/send, subscriber list, open/click tracking via SQLite | **REDESIGN** | Dispatch + tracking → `notifier` worker; subscriber list → [brief G](./G-web-surfaces.brief.md) engagement-portal; drafting UX → iOS chat + new `content-newsletter` specialist (see [brief L](./L-specialists-fleet.brief.md) §L1 content axis) | v2 has **no AI generation** — body is hand-composed. v4 adds optional Claude-drafted bodies via the new specialist. Storage moves SQLite → platform.db `newsletter_*` tables (P2 migration). Currently active (13 commits, May 2026); shipped feature. | | **`event-scrapers`** | Nightly + weekly aggregation across 13+ event sources (Ticketmaster, Eventbrite, anime cons, renfaires, conventions) with Tor-routed Playwright, fuzzy dedup, systemd timers; writes to `quinn-my.events` | **PORT** | `events-aggregator` worker (new in v4 P1 plan) feeding [brief H §H3](./H-recurring-chores.brief.md) tour-calendar drawer | All 13 source adapters, Tor circuit-pool pattern (`black.lan:3131` HAProxy → 20 circuits), dedup logic, and systemd schedules port. Pure data pipeline (no AI). Tenancy decision needed: events table can be global (shared aggregation across providers) or per-org — lean global since it's external public data. | | **`futa-waifu-tour`** | Single-brand static landing-page site (hero + event pages for FanimeCon stop, SEO keyword stubs) | **SKIP** | N/A | Quinn-specific brand site (hardcoded tour dates, niche audience). Not a multi-tenant platform capability. Per v4 DESIGN §brand-sites-are-templates, brand sites are *instantiations* of `org-site/` with config — when an Org wants this kind of site, they'll get one via `org-site` template, not by porting this one. | | **`adult-therapy-tours`** | Quinn's multi-city escort-tour brand static site (SF + San Jose stops, photo gallery, press kit + 4 SEO-bait domains) | **SKIP** | N/A | Same reasoning as `futa-waifu-tour` — single-person brand site, not platform primitive. Tour *dates* (not the HTML) may be referenced by `events-aggregator` if Quinn enters them as her own events; the marketing HTML doesn't port. | ## Cross-cutting principles when porting 1. **Schema first, code second.** Every v2 table referenced (`outreach.event_log`, `outreach.bookings`, `outreach.prospect_state`, `outreach.scheduled_send`, `outreach.heartbeat`) gets a v4 equivalent. The 9 v4 tables already in migration `0001_tenancy_and_content.sql` cover engagement; ports may need migration `0003_outreach_pipeline.sql`. 2. **Multi-tenancy at the port boundary.** v2 assumes single-tenant Quinn. Every ported function takes `(user_id, org_id?)` and every ported query has the v4 `tenant_isolation` RLS policy applied. 3. **Platform-action surfaces are Cocotte-internal.** Per [`@applications/@ai/CLAUDE.md`](../../../../../../@applications/@ai/CLAUDE.md), `@ai` owns cognition (identity / memory / personality / nag / context / process / relationship) — NOT surface adapters. Per Quinn-direct rule "if it's only used by cocotte it's not a peer @application," surface adapters belong **inside @cocottetech** at `@cocottetech/@platform/codebase/@features/{bookings,content}-*/adapter/`. When porting v2 tools (the 8 chat tools, the workers' dispatch sites), the surface-action tools land in `@cocottetech` features. 4. **Naming sweep at port time.** No `quinn.*` / `quinn-*` (provider-specific) and no `lilith*` (umbrella rename deferred — see rename memory). `cocotte.*` IS the correct brand-tier naming and stays (`cocotte.io` infra, `cocotte.maison` public brand, `cocotte.dev` OSS — see [[brand-family]] memory). `demimonde*` appears ONLY where the back-office LLC is genuinely the subject (Org seed row, financial flows, internal-only coop refs); never customer-facing. Use `platform.*` for service names and `@cocottetech/*` for package names. 5. **The Bun → NestJS rewrite is not optional.** v2 routes use Hono / Bun's `serve`. v4 routes are NestJS controllers. The handler bodies port; the routing scaffolding is rewritten. ## Out of scope (for this brief) - Database migration choreography during cutover (v2 keeps serving prod; v4 reads same iMessage stream via macsync mirror — coexistence specifics belong in INFRA / a runbook, not a design brief). - Authentication shape (briefs A + D cover SSO; this brief assumes shared `sso.cocotte.io`). - Cost/budget tracking for the LLM calls — relevant but covered in the model-boss + ai-core docs upstream. ## Open questions - **Where does the v2 `outreach.event_log` history live in v4?** Two options: (a) freeze v2's table, v4 starts a fresh `agent_actions` ledger; (b) backfill v4's `agent_actions` from v2's event log for historical visibility in brief I's audit drawer. Pick (a) for P0 (less migration risk), (b) as a P3 enhancement. - **`prospect-classifier-claude` invocation site.** v2 runs it on-demand from `/prospects` page. v4 has `prospect-resolver` worker — does it run continuously, or on-trigger? Probably trigger-based (per `engagement_events` row) to keep cost down. - ~~`comm-newsletter` deep-read priority.~~ **Resolved 2026-05-17**: shipped product, no AI in v2 (hand-composed bodies). v4 adds optional AI drafting via new `content-newsletter` specialist; dispatch + tracking port verbatim. See updated row above. - ~~Persona rename mechanical detail.~~ **Resolved 2026-05-17 (corrected to 2026-05-18)**: `cocotte*` personas stay named `cocotte*`. Cocotte IS the public brand. No rename needed. See [[brand-family]] memory. ## Related - v2 inventory raw notes — see this session's transcript (2026-05-17) where `quinn-ai/README.md` + `docs/architecture.md` + workers/agents/personas dirs were surveyed. - [[brand-family]] — Cocotte (public umbrella) vs Demimonde (back-office LLC) architecture. Personas stay `cocotte*`; never `demimonde*`. - [[jobs-to-metaphor]] — confirms the v4 framing of these v2 capabilities as domestic chores under Cocotte branding. - Brief I (`I-audit-trust-replay.brief.md`) — primary consumer of the ported event-log + correction-loop. - Brief H (`H-recurring-chores.brief.md`) — primary consumer of the ported engine-mode + nudges + scheduler.