atlilith/DESIGN.md
autocommit 05f2666088 chore(git): 🔧 Enforce LF line endings and mark binary files in .gitattributes
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-16 21:33:57 -07:00

25 KiB

@atlilith — Designing the V3 Platform

Status: Design phase Date: 2026-05-16 Lineage: v0 egirl-platform (viky-era, ambitious 27-app monorepo) → v1 lilith-platform (54-feature SaaS, shipped only partial) → v2 lilith-platform.live (Quinn-personal, currently in production) → v3 @atlilith (org-aware, provider-generic, multi-tenant)


1. Why a V3?

V2 works, but is outgrowing the single-person frame:

  • Quinn now operates 2 parent orgs (Lilith Apps, Cocotte), a tour (ATT), and multiple brands (Cocotte, Maison Sansonnet, ftw.pw, future merche biche)
  • transquinnftw is both a standalone provider AND org-admin of Cocotte — V2 has no concept of "org"
  • V2's user-data feature should really be org-analytics (analytics aggregating at both user AND org level)
  • V2's services are named quinn.* — fine for Quinn's instance, but ossifies the "platform = Quinn" assumption that prevents onboarding new providers
  • V1 has features V2 dropped (marketplace, profile/attributes, bookings, payments, reviews, trust, streaming) that will be needed eventually — V3 architecture must accommodate mining them

V3 keeps everything V2 has working, generalizes the naming, and adds the tenancy model V2 lacks.


2. Core Concepts

Three concentric layers

