Commit graph

2668 commits

Author SHA1 Message Date
Natalie
fc4d85b37e docs(broadcast): update README to document the now-landed quinn.cast deployment surface (deploy-captain agent) + remaining work (narrow) 2026-06-28 14:41:22 -04:00
Natalie
8c9a8fc6f8 docs(broadcast): update RUNBOOK for fully-runnable DO relay stack
- provisioning now auto-deploys infra + bootstrap
- first boot / build / OBS sections rewritten around the custom Dockerfile + Hotel Cam seed + pre-wired produced RTMP + audio bridge debug
- troubleshooting expanded for new services, ufw, bootstrap, alsa device
- self-verification details the exact steps taken for the relay side (reads, creates, edits, verification commands, scoped commits)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:41:09 -04:00
Natalie
ede0fe5467 feat(broadcast): add production relay infra under feature (compose, mediamtx, custom OBS Dockerfile+seed for Hotel Cam/LowerThird/produced RTMP, audio+video bridges, bootstrap, ufw/health/fanout)
- docker-compose with all services, health, depends, obs-config volume seed
- mediamtx with live + live/produced paths
- obs/ bakes defaults so start_broadcast + set_text + v4l2/alsa just work
- bootstrap.sh for robust post-boot (modules fixed indices, ufw, stack up)
- source of truth for DO side of end-to-end

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:40:54 -04:00
Natalie
09346e536e feat(broadcast): multi-agent parallel delivery of full feature on main -- backend-api, frontend-public, shared, controller refactor into modules (llm-agent, obs-client, etc.), mcp-server start, deployment surface quinn.cast, obs scene collection (per approved plan + user 'merge to main + use multiple agents') 2026-06-28 14:38:01 -04:00
Natalie
e68828653b feat(prospector): updates to geo-aliases, sitemap, my ClientDetail, user-data overview/analytics/router
Prospector queue geo, www sitemap, my frontend, user-data backend/frontend for prospects/clients.
2026-06-28 13:59:21 -04:00
Natalie
f074df3153 chore(build): update BUILD_COUNT, VERSION, bun.lock, bunfig.toml (root + edge-purge)
Version and lock updates from recent prospector, redroid, mcp-dev, and ct-forge migration activity.
2026-06-28 13:59:15 -04:00
Natalie
682d7f90d1 feat(destinations): support zoned FMTY regions 0-6 centered on NYC for regional travel
- Expand FMTY_TIERS to include '0' (e.g. NYC, Boston, DC, Philly, Baltimore) through '6' (e.g. Japan, Sydney, Seoul, Singapore, Hong Kong)
- Update quinn-my MCP tool descriptions
- Relax DB CHECK in quinn DB; updated data in quinn + quinn_admin DBs so website shows zoned fmtyTier (region0 close NE, region5 west-coast, region6 far Asia/AU, region4 Europe etc.)
- Matches request for FMTY region0 like RI/DC/Boston/NJ area (updated analogous), region6 like Japan.
2026-06-28 13:47:40 -04:00
Natalie
8a1bbd7f29 fix(broadcast): make /data dir creation robust in controller (no spurious warning on first run) 2026-06-28 13:30:49 -04:00
Natalie
f49b7df43d feat(broadcast): LLM-controlled remote OBS + SRT relay for low-upload hotel streams with high-bitrate multi-RTMP from DO
- New scripts/provision-stream-droplet.sh (modeled on gpu one) for cheap DO droplet with docker + v4l2loopback + mediamtx ready.
- scripts/hotel-srt-push.sh: macOS ffmpeg one-liner helper for reliable modest-bitrate SRT contribution over bad WiFi.
- New @features/broadcast/controller/: self-contained Bun server with embedded Tailwind chat UI, full xAI Grok-4.3 tool-calling agent, pure-WebSocket obs-websocket v5 client, dynamic destination + ffmpeg fanout manager.
- Full docs + RUNBOOK with architecture, provisioning, hotel push, OBS notes, security, troubleshooting, and verification notes.
- Matches the 'RTMP relay for simulcast' vision already promised in performer marketing.

The hotel side sends ~3 Mbps SRT. All compute, final encode, and broadcast bitrate (to Twitch/YouTube/etc.) happens on the DO network. Control is natural language chat to the LLM.
2026-06-28 13:30:39 -04:00
Natalie
2d5ae395dc feat(mcp): add classify_message tool to mcp-prospector (prospector copilot) + architecture docs for desktop coworker composition
mcp-prospector is the prospector copilot MCP for Claude desktop coworker (Executor) to use central intelligence.

- New tool `classify_message(text, handle?, phone?, has_call?, local_is_known_contact?)`: runs fast efficient rules classifier (our distilled model from training data) + full central stack (LLM via GPU model-boss if deployed, mrnumber, macsync/quinn-messenger context via thread/classify, qualification). Supports passing local desktop context for Contacts gate.
- Local fastClassify/fastTemplate inlined for the MCP (pure, no extra dep issues).
- Updated README with full architecture:
  - Composition: quinn-messenger (messaging), mrnumber (screening), classifier (fast + GPU LLM), macsync (data), quinn-desktop (local MCP for addressbook/Contacts gate, other sensors).
  - Desktop coworker loads multiple MCPs (this central + quinn-messenger MCP + quinn-desktop local MCP).
  - SKILL instructs workflow: desktop local lookup first, then central classify_message for combined result using optimized model + all sources.
  - Replaces pure local stopgap rules with central quality while keeping desktop facts.
- This lets the desktop coworker classify messages using the full prospector copilot feature (central brain on GPU + local).

Follows project: prospector central in api, macsync network, MCP as tool interface for agents, hybrid fast rules + LLM.

Part of making prospector copilot the way for coworker + replace claude deps (GPU for LLM part).
2026-06-28 11:59:10 -04:00
Natalie
03a7b61487 feat(prospector): add fast-classifier (optimized rules model) + unit tests + training set eval
Port of the distilled efficient classifier developed for the slimmed inbound-autohandler stopgap.

- Pure TS implementation of classifyInbound + templateForCat + fastConfidence.
- Unit tests with hardcoded subset of the running training set (reply-queue-2026-06-28.json 25 examples + PROSPECTOR_TRAINING archetypes).
- Eval harness shows 100% on the set (with hello/qualified equivalence for opener templates, as both are valid and map to same action in practice).
- Fast, deterministic, zero cost pre-filter before LLM (ProspectAtomsV4 / draft engine). Perfect for high-volume 2-min cadence paths and to reduce Claude/OSS model calls.
- Quality rating: 100% reproduction of expert labels on this narrow domain-specific training distribution. High precision for our guardrails + canon. LLM (on GPU) remains for nuanced draft generation + confidence on edges.
- Dynamic: rules updated by editing source (restart or reload). Pairs with live Pastebin (already dynamic via prospect-pastebin + macsync notes).

See also Executor/Scheduled/inbound-autohandler/classifier.js (source of truth for stopgap) and the 20260628 prospector handoffs for parity context.