┌──────────────────────────────────────────────────────────┐
│  PLATFORM  (Lilith Apps ehf — the tech company)          │
│  Infrastructure, shared services, admin                  │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  PROVIDER  (user / org tenants)                     │ │
│  │  - Person: transquinnftw, future-provider-x         │ │
│  │  - Org:    cocotte, sansonnet, future-merche-biche  │ │
│  │  ┌──────────────────────────────────────────────┐   │ │
│  │  │  CLIENT  (the provider's customers)          │   │ │
│  │  │  Bookings, messaging-from-client-side        │   │ │
│  │  └──────────────────────────────────────────────┘   │ │
│  └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

Tenancy model: Person-first, Org-as-overlay

Person (primary tenant)
  ├── standalone mode (most providers start here)
  └── may admin or join one or more Orgs (optional)

Org (optional overlay)
  ├── owner: 1 Person
  ├── admins: N Persons
  ├── members: N Persons
  └── brands / domains / sub-entities attached

Worked example — transquinnftw + Cocotte:

  • transquinnftw is a Person tenant with their own profile (transquinnftw.com), inbox, bookings, analytics
  • cocotte is an Org tenant with its own dashboard, members, brand sites (cocotte.maison)
  • transquinnftw is the owner of cocotte — when logged in, they see a context switcher: [ Personal | Cocotte ]
  • Future providers can:
    • Operate as Person-only (no org needed)
    • Be a Person + admin of an Org
    • Be a Person who is just a member of someone else's Org (e.g., joining Cocotte as a managed talent)

What this is NOT

  • Not "org-as-workspace" where Person logs into a single tenant context (Slack/Notion model)
  • Not "single Person per Org" — Orgs can have multiple members
  • Not mandatory — most providers will operate without ever touching an Org

3. Architecture Principles

  1. Person-first, Org-optional. Onboarding never asks "what's your org" — that's a later upgrade.
  2. Provider-generic in code. Internal package names contain no person/org name. Quinn's instance lives at quinn.* domains, but the code is provider-portal, ai-assistant, messenger, etc.
  3. Sibling services stay sibling. mail-sync, mac-sync, knowledge-platform, agents — peer services consumed over HTTP/MCP. Not inside the platform monorepo.
  4. ML/AI work belongs in peer apps, never in the platform. Crystal-AI, image diffusion, translation, content moderation, classification, RAG — all live under ~/Code/@applications/@{ai,ml,imajin}/ and are called over HTTP/MCP. The platform contains orchestration (request routing, prompt assembly, draft tracking), never weights or training data. If a v1 feature mixed orchestration + ML, port only the orchestration part. No ml-service/ directories in @platform/.
  5. Brand sites are templates. Cocotte, Sansonnet, ATT, future merche biche — each is an instantiation of org-site/ with config, not a forked codebase.
  6. Monolith repo holds the full lineage. All three prior versions live inside this repo as zstd-compressed tarballs under .archive/, tracked through Git LFS. The whole point of @atlilith is to be the canonical workspace — having v0/v1/v2 co-located here is by design, not a bloat problem. (Practical caveat: the LFS push hit Forgejo's reverse-proxy client_max_body_size limit on the first attempt — that's a server config fix tracked in Phase 5.7, not an architectural pivot.)
  7. No leakage between tenants. Org A's data invisible to Org B. Person A's data invisible to Org A unless explicitly shared.
  8. Schema is the contract. Add orgs, org_members, org_id foreign keys. Migrations are forward-only; no downgrades after Phase 2.

4. Directory Layout

~/Code/@projects/@atlilith/
│
├── @platform/                       ← V3 monorepo
│   ├── codebase/
│   │   ├── @apps/                   ← user-facing apps (audience layer)
│   │   │   ├── platform-admin/      (was quinn.admin)
│   │   │   ├── provider-portal/     (was quinn.my — provider's dashboard)
│   │   │   ├── provider-site/       (was provider-website — public profile)
│   │   │   ├── org-site/            (template — cocotte-web, sansonnet-web)
│   │   │   ├── tour-site/           (template — adult-therapy-tours)
│   │   │   ├── ai-assistant/        (was quinn-ai)
│   │   │   ├── messenger/           (was quinn-messenger)
│   │   │   ├── landing/             (atlilith.com)
│   │   │   └── waitlist/
│   │   ├── @features/               ← cross-cutting domain features
│   │   │   ├── api/                 (Hono gateway — port 3030)
│   │   │   ├── sso/                 (org-aware JWT)
│   │   │   ├── org-analytics/       (was user-data)
│   │   │   ├── messages/
│   │   │   ├── mail-autoresponder/
│   │   │   ├── ai-engine/
│   │   │   ├── newsletter/
│   │   │   ├── client-intel/
│   │   │   ├── age-verification/
│   │   │   ├── image-protection/
│   │   │   ├── vip/
│   │   │   ├── merchant/
│   │   │   ├── tour-scout/          (was hotel-scout)
│   │   │   ├── price-watcher/
│   │   │   ├── edge-purge/
│   │   │   ├── db-monitor/
│   │   │   ├── seo/                 (extracted, with v1's ML pSEO mined)
│   │   │   └── platform-seed/
│   │   └── @packages/               ← shared libs
│   │       ├── auth-provider/
│   │       ├── i18n/
│   │       ├── mailer/              (HTTP client for mail-sync peer)
│   │       ├── provider-api-client/
│   │       ├── app-switcher/        (was quinn-app-switcher)
│   │       ├── ui-auth/
│   │       └── ui-dev-content/
│   ├── deployments/
│   │   └── @domains/                ← per-domain deploy configs
│   │       ├── atlilith.com/
│   │       ├── quinn.*/             (Quinn's instance kept under quinn.*)
│   │       ├── cocotte.maison/
│   │       ├── sansonnet.maison/
│   │       ├── adulttherapytour.com/
│   │       └── ...
│   ├── infrastructure/              (postgres, timescale, redis, minio, mailpit)
│   ├── docs/
│   ├── scripts/
│   ├── CLAUDE.md
│   ├── package.json
│   ├── bunfig.toml
│   ├── bun.lock
│   └── tsconfig.json
│
├── .archive/                        ← read-only history
│   ├── platform.0/                  (egirl-platform, v0)
│   ├── platform.1/                  (lilith-platform, v1)
│   ├── platform.2/                  (lilith-platform.live, v2)
│   └── ARCHIVED.md                  (lineage notes + mining map)
│
├── tooling/                         ← shared scripts (carried from @lilith)
├── .content-lifecycle/              ← content workflow config
├── CLAUDE.md                        ← project-root rules
├── DESIGN.md                        ← this file
└── README.md

Peer services (NOT inside @atlilith/)

These stay at their current locations and are consumed over HTTP/MCP:

  • ~/Code/@projects/@lilith/mail-sync/ — Proton Bridge wrapper (port 4444)
  • ~/Code/@applications/@mac-sync/ — macOS iMessage bidirectional sync
  • ~/Code/@applications/@messenger/ — separate messenger codebase (needs reconciliation with v2's messages feature)
  • ~/Code/@applications/@agents/ — Claude SDK agent monorepo (assistant, companion, egirl, nag, nudge, social, travel, quinn-voice, quinn-prospector)
  • ~/Code/@applications/@ml/knowledge-platform/ — Crystal's successor (knowledge verification, content auditing)
  • ~/Code/@applications/@ml/* — other ML pipelines (rag-retrieval, content-moderation, message-classifier, prospect-classifier-claude, cot-reasoning, draft-pipeline-claude, etc.)
  • ~/Code/@applications/@quinn-ios/ — Swift iOS app

5. Schema additions for tenancy

-- New tables in quinn.db (renaming to platform.db pending)

CREATE TABLE orgs (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  slug        TEXT UNIQUE NOT NULL,
  name        TEXT NOT NULL,
  owner_id    UUID NOT NULL REFERENCES users(id),
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE org_members (
  org_id      UUID NOT NULL REFERENCES orgs(id) ON DELETE CASCADE,
  user_id     UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  role        TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
  joined_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  PRIMARY KEY (org_id, user_id)
);

CREATE INDEX idx_org_members_user ON org_members(user_id);

-- Existing tables gain optional org_id where data can belong to either Person or Org:
ALTER TABLE bookings    ADD COLUMN org_id UUID NULL REFERENCES orgs(id);
ALTER TABLE brands      ADD COLUMN org_id UUID NULL REFERENCES orgs(id);
ALTER TABLE analytics_events ADD COLUMN org_id UUID NULL REFERENCES orgs(id);
-- (When org_id IS NULL, the row belongs to the Person via user_id)

Seed for V3 launch

-- Bootstrap Quinn + Cocotte
INSERT INTO users (id, slug, ...) VALUES ('<quinn-uuid>', 'transquinnftw', ...);
INSERT INTO orgs (slug, name, owner_id) VALUES ('cocotte', 'Cocotte', '<quinn-uuid>');
INSERT INTO org_members (org_id, user_id, role)
  VALUES ((SELECT id FROM orgs WHERE slug='cocotte'), '<quinn-uuid>', 'owner');

SSO JWT extension

interface SessionToken {
  user_id: string;
  device_id: string;
  // NEW: optional org context (set when user switches into org view)
  org_id?: string;
  org_role?: 'owner' | 'admin' | 'member';
}

UI: context switcher in provider-portal nav toggles between Personal and each Org the user is a member of. Switching emits a new JWT scoped to that context.


6. Feature Composition for V3

Carry from v2 directly (working code, just rename + org-context)

21 features: sso, api, landing, my→provider-portal, provider-website→provider-site, cocotte-web+sansonnet-web→org-site, adult-therapy-tours→tour-site, messages, quinn-ai→ai-assistant, quinn-messenger→messenger, comm-newsletter→newsletter, user-data→org-analytics, admin→platform-admin, age-verification, client-intel, image-protection, vip, merchant, hotel-scout→tour-scout, price-watcher, edge-purge, db-monitor, platform-seed, waitlist, ui-dev-content, ai-engine, mail-autoresponder

Mine from v1 (production-quality code worth porting)

Phase A (foundations for multi-provider): marketplace, profile, attributes, bookings, trust, queue-worker, feature-flags, bot-defense, media

Phase B (depth & monetization): payments, reviews, threat-intelligence, safety, cms, blog, streaming, health-verification, content-moderation, image-generator, webmap

Mine from v0 (older but unique)

onboarding flow, mobile-messenger patterns (if pursuing a native mobile build), drive / mobile-drive (if pursuing first-class file management), content-moderation ML lineage (Python-based)

Superseded — don't port, but don't dismiss either

The following were genuinely novel and useful work in v0/v1 that has since been superseded by dedicated peer apps under ~/Code/@applications/@{ai,ml,imajin}/. The platform calls those peers over HTTP/MCP; the platform itself contains zero ML inference code.

v0/v1 thing Superseded by (peer app)
knowledge-verification (v1, 262 tests, Crystal-AI's predecessor) ~/Code/@applications/@ml/knowledge-platform/
conversation-assistant/ml-service (v1, 4.5G) ~/Code/@applications/@ml/{assistant-trainer,chat,draft-pipeline-claude,message-classifier}
i18n/ml-service (v1, 4.4G translation model) ~/Code/@applications/@ml/ translation pipeline
image-generator/ml-service (v1, 4.2G diffusion weights) ~/Code/@applications/@imajin/
content-moderation/ml-service (v0+v1) ~/Code/@applications/@ml/content-moderation/
talent-scout/packages/captcha-solver (v1, 3.8G) rebuild via @applications/@ml/ if needed
LLM gateway / agents (v0+v1) ~/Code/@applications/@ai/{@agents,services,packages}

Drop entirely

v0/v1 work that didn't pan out or was just placeholder: dating-autopilot, bio-scraper, linky/link-tree, pitch-deck, investor-dashboard, user-guide (move pitch-deck/investor/user-guide content into business/ non-code), and v1 empty shells consumable, content-editing, video-studio, share, favicon-generator, platform-content-tools, platform-assistant.


7. Naming Convention

Concept Old (Quinn-specific) New (provider-generic) Deployed domain
Provider dashboard quinn.my provider-portal quinn.my (Quinn's instance), {provider}.my (others)
AI assistant quinn-ai ai-assistant quinn.ai (Quinn's), {provider}.ai
Messenger quinn-messenger messenger quinn.m (Quinn's), {provider}.m
Admin quinn.admin platform-admin platform.admin (single, shared)
SSO quinn.sso auth / sso sso.atlilith.com (single, shared root)
Analytics quinn.data / user-data org-analytics {provider}.data
Tour booking hotel-scout tour-scout (internal)

Rule: Quinn's deployed quinn.* domains remain — they are Quinn's instance of the generic system. New providers get parallel {name}.* domains at onboarding time. The shared root (SSO, admin) lives at atlilith.com.


8. Migration Strategy

Sequencing principle: infrastructure first, features second. Paving the road (ports, DBs, proxy, tunnel, ACS, backups, CI/CD) is the highest-effort / lowest-rework work — and every feature lands on top of it. Lock it down before touching feature code. The infrastructure phase replaces both the old "Phase 4 skeleton" and "Phase 5 schema" steps because the two are coupled (compose files + ports + migrations + Caddy must all agree).

Phase 1 — Scaffold (DONE)

Create @atlilith/{@platform,.archive,tooling,.content-lifecycle}. Write DESIGN.md, INFRA.md, CLAUDE.md, README.md. Initial git + remote setup.

Phase 2 — Archives located, NOT vendored (DONE)

Prior versions are referenced where they already live; v0 is locally cached on apricot at ~/.cache/atlilith-archives/platform.0.tar.zst because its NFS source is slow. No archives in git. See .archive/ARCHIVED.md and scripts/{cache-v0.sh,extract-archive.sh}.

Phase 3 — Carry-overs (DONE, no-op)

tooling/ and .content-lifecycle/ from @lilith/ are empty stubs — already mirrored as empty directories. mail-sync and other peer services stay where they are.

Phase 4 — Monorepo skeleton (DONE)

@platform/{package.json, bunfig.toml, tsconfig.json}, codebase/tsconfig.base.json, empty codebase/@{apps,features,packages}/, deployments/@domains/, infrastructure/, docs/, scripts/.


Phase 5 — Infrastructure foundation (NEXT, must complete before any feature work)

Goal: a deployable, observable, recoverable system with all the glue in place. No feature code yet. When this phase ends, manage-apps start atlilith apricot brings up an empty-but-functional platform, deploys can be triggered via ./run deploy:<service>, and backups + tunnels run unattended.

5.1 Port registry & env

  • infrastructure/ports.yaml — adapted from v2; provider-generic service names (no quinn.*); new port ranges that don't conflict with v2 running in parallel (e.g. 3040-3059 APIs, 5210-5300 frontends, 25440-25445 PGs)
  • infrastructure/.env.ports — committed env file in sync with ports.yaml; sourced by manage-apps

5.2 Databases

  • infrastructure/compose.platform-db.yml — main Postgres (port 25440)
  • infrastructure/compose.platform-minio.yml — MinIO for object storage
  • infrastructure/pg-services.yml — PostgREST/service config if used
  • infrastructure/platform-db-init.sql — base schema (adapted from v2's quinn-db-init.sql)
  • infrastructure/sql/migrations/:
    • 001_add_orgs.sqlorgs + org_members tables + owner-membership trigger
    • 002_seed_cocotte.sql — inaugural Cocotte org, transquinnftw as owner
    • (more as features land — every feature ships its own migrations)

5.3 Reverse proxy (Caddy)

  • infrastructure/Caddyfile.local — dev TLS for *.atlilith.apricot.lan
  • infrastructure/certs/ — self-signed certs (gitignored)
  • infrastructure/gen-local-certs.sh — regenerates certs as services are added
  • Production: per-host Caddyfile templates under deployments/@domains/<domain>/

5.4 SSH reverse tunnel (vps-0 ↔ black)

  • infrastructure/scripts/setup-tunnel.sh — establishes ssh -R 25440:localhost:25440 -R 25441:localhost:25441 vps-0 from black
  • systemd user@.service unit + autossh for persistence + reconnect
  • Health-check script: vps-0 can pg_isready -h localhost -p 25440
  • INFRA.md §5 documents this tunnel direction (black-initiated for security)

5.5 ACS integration

  • users/transquinnftw/app.manifest.yamlmanage-apps service registry for @atlilith (apricot + black + vps-0 platforms each with their services)
  • Verify ACS picks up @atlilith repo (it already commits; just confirm hooks/CI integration)
  • Pre-commit hook for pnpm typecheck / bun lint on changed files

5.6 Backups & DR

  • infrastructure/scripts/backup-pg.sh — nightly pg_dumpall from black → restic repo on apricot tank
  • infrastructure/scripts/restore-pg.sh — counterpart with --dry-run guard
  • MinIO replication: vps-0 (hot) → black (cold) via mc mirror cron
  • Forgejo daily mirror push to GitHub (existing org policy)
  • Verification: end-to-end restore drill into a scratch DB on apricot

5.7 Build / deploy pipeline

  • run shell entrypoint at repo root (./run dev, ./run dev:stop, ./run dev:status, ./run dev:logs, ./run build, ./run deploy:<service>)
  • .forgejo/workflows/:
    • typecheck.yml — runs on every push (bun install && bun run typecheck)
    • lint.yml — runs on every push
    • deploy-template.yml — reusable workflow each feature can call with with: service: provider-portal
  • Per-service deployments/@domains/<svc>/deploy.sh template — rsync to vps-0/black, systemd reload, smoke test

5.8 Verification gates (must all pass before Phase 6)

  • manage-apps start atlilith apricot brings up DB + MinIO + mailpit cleanly
  • manage-apps status atlilith apricot returns healthy for every service entry
  • psql -h apricot -p 25440 -U platform -c "SELECT * FROM orgs WHERE slug='cocotte'" returns 1 row
  • JWT-bearing curl through Caddy to a placeholder route succeeds (TLS works)
  • vps-0 can connect to black's PG through the tunnel
  • Backup script runs end-to-end (backup-pg.sh && restore-pg.sh --to=/tmp/restore-test)
  • A trivial commit triggers .forgejo/workflows/typecheck.yml and it passes

Files to read/carry from v2:

  • ~/Code/@projects/@lilith/lilith-platform.live/infrastructure/{ports.yaml,.env.ports,Caddyfile.local,compose.*.yml,pg-services.yml,quinn-db-init.sql,gen-local-certs.sh,setup-*.sh}
  • ~/Code/@projects/@lilith/lilith-platform.live/users/transquinnftw/app.manifest.yaml
  • ~/Code/@projects/@lilith/lilith-platform.live/run

Phase 6 — V2 carry-over wave (features start here)

Port the 21 "carry directly" features from v2 (see §6). Rename internal package names (quinn-aiai-assistant, etc.). Verify deployment to existing quinn.* domains still works through the new infra (no drift).

Phase 7 — Context switcher UI

Provider-portal nav: transquinnftw ▾ dropdown → [ Personal | Cocotte ]. Org view shows member roster, org-level analytics, brand settings.

Phase 8 — V1 mining (Phase A foundations)

Marketplace, profile/attributes, bookings, trust, queue-worker, media, feature-flags, bot-defense. Adapt to org-aware schema.

Phase 9+ — V1 mining (Phase B depth)

Payments, reviews, streaming, content-moderation, threat-intelligence, etc. — sequenced by demand.

Phase 10 — Deprecation

Once V3 is at parity + org-aware: cut over quinn.* domains to V3, decommission V2 deployment. Archive V2's running infra.


9. Open Questions

  1. Operating host: should @atlilith live on apricot only, plum only, or both (synced)? Default assumption: develop on plum, push to apricot for deploy.
  2. v0 variants: archive only egirl-platform/ as platform.0, or also include -worktrees/, -releases/, -partial-backups/, egirl.vault/ as .archive/platform.0-variants/?
  3. @messenger vs messages feature: ~/Code/@applications/@messenger/ exists separately from v2's messages feature. Are they the same code? Different concerns? Needs reconciliation before V3 messaging starts.
  4. knowledge-platform lineage: v1 had features/knowledge-verification (262 tests, Python). crystal-cli (Feb 2026) was a CLI wrapper. @applications/@ml/knowledge-platform v2.0.0 is the current successor. Confirm: is this dead code or active? Should it stay peer, or fold into V3?
  5. Mining sequence: which V1 feature lands first in V3 — marketplace (multi-provider discovery), profile/attributes (provider profile depth), or trust/safety (verification before opening up)?
  6. Client surface: when V3 launches, do clients have accounts (logged in to view bookings, message providers), or stays anonymous-on-request?

10. Success Criteria

V3 is considered shipped when:

  1. Quinn's quinn.* deployments are running on V3 code (no V2 infrastructure left)
  2. Cocotte exists as an Org with Quinn as owner; org context switcher works in provider-portal
  3. A second provider (e.g., merche biche) can be onboarded as a Person + optional Org without code changes
  4. All 21 V2-carry features work in V3
  5. At least one V1-mined feature (likely marketplace OR profile) is live and providing value beyond V2
  6. V2 deployment is shut down; .live directory becomes purely historical (.archive/platform.2/)

Appendix: Sources

  • v0 source: apricot:/mnt/bigdisk/_/last-linux-backup/applications/src/@egirl/egirl-platform/ (27 apps, 23 services, 14 packages, viky-era)
  • v1 source: ~/Code/@projects/@lilith/lilith-platform/ (54 features, 38 packages, 72GB, never deployed end-to-end)
  • v2 source: ~/Code/@projects/@lilith/lilith-platform.live/ (24 features, 7 packages, 27 live domains)
  • Peer apps source: ~/Code/@applications/{@mac-sync,@messenger,@agents,@ml,@quinn-ios}/