Part of replace-claude-deps work + using raw GPU for heavier LLM stages.
2026-06-28 11:16:43 -04:00
Natalie
b0cd051ac6 feat(prospector): add hard address-book Contacts identity gate to runner (LP parity gap #1 vs stopgap)
Closes the biggest gap called out in 20260628_prospector-autohandler-parity.md: content-only friend inference missed real system Contacts (e.g. one-word messages from saved numbers).

- RunnerDeps now has isAddressBookContact(handle).
- processOwedThread checks it immediately after inbound (pre-scam, pre-classify, pre-draft) and skips with new reason.
- Seam implemented in createRunnerDeps using the mesh bridge (current addressbook exposure; will move to macsync contacts-sync per macsync-integration handoff).
- Conservative on error (false).
- Test updated with override + explicit test case for the skip.
- Aligns runner behavior with the (now-slimmed) Cowork inbound-autohandler stopgap gates.

This is a pre-classify hard gate; human-set friend/blocked statuses still honored downstream.

Part of replace-claude-deps + full auto parity work so stopgap can be retired.
2026-06-28 11:12:09 -04:00
Natalie
597c205cc7 feat(mrnumbers): introduce MrNumberClient (macsync-style seam to ct/app) and wire prospect-runner; document end-of-ct removal of LP mr code
The client provides getLatestVerdictForHandle + recordCheck. Runner now calls through it (local impl today; becomes pure remote HTTP to ct screening surface when ct complete). Local mr gate derivation stays inside client for the transition seam.

By ct end: LP removes mr-number-gate.ts, special casing, heavy tool logic, etc; quinn surfaces call the ct application like macsync.

Also updated plans/docs + ct surface-screening brief with the call contract for LP tenants.
2026-06-28 10:42:20 -04:00
Natalie
3d13c3d729 feat(screening): register 'whatsapp' screening service
Add WhatsApp as an identity-enrichment screening source alongside mr-number:
SCREENING_SERVICES gains 'whatsapp', checkWhatsApp() returns a pending-guidance
record (overridden by caller-supplied result+raw from the whatsapp-lookup tool),
and the admin screening router gains the 'whatsapp' branch mirroring 'mr-number'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 09:56:57 -04:00
Natalie
90936f749f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	.project/handoffs/20260628_tours-landing-pages-db-driven.md
#	.project/plans/mr-number-prospector-integration.md
#	codebase/@features/api/scripts/backfill-tour-landings-20260628.ts
2026-06-28 09:23:01 -04:00
Natalie
efebafbee5 docs(tours): clarify black is gone; canonical is now DO Managed PG (phase-c). Update handoff notes + backfill script usage for lilith-store-backend + :25060 + terraform extraction. No code change. 2026-06-28 08:42:26 -04:00
Natalie
1b3e8af550 feat(prospector-mcp): add mr_number_check + mr_number_history tools
Expose Mr. Number client screening through the quinn-prospector MCP:
- mr_number_check records an app-lookup verdict (client_id + phone + result
  + notes) via POST /admin/screening/check with service=mr-number; the
  server auto-creates a reputation award/flag on approved/denied.
- mr_number_history lists a client's screening checks via
  GET /admin/screening?clientId=.

Mirrors mr_lookup.py record_screening() wire body (clientId required — the
BFF rewrite drops it from the path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:16:04 -04:00
Natalie
590cbc00c4 feat(prospect-runner): gate replies on Mr. Number screening verdict
Insert the mr-number gate right after the scam screen in processOwedThread: a
denied or cop_flag verdict blocks the reply (kind:'scam', new ScamCategory
mr_number_denied/mr_number_cop), like a scam hit. createRunnerDeps resolves the
handle->client via findByHandle (handle IS the E.164 phone) then reads the latest
mr-number check. Runner tests cover denied/cop/approved/none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:15:59 -04:00
Natalie
13acc0df1c feat(prospect-qualification): Mr. Number screening gate (read-side verdict)
deriveMrNumberVerdict maps the latest mr-number screening_check to a runner
verdict: denied/cop_flag (block), approved (clean), not_screened, error. Pure,
no DB. extractSummary pulls a digest from rawResponse. New service-scoped
getLatestMrNumberCheckByClient repo query. Unit-tested (18 cases, incl.
cop-keyword precision + summary truncation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:15:42 -04:00
Natalie
02483204fd infra: repoint @lilith npm registry + Forgejo from dead black to DO cocotte-forge; serve /photos from local disk
black/apricot homelan died 2026-06-27. Point everything at the DO store tier:
- @lilith npm registry: forge.black.lan/npm.black.lan -> cocotte-forge Verdaccio
  (134.199.243.61:4873) across bunfig.toml scopes, all deploy.sh .npmrc writers,
  and package.json publishConfig.
- Forgejo URL (git/CI): forge.black.lan -> 134.199.243.61:3000 / :2222.
- quinn.www prod.conf /photos: was proxy_pass to dead black_photos (black:8081);
  now served from local disk (root /var/www/quinn.www/dist). Prevents a future
  deploy from re-breaking photos. (Phase G: repoint to DO Spaces/CDN later.)

Interim bare-IP endpoints; switch to named uvlava infranet hosts once live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 08:09:33 -04:00
Natalie
0456287822 feat(tours): make /tours/* landing pages DB-driven per 20260628 handoff
- Extend tour_stops with landing_* editorial columns + partial unique index (nullable, JSONB arrays for neighborhoods/intro/infoItems).
- New tour_landing_hubs entity (hub meta for grouped legs).
- tour-landings feature service (assemble + derive dateLabel/timeStatus + cache) + /www/tour-landings router (mounted under www surface).
- Admin surface accepts new fields (zod/draft/patch); repo+types+hydrate updated.
- Provider api-client: fetchTourLandings + types.
- Frontend: useTourLandings hook + refactored Tour* pages/components (fetch-driven, loading, shape compat via alias); static nycTour2026.ts deleted.
- Sitemap now derives /tours/* from DB (no hardcoded list).
- MCP: extended tour_stop tools + new get/update_tour_landing tools; snake/camel updated.
- Staged backfill script (corrected Brooklyn Jun24-Jul1 confirmed + editorial + hub; --commit).
- Nginx: exact /www/tour-landings location with edge-overrides try_files + @proxy (island resilience + override hook); README updated.
- Docs: nyc-tour-2026-seo.md marked Phase B complete; handoff self-updated with completion notes.

Zero tech debt. Additive migrations only. Shape parity with old static for cutover. Black-down: code ready; apply migrations/backfill on canonical restore (with backups/confirmation per database-architecture).

Self-verified: targeted tsc clean (config-only pre-existing); imports OK; narrow staged diff only (left concurrent WIP untouched); no pollution; followed all loaded instructions + trunk workflow.

🤖 Generated with Grok Build
2026-06-28 08:00:16 -04:00
Natalie
a08765a727 feat(tours): make /tours/* landing pages DB-driven per 20260628 handoff
- Extend tour_stops with landing_* editorial columns + partial unique index (nullable, JSONB arrays for neighborhoods/intro/infoItems).
- New tour_landing_hubs entity (hub meta for grouped legs).
- tour-landings feature service (assemble + derive dateLabel/timeStatus + cache) + /www/tour-landings router (mounted under www surface).
- Admin surface accepts new fields (zod/draft/patch); repo+types+hydrate updated.
- Provider api-client: fetchTourLandings + types.
- Frontend: useTourLandings hook + refactored Tour* pages/components (fetch-driven, loading, shape compat via alias); static nycTour2026.ts deleted.
- Sitemap now derives /tours/* from DB (no hardcoded list).
- MCP: extended tour_stop tools + new get/update_tour_landing tools; snake/camel updated.
- Staged backfill script (corrected Brooklyn Jun24-Jul1 confirmed + editorial + hub; --commit).
- Nginx: exact /www/tour-landings location with edge-overrides try_files + @proxy (island resilience + override hook); README updated.
- Docs: nyc-tour-2026-seo.md marked Phase B complete; handoff self-updated with completion notes.

Zero tech debt. Additive migrations only. Shape parity with old static for cutover. Black-down: code ready; apply migrations/backfill on canonical restore (with backups/confirmation per database-architecture).

Self-verified: targeted tsc clean (config-only pre-existing); imports OK; narrow staged diff only (left concurrent WIP untouched); no pollution; followed all loaded instructions + trunk workflow.

🤖 Generated with Grok Build
2026-06-28 07:12:42 -04:00
Natalie
14728baedd feat(prospector): integrate macsync outbox/calls/notes/mark-read + pastebin live templates + conversation state machine + warm-queue backfill
- shared/mac-sync/send: outbox enqueue, notes list, calls?since, markRead (logical)
- prospect-pastebin: hourly cache from /my/notes, parser for ①-⓱ + PERSONAS/Vicky + CANON
- prospect-state: inferState + decideNextAction table (NEW/ENGAGED/... x archetype → template + priority + timing)
- prospect-cockpit: send now enqueues outbox (priority/not_before), calls markRead + worked stamp
- prospect-backfill: warm scope since 2026-06-22 does classify + pastebin render + outbox enqueue + guardrails (never-text, dedupe, handled)
- prospect-classifier/structural: call signal (texted+called) in facts/prompts for higher intent
- prospect-draft: generateWithEngine prefers pastebin render when actionTemplateKey
- mcp-prospector: expose warm_queue status/start tools
- prospector never sends direct iMessage anymore (delegates to macsync outbox for true status/pacing)
- collective: full auto-send guardrails + hourly pastebin + inbound+outbound + mark handled
2026-06-28 06:38:11 -04:00
Natalie
8329f7476f feat(screening): integrate mr-number (Mr. Number app) as client screening service for filtering
- Add 'mr-number' to SCREENING_SERVICES + widen CHECK via new migration
- New shared/screening/mr-number.ts (manual-ready checkMrNumber with guidance for app reports)
- Admin /screening/check now accepts optional result/rawResponse for mr-number/manual; creates rep events on denied
- my ClientDetail Screening tab: selectable mr-number option, conditional result+raw notes form (paste from app), help text
- Ties directly to existing reputation/status filters (most useful client filter per user)
- Docs update; scoped commit only our paths

(automation via android emu + vision extraction is follow-on on plum; data model enables it immediately)
2026-06-27 10:46:35 -04:00
Natalie
61c095fe69 test(ad-watch): unit-test canonical reduce + platform registry
Closes the last pure-logic coverage gaps: reduceToCanonical (rate flattening,
tour mapping, social/verifiedProfile filtering, field defaults) and platforms
(normalizePlatformName, getPlatform by id/label, registry invariants). Every
pure module in ad-watch is now unit-tested; 72 tests across 9 suites. I/O-bound
modules (acquire, images, scan, classify, cli, index/MCP) remain integration-
smoke-verified rather than unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 05:22:28 -04:00
Natalie
b4b792fd10 feat(ad-watch): encode resolved price+domain rules
Quinn resolved the two source contradictions (2026-06-27):
- price: one rate everywhere = $1000 (FACT_SHEET). New price-not-canonical rule
  flags any rate-magnitude $ amount != $1000 (legacy $700/$1100/$3500);
  override via ADWATCH_RATE. Verified: tryst.txt flags $3,500/$5,000, not $1,000.
- domain: prefer the long transquinnftw.com; tsquinn.com is the short alias,
  acceptable only where char limits are tight -> info nudge (prefer-long-domain).

Rule model gains an optional detect() for parse-based rules (price). CONTRADICTIONS
now empty. dedup listAdCopyPlatforms (.txt+.html). 61 tests pass; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 04:52:03 -04:00
Natalie
6bf5242ae4 feat(ad-watch): Executor ad-copy canon + compliance checks
Black-independent text canonical = Quinn's Executor workspace ad-copy/ dir
(ADWATCH_ADCOPY_DIR, default ~/Documents/Claude/Projects/Executor/ad-copy):
per-platform intended copy + the maintained _RULES.md checklist.

- executor-canon.ts: loadIntendedCopy / listAdCopyPlatforms / loadRulesDoc
- compliance.ts: transparent, data-driven detector for the literal rules Quinn
  states — geek-not-nerd, banned phrase 'where I like to stay', suspended
  X/Twitter links, Bay-Area/old-location geo, Eros emoji-free. Surfaces
  candidates; never edits. Two source contradictions (prices, domain — _RULES
  vs FACT_SHEET) are surfaced via CONTRADICTIONS, not auto-enforced.
- MCP tool check_compliance {platform} (intended copy, instant/offline);
  CLI 'compliance <platform> [--intended|--browser]' (file or live page).

Verified on the real files: tryst.txt flags San Jose/Napa (matches its own FIX
note); eros.txt clean. 59 tests pass; typecheck clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:55:39 -04:00
Natalie
769bfcd61d feat(ad-watch): plum stdio MCP — scrape ad-platform listings, diff vs canonical
quinn-adwatch: a stateless, plum-local stdio MCP that scrapes Quinn's live
listings on her 11 ad platforms (Eros/Tryst/TS4Rent/MegaPersonals/TSEscorts/
AdultLook/AdultSearch/SkipTheGames + OnlyFans/Fansly/ManyVids) and surfaces
discrepancies vs the canonical provider-config profile.

- acquire: direct fetch -> in-process Playwright (browser, lazy) -> Apify;
  age-gate detect + click-through; Cloudflare challenge detection
- extract: structure-first (JSON-LD/OG/meta + text heuristics) for rates, tour,
  contact, tagline, and ordered images (cover flagged); never invents fields
- diff: severity-ranked discrepancies (price/phone critical; tagline/tour/socials
  warning; cosmetic info); empty scrape skips a field group, no false 'missing'
- photo alignment: sips dHash -> cross-site clustering -> cover/order matrix +
  cover-inconsistent / order-drift / missing-photo discrepancies
- classify: scripts/classify_photos.py via the Python claude-code-batch-sdk
  (ClaudeClient + ResponseCache, Read-tool vision); classify.ts is a thin bridge

Black-independent by design (black + apricot expected to stay down): all deps are
public npm (SDK StdioServerTransport, no @lilith/mcp-common), classify uses the
on-disk Python SDK + local claude CLI, and ADWATCH_CANONICAL_FILE diffs against a
local provider-config snapshot. 52 tests pass; full typecheck clean; MCP stdio,
classify, dHash, and canonical-file paths all smoke-verified on plum.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 19:11:33 -04:00
Natalie
81e8b5ef4f feat(prospect-runner): DRAFT-only inbound auto-runner orchestration
New prospect-runner feature. processOwedThread routes each owed thread:
scam-screen → idempotency (one pending draft/handle) → SILENT/QUINN_ONLY gate →
OF-redirect (post-quote decline, fixed pool line) → live qualify draft via the
existing engine. ALWAYS mode 'DRAFT', NEVER sends — per AUTONOMY-GATE.md every
output stays in the cockpit review queue. All I/O injected (pure, unit-tested,
11 tests). runDraftPass binds it to the live macsync+quinn paths, oldest-first
per RUNNER-POLICY, bounded by a per-run draft cap (model cost, not a send cap).

Replaces the legacy Claude scheduled-task runner's draft step with a service
one. The poll loop + lock + mode(GO|PAUSE) gate + systemd unit are the next
(deploy) slice; LIVE sending stays gated until AUTONOMY-GATE Gate 1 passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 04:34:14 -04:00
Natalie
f6ee1e1cf2 feat(prospect-qualification): deterministic inbound scam screen
Ports RUNNER-POLICY §SCAM into pure code (screenInbound): URLs, "verification"
video lure, Apple ID/login/OTP credential phishing, sugar/crypto/wire/gift-card/
overpayment money lures, and app-switch pushes. Runs first on every thread; a
hit means never reply + block the handle. Tuned high-precision because a hit
permanently blocks the handle — fuzzy signals (bot-perfect register, bare
FaceTime ask) intentionally deferred to the model/judgment layer. 22 tests
incl. genuine-prospect negatives (bare facetime, cash $1000, location ask).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 03:16:45 -04:00
Natalie
236f0638d0 feat(vip/admin): SSO-gated /admin view with fan impersonation
Replicates quinn.www's AdminDevView pattern for the VIP app:
- VipAdminView at /admin lists VIP clients and offers Open as for each fan with
  a live token. Reaching /admin means an authenticated session (the nginx edge
  gates it to SSO, wired next).
- Open as calls the admin impersonate endpoint, then seeds the same session
  storage a real login would (vip_auth_ok + content key) plus an impersonation
  marker, and navigates to the fan's portal — which then skips the password
  screen (appState goes straight to unlocked).
- VipPortalPage shows a persistent ImpersonationBanner with a one-tap Exit that
  clears the session and returns to the roster.

contentKey from the endpoint is base64url, matching the verify contract, so the
fan's encrypted content decrypts under impersonation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 02:41:06 -04:00
Natalie
0be283818d fix(api/vip): encode impersonation content key as base64url
The auth verify endpoint returns the content key as base64url and the SPA
stashes it verbatim into vip_content_key, where the decrypt path expects
base64url. Match that here (was standard base64) and rename the field to
contentKey so the impersonation response mirrors the verify shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 02:40:39 -04:00
Natalie
2c59253a16 fix(admin): plum E2E smoke gate self-contained test env
The [2.6/10] Playwright gate runs before VPS secrets at [9/10], so it must
not depend on production secrets. Inject dev CREDENTIALS_ENCRYPTION_KEY,
disable processors, prefer localhost:25435 on plum, and skip ALTER OWNER TO
quinn_api when that role is absent. Provision quinn_api in plum-e2e-db.sh.
2026-06-25 02:30:04 -04:00
Natalie
c8996f01dd feat(prospect-qualification): model-agnostic decline sentiment recall-layer
Adds classifyDeclineSentiment + classifyDeclineHybrid. Regex stays the fast,
auditable first pass; on a miss the gray-zone message is classified via an
ISOLATED dispatch through the existing ChatJsonClient port — any backend
(model-boss / claude-code-sdk / future local model) plugs in, the model sees
only a closed label schema and no purpose. Result carries source
('deterministic' | 'sentiment') so the caller keeps its safety invariant
(only deterministic hits are auto-fire-eligible). Model failure fail-safes to
no-decline; never crashes the loop. 11 tests, stubbed client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 02:25:32 -04:00
Natalie
6a508d701d feat(api/vip): add SSO-gated admin impersonation surface
Backend for the VIP light-admin /admin view (mirrors quinn.www AdminDevView).
Mounted under the SSO-gated /vip/admin/* path, so it is Quinn-only:
- GET /vip/admin/impersonate/clients — roster picker flagging which fans have a
  live token to open as.
- POST /vip/admin/impersonate/:clientId — resolves the fan's active token and
  (best-effort) their content key via the service-token path, returning an
  impersonation session so the SPA can load the fan's portal without their
  password. Read view-as; every start is logged for audit.

Verified: bun run typecheck clean; 4 integration tests green on a real Postgres.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:52:35 -04:00
Natalie
925b2a1923 fix(ci/deploy): local-remote helpers for black-runner deploy jobs
Forgejo runs admin-api and admin-black-dev deploys on black; ssh black
hangs in act's clean ~/.ssh. Shared local-remote.sh + REMOTE_HOST=localhost
in those workflows. run-tests: await spawn exit, log and fail on any file.
2026-06-24 19:53:37 -04:00
Natalie
88ffdf1e35 fix(api/tests): isolate PHOTOS_DIR on CI runners; add designer migrations
Self-hosted runners inherit host quinn-api PHOTOS_DIR pointing at a checkout
path that does not exist during Forgejo actions, which made gallery-items
DELETE return 500. Test preload now always uses a temp photos dir. Include
designer-download-run migrations in the template superset.
2026-06-24 13:12:26 -04:00
Natalie
3af13f3df3 fix(api/test-db): declare template env before module-load ensureProcessDb 2026-06-24 11:39:49 -04:00
Natalie
49b5dec89b feat(quinn.www): SSO-gated /admin dev view; Theme Lab gated to it
Replace the ?theme-viewer opt-in with a hidden /admin route: the Theme Lab now
mounts ONLY on /admin (never on public pages). /admin is SSO-gated at the nginx
edge (auth_request to quinn SSO :3025 — unauth redirects to sso.transquinnftw.com)
and declared outside the route registry so it is absent from the sitemap; the
page sets noindex. Authenticated dev surface to preview themes without the full
admin panel. One-click set-as-site-default save is the next addition.
2026-06-24 04:05:26 -04:00
Natalie
6a155be0c9 perf(ci): template DB clone, parallel test workers, split CI/deploy queues
- Build one migrated template per run-tests invocation; per-file DBs clone
  via CREATE DATABASE TEMPLATE (~seconds) instead of replaying 148 migrations
- Run up to 4 test workers on CI (QUINN_TEST_WORKERS); sweep orphans once
- QUINN_CI_FAST smoke subset (12 files) on push/PR; full suite on dispatch
- ci-${{ ref }} concurrency separate from deploy-${{ ref }}; cancel stale runs
- Cache Playwright browsers on quinn.www deploy workflow
2026-06-24 03:59:48 -04:00
Natalie
8c57818276 fix(quinn-my/mcp): verified-profile update verb PATCH→PUT to match API route
The API exposes PUT /admin/verified-profiles/:id (partial body accepted);
the MCP tool was issuing PATCH and 404ing on every reorder/update.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:52:13 -04:00
Natalie
889b11039d feat(prospect-qualification): OF-redirect decline matcher + rotation pool
Build-order steps 1-2 of docs/prospector-of-redirect-spec.md (pure functions,
no I/O, no send). classifyDecline() separates curious rate-askers (handled by
isBudgetBalker — quote, no redirect) from soft can't-afford vs lowball/haggle;
lowball wins ties (counter-number → disengage). rateAlreadyQuoted() is gate #1
(post-quote only, outbound-scan). Rotation pools are Quinn's verbatim approved
copy with a [link] token filled at staging time (never hardcoded). 31 tests.

LIVE sending, engine_drafts staging, of_redirected_at migration, and the
local-model/worker rails remain Quinn-gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:20:48 -04:00
Natalie
91f6dc2adb fix(promo-banner): make entire banner clickable, not just the pill
The whole bar is now a single anchor (CardLink) wrapping artwork, copy and
the CTA pill, so clicking anywhere navigates — eliminating the ~80px dead
zone that was the real cause of near-zero promo CTR. The pill becomes an
aria-hidden visual affordance; dismiss (X) stays outside the link so it
never triggers navigation. One folded aria-label + focus-visible outline
keep it accessible; hover lift now driven from the parent anchor. Thumb
bumped 44->48px so the image creative reads as the hero.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:31:39 -04:00
Natalie
6c80e6cb12 feat(send-rate-limit): thread autoQueue through quinn.api + MCP client
Complete the autoQueue toggle wiring: the api facade getSendRateLimit/
setSendRateLimit and /m/messages/send-rate-limit GET/PUT now carry autoQueue,
and the MCP client's setSendRateLimit passes it. Pairs with the MCP
set_send_rate_limit tool's autoQueue input (added separately) and the
mac-sync send_rate_config.auto_queue column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:38:51 -04:00
Natalie
c572c1c18d fix(ci): repair typecheck failures blocking verify on main
quinn-messenger MCP set_send_rate_limit passes autoQueue (3rd arg).
Admin site-settings drops unsupported EditorField.help property.
my backend-api removes unused QUINN_API_URL/TOKEN module constants.
2026-06-23 21:37:14 -04:00
Natalie
26fa2733ec feat(quinn-messenger): MCP control for outbound send-rate cap
Wire the mac-sync send-rate cap (max N sends per window) through to the
quinn-messenger MCP so it's adjustable at runtime, routed through quinn.api
per the canonical messenger→quinn.api→mac-sync path.

- api: shared/mac-sync/send.ts gains getSendRateLimit/setSendRateLimit
  (direct call to mac-sync /admin/send-rate-limit, deadline-guarded);
  /m/messages/send-rate-limit GET/PUT surfaces them.
- mcp: get_send_rate_limit / set_send_rate_limit tools call those routes.

Backing cap + storage live in the mac-sync server (default 10/300s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:56:40 -04:00
Natalie
7e6c27c100 feat(quinn-my): add show_on_site toggle to platforms (SSOT from credentials) with admin toggle to list on public site
- Added migration for show_on_site boolean on platforms_escort and platforms_content.
- Updated platforms-data handler to support PATCH for the field, serialize it, and on toggle=true auto-create default verified_profile entry (using site logo) via quinn.api admin surface. This makes platforms the SSOT for the list, and the toggle the choice to 'list on site' in verified on / banners.
- Updated my frontend: Platform type, PlatformsPage (dots + table checkbox for 'Site'), PlatformModal (dots).
- The toggle in quinn-my PlatformsPage (the admin for the credentials/platforms list) now controls populating the public verified on section.
- Verified profiles remain for rich customization (custom img/desc/href per platform); defaults use logo per prior requirement.
- Env for quinn api added to my server for the sync call.
- Matches the platforms list from quinn-my credentials (escort+content) as source of truth for which to manage/toggle.
2026-06-23 13:20:24 -04:00
Natalie
c4d4ec5ecb docs(my-socials): scaffold socials feature (plan + composer UX docs)
Add the my-socials feature skeleton (backend-api/frontend-public/mcp-server/
shared dirs) with CLAUDE.md, README, PLAN.md, the general promo-graphic-composer
UX spec + HTML mockup, and the ts4rent avatar-overlay spec.
2026-06-23 13:19:58 -04:00
Natalie
efd3b2fcbf feat(quinn.my): platform graphic composer page
Add PlatformCreativePage (new-file / dimensions / asset placement / crop /
rotate), lazy-routed at /platforms/:name/creative on desktop and mobile, and
linked from PlatformsPage and the platform ad-copy editor.
2026-06-23 13:19:52 -04:00
Natalie
5769d5c874 feat(messenger): canonical domain messenger.transquinnftw.com
Align the messaging surface with other quinn.* subdomains (my, admin, data).
m.transquinnftw.com and m.quinn.apricot.lan now 301 to messenger.*.
App switcher id/subdomain updated to messenger; shared SAN cert expanded
on deploy.
2026-06-23 07:55:54 -04:00