atlilith/talent-scout-port-findings.md
Natalie 4365c8a47f docs(@projects/@atlilith): update infrastructure documentation for lan-to-lan migration
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 03:15:02 -07:00

58 KiB
Raw Permalink Blame History

talent-scout v1 → v4 port findings (apricot read, 2026-05-18)

Purpose: capture port-plan facts read directly from v1 talent-scout on apricot, for integration into the @cocottetech Mac corpus once synced. This file lives at @atlilith/ (read-only tombstone tree) as the staging point.

Source paths (apricot):

  • /var/home/lilith/Code/@projects/@lilith/lilith-platform/operations/talent-scout/ — main scraper code
  • /var/home/lilith/Code/@projects/@lilith/lilith-platform/codebase/tools/talent-scout/ — docker subset (only docker/ dir)
  • /var/home/lilith/Code/@projects/@lilith/lilith-platform/.quinn/platforms/ — Quinn's per-platform operator playbooks (12 directories enumerated)

Destination corpus on Mac (currently desynced from apricot):

  • _engineering-surface-adapter-container.md — needs substantial revision; was written speculatively before this find.
  • New: _engineering-talent-scout-port.md — the concrete port plan (this doc's eventual form on the Mac corpus).

§0 — Single Source of Truth (SSOT) discipline (declared 2026-05-18)

The corpus has grown to ~50 docs across briefs / screens / flows / engineering annexes. As mass increases, drift becomes inevitable without explicit canonical-source rules. These rules are corpus-wide and apply going forward.

SSOT.1 — One canonical home per fact

Every fact lives in exactly one authoritative location. Other docs reference that location; they do not restate the fact. When the fact changes, only the canonical home updates.

Fact category Canonical home
Per-surface platform mechanics (tiers, cadence, photo counts, fields, API shapes) surface-<id>.brief.md §canonical-facts
Voice register + linguistic conventions 00-system-voice.md
Conversational-UX principle + screen-role model 00-system-conversational-ux.md
Visual tokens / typography / motion 00-system-visual-system.md
Cross-surface fanout pattern cross-surface-fanout.brief.md
Adapter / container architecture _engineering-surface-adapter-container.md
Credentials vault + auth modes _engineering-credentials-vault.md
v1 → v4 port mappings _engineering-talent-scout-port.md (this doc, post-sync)
Per-specialist contract (Does/Auto/Proposes/Never/Correction-lens) specialist-<name>.contract.md
Surface roster (which surfaces exist + categories) O-surfaces-roster.brief.md
Open questions across the corpus OPEN-DECISIONS.md
Brand-family architecture (Cocotte / Demimonde / Sansonnet) DESIGN.md §7.5 + brand-family memory

Concretely — for Tryst's tier cadence (the 2h/3h-per-tier fact that surfaced from research.md):

  • Canonical home: surface-tryst.brief.md §canonical-facts (table).
  • Consumers: policy-card.screen.md, specialist-bookings-tryst.contract.md, brief H §H1, _engineering-talent-scout-port.md — all reference the table, none restate the numbers.
  • When Tryst changes the cadence (new tier launched, platform tweak), update the table once; consumers reflect on next read.

SSOT.2 — Conflict resolution rule

When two sources disagree about a fact, resolve in this order:

  1. Quinn's direct statement to this collective (in chat) — highest authority.
  2. .quinn/platforms/<surface>/ playbook docs (research.md, account.md, advertisement-text.md) — canonical for platform mechanics; Quinn's lived-in knowledge.
  3. Cross-corpus brief inferences — lowest authority; speculative until validated against (1) or (2).

When (1) and (2) disagree (as happened for Tryst-reviews), (1) wins. Annotate (2) with a correction note pointing at (1) so the source-of-truth is preserved + the discrepancy is traceable.

SSOT.3 — DRY enforcement at write time

Before writing a fact into any doc, check whether it has a canonical home. If yes, write a reference, not a restate. If no, this doc IS the canonical home — mark it as such in the doc's header.

Pattern: Per [surface-tryst.brief.md §canonical-facts](./surface-tryst.brief.md#canonical-facts), Tryst's bump cadence is tier-dependent. — never repeats the 2h/3h numbers.

SSOT.4 — Cleanup on each corpus pass

When this collective touches a doc, if it finds duplicated facts that violate SSOT.1, the touch includes refactoring those into references (cost is bounded; one duplicate at a time).

SSOT.5 — Resolved contradictions from this session

Contradiction Resolution Authority
Tryst has reviews (research.md line 70) vs Tryst doesn't have reviews (Quinn direct) No reviews on Tryst. Annotated in research.md with correction; tryst-reviews.screen.md stays deprecated; reviews-list.screen.md remains the canonical N7 aggregator surface. SSOT.2 rule 1 > rule 2
Tryst cadence is every 4h (corpus speculation) vs every 2h or 3h tier-dependent (research.md) 2h on Premium/Premium+; 3h on Basic/Standard. Canonical home: surface-tryst.brief.md §canonical-facts (post-sync). SSOT.2 rule 2 > rule 3
Photo slot count 12 (tryst-photo-manager.screen.md) vs 16/24/32/40 per tier (research.md) 16/24/32/40 per tier. Canonical home: surface-tryst.brief.md §canonical-facts. SSOT.2 rule 2 > rule 3
Home cities monthly-lock with fee (initial surface-tryst.brief.md §7) vs N cities + Y cadence per tier (Quinn direct + research.md) N cities tier-gated (1/1/1/3); Y cadence per tier (TBD). Already corrected in surface-tryst.brief.md §7. SSOT.2 rule 1 + 2 > rule 3
Adapter home at @ai/@skills/platform-*/actions/ (corpus speculation) vs cocotte-internal (Quinn direct: "if it's only used by cocotte it's not a peer") Cocotte-internal: @cocottetech/@platform/codebase/@{packages,features}/.... SSOT.2 rule 1 > rule 3

SSOT.6 — Open contradictions (none currently)

All known contradictions resolved per SSOT.5 above. Future contradictions go here with their resolution.


§0.1 — Tryst canonical-facts table (STAGED — migrates to surface-tryst.brief.md §canonical-facts on sync)

This is the single source of truth for Tryst platform mechanics. Every other doc that mentions a Tryst tier number, cadence, slot count, etc. references this table, never restates.

Source: .quinn/platforms/tryst/research.md (Quinn's research notes, 2026-03-29) + account.md (Quinn's current state).

Tier table

Tier TLC/mo USD/mo Search Priority Photo slots Tour slots Home bases Bump cadence Native analytics
Basic 35 ~$40.71 Normal 16 1 1 Every 3h
Standard 75 ~$87.24 Boosted 24 5 1 Every 3h Rolling 30-day
Premium 100 ~$116.32 Premium 32 10 1 Every 2h Rolling 30-day
Premium+ 150 ~$174.48 Premium+ 40 10 3 (simultaneous) Every 2h Rolling 30-day

Tryst-platform invariants (not tier-dependent)

  • Reviews: NONE. Tryst has no native reviews system. (Per SSOT.5 — Quinn direct.)
  • Verification: Sumsub-style ID + selfie + photo authentication. Re-up delays can exceed 14 business days.
  • Payment: Visa/Mastercard processor discrimination — crypto-only for some clients.
  • Owner: Assembly Four (Australia, 2018) — sex-worker-owned + operated.
  • Audience: budget-to-mid-tier, touring-friendly. ~13M monthly visits.
  • Fraud baseline: 70-90% of profiles are scams/fakes/abandoned (per Ho-Work Research Group).
  • Verified-badge differential: Quinn's verified status is a major signal because of the fraud baseline.
  • Home-base change cadence Y: TBD — Quinn to confirm exact days.
  • Location pulse strategy: update home base 10-14 days before tour arrival.
  • Capabilities: explicit service listing allowed (no euphemism); full service menu expected.
  • Native analytics shape: Standard+ tiers expose rolling 30-day metrics (profile views, search rank, filter rank, click-through; exact fields TBD on first connect). Basic tier sees no native metrics. Cocotte ingests + feeds [analytics-dashboard.screen.md] T3 panel; Basic-tier Tryst accounts show "Upgrade to Standard for Tryst analytics" instead.

Quinn's current state (snapshot 2026-03-29)

  • Tier: Basic, upgrading.
  • TLC balance: purchased 150 TLC one-time on 2026-03-28 (~$170; covers ~1 month at Premium+).
  • Home location: San Francisco, CA.
  • Photo status: pending upload.
  • Handle: @TransQuinnFTW.
  • Phone: (424)466-3669.

Consumers of this table (auto-update when canonical-facts changes)

  • surface-tryst.brief.md §4 (visibility heartbeat) → references this for cadence
  • policy-card.screen.md → references for cadence picker
  • tryst-photo-manager.screen.md → references for slot count
  • tryst-home-cities.screen.md → references for N home bases
  • specialist-bookings-tryst.contract.md → references for Auto rules
  • brief H §H1 → references for cadence pattern
  • tryst-connect.flow.md → references for tier-mentioning copy
  • _engineering-talent-scout-port.md (this doc, post-sync) → references for adapter behavior tuning

What v1 talent-scout actually is

A production-grade provider-scraping engine built to discover escort-listing-site providers and invite them to lilith-platform. Different use case from v4's needs (v4 platform-tryst skill operates on Quinn's own account), but ~6070% of the adapter machinery is directly applicable.

Production status (verified)

  • Active recent commits — output/captcha-screenshots/captcha-tryst-*.png timestamps recent.
  • 18 source modules under src/ ([operations/talent-scout/docs/architecture.md]).
  • Full Express API on :3400 with 13+ controllers.
  • React control-panel UI (src/ui/).
  • BullMQ job queues + session/audit-trail entities.
  • TypeORM + dedicated talent-scout PostgreSQL DB.

Component-by-component port verdicts

Adapter base + Tryst adapter — PORT (most reusable) — verified read 2026-05-18 (full files)

File Purpose v4 destination
src/adapters/base-adapter.ts BaseAdapter abstract class — circuit breaker per platform, selector loading from JSON, delegated content extraction, page-nav helpers @ai/@skills/_shared/base-adapter.ts
src/adapters/tryst-adapter.ts TrystAdapter extends BaseAdapter — Tryst URL builders, ALTCHA visual-captcha solving, Cloudflare Turnstile handling, terms-toast dismissal, Stimulus.js controllers @ai/@skills/platform-tryst/adapter.ts
src/adapters/eros-adapter.ts Eros equivalent @ai/@skills/platform-eros/adapter.ts
src/adapters/transescorts-adapter.ts TSEscorts equivalent @ai/@skills/platform-tsescorts/adapter.ts
src/adapters/content-extraction.ts Shared extractors: rates, menu, touring, verification, photos, socials, similar-profiles, contact-reveal, tagline, profile-details, policies, bio-extract, bio-socials-merge @ai/@skills/_shared/content-extraction.ts — keep all 13 extractors
src/adapters/page-navigation.ts Helpers: hasNextPage, handleAntiBot, normalizePhone, screenshotOnError @ai/@skills/_shared/page-navigation.ts
src/config/selectors.ts + selectors/*.json Per-platform selector schemas — JSON-defined CSS/XPath selectors with Zod validation @ai/@skills/_shared/selectors.ts + per-platform JSON

Key adapter pattern: each per-platform adapter extends BaseAdapter and implements 3 abstract methods (buildListingUrl, buildProfileUrl, handleAntiBot). Everything else is shared. The same shape works for operate-on (login + bump + edit) — just add login(), bump(), updateProfile(), fetchInbox() methods.

v4 reframing: instead of crawl() + scrapeProfile(), the v4 adapter exposes login() + bump() + updateProfile() + fetchInbox() + replyDM(). The infrastructure (selectors, anti-bot, page-nav, screenshots) is shared.

BaseAdapter architecture (verified read 2026-05-18 — full 360L)

Class shape: abstract class BaseAdapter implements PlatformAdapter. Per-platform circuit breaker from @lilith/circuit-breaker (rename → @cocotte/circuit-breaker). Selector schema loaded from selectors/<platformId>.json via getSelectorSchema(platformId) (port the registry pattern).

Abstract methods adapters must implement: buildListingUrlFromSlug(slug, page), buildListingUrl(city, page), buildProfileUrl(slug). For v4 CocotteAI: the operate-on flow (Quinn manages her own profiles) replaces these with getOwnProfileUrl(), getEditUrl(), getBumpUrl(), getSettingsUrl() — same selector-schema-driven pattern, different verbs.

Helper-module split (lift verbatim):

  • content-extraction.ts — pure extractors: extractRates / extractMenu / extractTouringStatus / extractVerification / extractPhotos / extractSocials / extractSimilarProfiles / revealContact / extractTagline / extractProfileDetails / extractPolicies / extractFromBio / mergeBioSocials.
  • page-navigation.tshasNextPage / handleAntiBot / normalizePhone / screenshotOnError.

scrapeProfile parallelism pattern: 10 extractors fired via Promise.all([...]) after waitForSelector(name). Port directly — same pattern applies to v4 "read Quinn's current profile state" before computing a diff for the operate-on action.

Bio-text supplemental extraction: extractFromBio() parses phone/rates/socials out of bio text — DOM extraction takes precedence, bio extraction SUPPLEMENTS only when DOM is empty. Critical invariant for v4: applies symmetrically to the operate-on flow when Quinn's own draft bio mentions a number that didn't make it into the phone field — surface as a suggestion via the strategist specialist.

Telemetry hook (onSolveAttempt?: (data) => void): optional callback set by pipeline worker. Port verbatim — wire to captcha_solve_attempts insert in v4 pipeline worker.

Captcha solver — PORT (major win — already-trained ML pipeline)

The captcha-solver is NOT a single 3.8GB model as the archive map said. It's a Python ML pipeline:

Component Status
packages/captcha-solver/ml-service/ Python service
PARSeq architecture trained (train_parseq_by_style.py)
CRNN architecture trained (train_crnn.py + finetune_crnn.py)
SVTRv2 architecture trained (train_svtrv2_by_style.py)
Style classifier trained (train_style_classifier.py) — multi-style captcha format detection + routing
Temperature calibration calibrated (calibrate_temperature.py)
Error analysis tooling (error_analysis.py)
Integration tests tests/test_integration_classifier.py
HTTP service runs at 127.0.0.1:3099; max 5 attempts per captcha field
Service-side TypeScript types src/services/captcha-solver/types.ts defines CaptchaSolveResponse
Tryst-side adapter integration tryst-adapter.ts calls solver via HTTP with screenshot blob

Port plan: lift the Python ml-service intact to @applications/@ml/captcha-solver/ (its CLAUDE.md-canonical home). TypeScript client wrapper goes to @ai/@skills/_shared/captcha-solver-client.ts. Service stays at 127.0.0.1:3099 on apricot or moves to the ML host per @ml/ deployment policy.

Captcha-solver TypeScript types (read 2026-05-18 from src/services/captcha-solver/types.ts):

type CaptchaSolverStatus = 'active' | 'inactive' | 'starting' | 'error' | 'paused';

// 9 solve strategies — mirrors Python SolveMethod enum
type SolveStrategy =
  | 'trocr'           // OCR — Microsoft TrOCR
  | 'clip'            // CLIP-based matching
  | 'crnn'            // CRNN OCR
  | 'parseq'          // PARSeq
  | 'parseq_style'    // PARSeq + per-style routing
  | 'svtrv2'          // SVTRv2
  | 'svtrv2_style'    // SVTRv2 + per-style routing
  | 'style_expert'    // ensemble routed by style classifier
  | 'auto';           // pick best per detected style + confidence

interface CaptchaSolveResponse {
  text: string;
  confidence: number;
  strategy_used: string;
  model_used: string;
  detected_style: string | null;
  style_confidence: number | null;
  timing: TimingInfo;             // per-arch breakdown
  path_used: string | null;
}

interface TimingInfo {
  preprocess_ms: number;
  inference_ms: number;
  total_ms: number;
  crnn_ms?: number | null;
  trocr_ms?: number | null;
  clip_ms?: number | null;
  parseq_ms?: number | null;
  classify_style_ms?: number | null;
}

CaptchaSolverManager (read from src/services/captcha-solver/manager.ts):

  • Spawns Python uvicorn as a managed child process; tracks PID + monitors health via HTTP.
  • Dynamic scale-up: spawns extra instances up to maxInstances when all are busy.
  • Cooldown-before-scale-down: keeps extras warm for configurable period before SIGTERM'ing back to baseline+1.
  • tryAdoptExisting — attaches to already-running instance instead of spawning (process-adoption — no orphans).
  • Pool lifecycle delegated to generic ServicePoolManager (lives at src/services/pool-manager.ts).
  • Default port :3099.

Key port insight: the ServicePoolManager abstraction is also reusable for Tor pool management — same pattern (spawn N instances of a managed process, monitor health, scale dynamically, adopt existing). Port both Tor + captcha-solver as ServicePoolManager consumers.

Tor manager location

NOT in talent-scout/src/. The crawl-config.yaml proxy.managerUrl: http://localhost:7710 points to an external Tor management service — likely a Docker container (see talent-scout/docker-compose.yml) or a separately-deployed @applications package. Needs an additional read to fully locate; deferred to next read pass.

Tor proxy pool — PORT (reuses with config tweaks)

crawl-config.yaml documents the existing setup:

proxy:
  enabled: true
  type: tor-managed
  instances: 10
  maxInstances: 10
  cooldownMs: 600000          # 10-min cooldown per circuit
  startPort: 28118
  host: 127.0.0.1
  managerUrl: http://localhost:7710
circuitBreaker:
  failureThreshold: 5
  successThreshold: 3
  timeout: 60000

Port plan: lift the Tor manager service config + the circuit-breaker library (@lilith/circuit-breaker — already an internal package). For v4, may want fewer circuits (10 was for parallel crawling of N city-pages; Quinn-operate-on is mostly sequential per-surface).

Expert pool (LLM extraction experts) — PARTIAL PORT — verified read 2026-05-18

src/experts/expert-pool.ts runs 5 specialized LLM extractors (MenuExpert / RateExpert / BioExpert / ContactExpert / PolicyExpert) against scraped third-party profile HTML to normalize raw data into typed shapes. Execution adapts to pool state: parallel via Promise.all when LLM pool exists (llmClient.hasPool === true), sequential otherwise.

Port verdict: Reuse only for CocotteAI's competitor-research / prospector path (scanning Tryst listings for competitor pricing, regional trends, etc.). For the operate-on flow (Quinn manages her own profiles) the LLM-extraction experts are mostly N/A — Quinn's draft is already structured, no normalization needed. Drop into @cocottetech/@platform/codebase/@features/prospector/experts/ (or whichever feature owns competitor scanning); skip for bookings-tryst adapter.

LLM-pool reuse: the TalentScoutLLMClient.hasPool pattern + acquire/release semantics already align with ServicePoolManager — same shared infrastructure powers captcha-solver pool, Tor circuit pool, LLM expert pool. Three pools, one pattern. Confirmed unifies cleanly.

Helper modules — barrel layout verified read 2026-05-18

  • adapters/content-extraction.ts (27L) — pure barrel: re-exports from extractors/dom/{rates-menu, touring-verification, media-social, contact-reveal, profile-details}-extractor.ts + pure helpers from extractors/{bio-extractor, date-parser, rate-parser, social-classifier}.ts. Port the directory structure verbatim; the SRP-split is already correct.
  • adapters/page-navigation.ts (93L) — four standalone functions: hasNextPage / handleAntiBot / normalizePhone / screenshotOnError. normalizePhone strips non-digits AND strips leading 1 country-code for US (11-digit normalization). Default handleAntiBot handles generic Cloudflare challenge only; Tryst's adapter overrides with ALTCHA/Turnstile/terms-toast.

Cross-package dependencies — verified read 2026-05-18

@lilith/circuit-breaker (/var/home/lilith/Code/@packages/@ts/@infra/circuit-breaker/):

  • v0.1.1, zero runtime deps, ESM + CJS dual output via tsup, published to forge.black.lan/api/packages/lilith/npm/.
  • Port verdict: rename → @cocotte/circuit-breaker, republish to same Forgejo registry under @cocotte scope. Move source to @cocottetech/@platform/codebase/@packages/circuit-breaker/.

@lilith/tor-types (/var/home/lilith/Code/@applications/@tor/packages/tor-types/):

  • v0.1.0, only devDep is @lilith/configs, ESM + CJS dual output, published to same registry.
  • Contains TorNodeState, lease types, pool status types, ProxyProvider abstraction.
  • Port verdict: rename → @cocotte/tor-types. The package itself stays in the @tor application tree (Tor is a peer service per CLAUDE.md, not vendored into cocottetech), but consumers in the v4 platform reference the renamed scope.

Note on @tor application boundary: per @atlilith/CLAUDE.md peer-service rules + the brand-family architecture, the tor-manager service stays as a peer (@applications/@tor/) consumed over HTTP by the v4 platform. Only the types package gets renamed/republished — the manager service itself remains independent.

Detection module — PORT (key safety primitives) — verified layout read 2026-05-18

src/detection/index.ts re-exports four submodules:

  • honeypot/ — 5 detectors: data-poisoning-detector, fingerprint-auditor, hidden-link-detector, response-anomaly-detector, tarpit-detector (+ shared types.ts, all with .test.ts siblings)
  • blocklist/blocklist.ts + tests (operator-managed deny lists for known traps)
  • deduplication/ — identity dedup primitives (likely photo-hashing + cross-platform identity resolution per prospect-resolver contract)
  • content-integrity/ — verification primitives (signature/checksum-based detection of poisoned content)

Port verdict: all four submodules port to @cocottetech/@platform/codebase/@packages/detection/ (shared by both bookings-* surface adapters AND prospector competitor-research feature). The deduplication module's photo-hasher is the direct dependency for the prospect-resolver specialist contract (cross-surface identity dedup per §0.2). Lift verbatim.

Sub-module Verdict Notes
blocklist/blocklist.ts PORT SHA-256-hashed identifier storage (never plaintext); aligns directly with brief N §N7a privacy mechanics. Reuses Quinn's existing K1 block-list semantics.
deduplication/dedup-engine.ts + photo-hasher.ts PORT for v4 prospect-resolver (P4) Multi-signal identity matching across surfaces. Already does photo-hashing + cross-platform username matching.
content-integrity/ PORT-pending-evaluation Cross-channel hash verification — useful when Cocotte ports photos across Tryst + OF (consent-tracking).
honeypot/ (6 detectors) REPURPOSE — these were defensive (don't get trapped while scraping). v4 use case is operating Quinn's own account, so traps less relevant. Useful for screening: are the screening sites legitimate?

Other modules

Module Verdict
analysis/classifier.ts + clustering.ts + vector-encoder.ts SKIP — provider-classification for outreach. v4 doesn't need this for platform-tryst (Quinn's own account); maybe partial reuse in prospect-resolver for client classification.
experts/ (LLM expert extraction) REPURPOSE for strategist specialist — talent-scout uses LLM experts to extract structured data from bios. v4 can use for analyzing prospects + drafting per-surface content.
outreach/ (18 modules) SKIP — campaign engine for inviting providers to lilith. Different use case.
pipeline/ (orchestration + steps) PARTIAL PORT — the pipeline abstraction is sound; the specific steps are scrape-specific. v4 needs operate-on-pipeline (login → action → audit) which is much simpler.
jobs/ (BullMQ queues + workers) PORT — same job-queue infra; different jobs.
metrics/ (Prometheus) PORT as-is.
api/ (Express on :3400, 13 controllers) SKIP — this is the v1 control panel for talent-scout itself. v4 has platform.api for the same role.
ui/ (React dashboard) SKIP — v4 has its own iOS-primary UI per the design corpus.
db/ (TypeORM, dedicated Postgres) PARTIAL — TypeORM patterns port; the dedicated DB is replaced by platform.db. Entities for sessions/captcha-stats may port.

Per-platform asset: Quinn's operator playbooks

apricot has .quinn/platforms/<dir>/ for 12 escort directories — exactly matching the v4 brief O N2 surface list:

  • adultlook, adultsearch, eros, eroticmonkeys, megapersonals, privatedelights, seeking, skipthegames, tryst, ts4rent, tsescorts
  • Plus COMPARISON.md at the parent level

Each per-platform dir contains operator notes (account.md, advertisement-text.md, imgs/, research.md). These are Quinn's lived-in playbooks for each surface — canonical input data for the per-surface briefs the design corpus is building.

Specifically the Tryst dir confirms the surface-tryst brief gets accurate details:

  • account.md (1155 bytes) — tier + handle + credentials notes
  • advertisement-text.md (2438 bytes) — Quinn's actual current Tryst about-me copy (gold for the strategist's voice-lean training)
  • research.md (3911 bytes) — Quinn's notes on Tryst-platform dynamics

Port plan: these become inputs to the v4 personas.facets[surface_id] config + the strategist's training data. Migration script: read each .quinn/platforms/<surface>/ → write per-surface persona facet row in platform.db.personas + per-surface initial ad-copy as the first content_assets row.


Tryst-specific anti-bot details (verified — full tryst-adapter.ts 775L read 2026-05-18)

Reveal flow (Stimulus unobfuscate-details controller) — 3-path extraction

For each contact field (email, mobile), the v1 adapter executes a triple-redundant extraction strategy because Tryst's reveal mechanism varies by browser/timing:

  1. Path A — API interception (primary): page.waitForResponse((r) => r.url().includes('/api/v1/profiles/') && r.request().method() === 'POST' && r.status() === 200) installed BEFORE the reveal click; parses JSON for data.mobile / data.email / data.phone. Bypasses DOM timing issues.
  2. Path B — DOM polling (fallback): waitForFunction checks [data-unobfuscate-details-target="output"] until (obfuscation) chars disappear. Then reads from injected mailto: / sms: / tel: link if present, else from span text.
  3. Path C — postMessage capture (final fallback): listens to window.message events pre-click; iframe sometimes postMessages the revealed value to parent.

Key trigger detail: showButton.dispatchEvent('click') is used INSTEAD of Playwright's .click() — the latter doesn't reliably fire Stimulus action handlers under stealth-mode. Port directly.

CAPTCHA dialog detection

Tryst's fancybox iframe doesn't reliably URL-match — content-verifies via frame.$('img') AND frame.$('input[type="text"], [role="textbox"]') both present, then falls back to 'dialog iframe, .fancybox__container iframe, [id^="fancybox__iframe"]' with same content check. Critical: after successful solve, the iframe navigates to a postMessage-bridge URL that still includes "challenge" — URL alone is insufficient.

CAPTCHA submit form quirks

  • Image: any <img> in iframe (SVG-distorted text)
  • Input: input#captcha_text, input[name="captcha_text"], input[type="text"]
  • Submit: <input type="submit"> not <button> — selector must include both: 'input[type="submit"], button[type="submit"], button'

Captcha-solver HTTP contract (port verbatim)

POST http://127.0.0.1:3099/solve · FormData: image (Blob) + strategy=style_expert · 30s timeout · returns CaptchaSolveResponse { text, confidence, strategy_used, model_used, detected_style, style_confidence, timing: { total_ms, preprocess_ms, inference_ms }, path_used }.

Telemetry callback contract (onSolveAttempt)

Both success and failure paths emit per-attempt telemetry → feeds captcha_solve_attempts table:

  • Success: success=true, failureReason=null
  • Failure: failureReason classified via body-text-match → 'server_error' (text "Something went wrong") | 'wrong_answer' ("did not match") | 'new_captcha' (default). Port the classification logic verbatim.

Original section: lines 60180 ALTCHA + Turnstile + terms-toast (kept below)

Read directly from the adapter code:

ALTCHA verification (Tryst's primary protection)

  • Two-step gate:
    1. Client-side PoW auto-solves (checkbox text changes "Verifying..." → "Verification required!")
    2. Visual text-captcha dialog appears (distorted text image + code input)
    3. After correct solve, form POSTs + page redirects to real content
  • Adapter has waitForAltchaPow(page) + solveAltchaChallenge(page) (called from handleAntiBot)

Cloudflare Turnstile

  • Selector: [data-sitekey], .cf-turnstile, iframe[src*="turnstile"]
  • Auto-solved by playwright-extra-stealth plugin (~5s wait)
  • Verify success: .profile-header, .escort-profile, [data-controller="profile"] visible within 30s

Cloudflare full challenge page

  • Selector: #challenge-running, #challenge-stage
  • Wait for detached (up to 60s)

Terms-toast dismissal

  • Selector: [data-controller="terms-toast"] → click button, [data-action*="accept"], .btn
  • 500ms settle wait

Stimulus.js controllers

  • Tryst uses Stimulus.js heavily. Adapter waits for specific [data-controller="..."] markers to confirm dynamic content loaded before extraction.

v4 implication: when porting for operate-on (login + bump + edit), the same anti-bot handling applies. Tryst will challenge Cocotte's container the same way. The exact flow ports directly.


File-mapping summary (v1 → v4)

Correction 2026-05-18 (per user feedback): the destinations originally written as @applications/@ai/@skills/platform-*/actions/ were wrong. Per @applications/@ai/CLAUDE.md (the authoritative source), @ai owns cognition (identity, memory, personality, nag, context, process, relationship) — NOT surface adapters. And per the user's rule "if it's only used by cocotte it's not a peer @application," surface adapters belong inside @cocottetech.

The corrected layout uses the existing per-feature structure already in the design corpus:

  • Shared adapter base + helpers@cocottetech/@platform/codebase/@packages/surface-adapter-base/ (new package; shared by all per-surface adapters)
  • Per-surface adapter → inside the matching feature, e.g. @cocottetech/@platform/codebase/@features/bookings-tryst/adapter/
  • Captcha-solver Python ML service@cocottetech/@platform/codebase/@features/_captcha-solver/ (cocotte-internal Python service; not @ml since only cocotte uses it)
  • Tor manager@cocottetech/@platform/codebase/@packages/tor-pool/
v1 path v4 path (corrected)
operations/talent-scout/src/adapters/base-adapter.ts @cocottetech/@platform/codebase/@packages/surface-adapter-base/src/base-adapter.ts
operations/talent-scout/src/adapters/tryst-adapter.ts @cocottetech/@platform/codebase/@features/bookings-tryst/adapter/tryst-adapter.ts
operations/talent-scout/src/adapters/eros-adapter.ts @cocottetech/@platform/codebase/@features/bookings-eros/adapter/eros-adapter.ts
operations/talent-scout/src/adapters/transescorts-adapter.ts @cocottetech/@platform/codebase/@features/bookings-tsescorts/adapter/tsescorts-adapter.ts
operations/talent-scout/src/adapters/content-extraction.ts @cocottetech/@platform/codebase/@packages/surface-adapter-base/src/content-extraction.ts
operations/talent-scout/src/adapters/page-navigation.ts @cocottetech/@platform/codebase/@packages/surface-adapter-base/src/page-navigation.ts
operations/talent-scout/src/config/selectors.ts + selectors/*.json @cocottetech/@platform/codebase/@packages/surface-adapter-base/src/selectors.ts + per-platform JSON co-located inside each feature
operations/talent-scout/packages/captcha-solver/ml-service/ @cocottetech/@platform/codebase/@features/_captcha-solver/ml-service/ (Python; cocotte-internal)
operations/talent-scout/src/services/captcha-solver/ @cocottetech/@platform/codebase/@packages/surface-adapter-base/src/captcha-solver-client.ts
operations/talent-scout/src/services/tor-manager.ts @cocottetech/@platform/codebase/@packages/tor-pool/src/index.ts
operations/talent-scout/src/detection/blocklist/ @cocottetech/@platform/codebase/@features/platform-api/src/blocklist/ (already brief K's home)
operations/talent-scout/src/detection/deduplication/ @cocottetech/@platform/codebase/@features/prospect-resolver/dedup/ (P4 specialist feature)
.quinn/platforms/<surface>/ × 12 seed data for personas.facets[surface] in platform.db

Layering rule (the corrected SRP)

  • @applications/@* — peer services consumed by multiple @projects (@ai, @mac-sync, @model-boss, @chobit, …).
  • @cocottetech/@platform/codebase/@packages/ — shared libraries used by multiple features within cocotte (BaseAdapter belongs here).
  • @cocottetech/@platform/codebase/@features/<feature-name>/ — per-feature code. Per-surface adapters live inside the matching feature (bookings-tryst, bookings-eros, content-onlyfans, etc.).
  • @cocottetech/@platform/codebase/@features/_captcha-solver/ — leading underscore marks it as an internal-service feature (not a user-facing specialist). Same pattern as _engineering- and _deprecated- doc prefixes.

Corollary: docs needing updates when Mac corpus syncs

These design-corpus docs reference the wrong destination paths and need patching:

  • _engineering-surface-adapter-container.md — multiple references to @ai/@skills/platform-*/actions/.
  • _engineering-credentials-vault.md — "Adapter consumer pattern" section.
  • surface-tryst.brief.md — §2 references the wrong adapter home.
  • surface-screening.brief.md — connect references.
  • specialist-bookings-tryst.contract.md + every other specialist-bookings-*.contract.md + specialist-content-*.contract.md.
  • tryst-connect.flow.md + tryst-connect.screen.md.
  • README dependency map (if it shows the adapter relationships).

The fix is mechanical: search/replace @ai/@skills/platform-{name}/actions/@cocottetech/@platform/codebase/@features/bookings-{name}/adapter/ (or content-{name}/adapter/ per axis).


Corpus-update implications

When the @cocottetech Mac corpus syncs to apricot:

  1. _engineering-surface-adapter-container.md needs substantial revision — the speculative architecture is mostly already built. Many sections (Layer 3 fingerprint, Layer 5 captcha 3-tier, Layer 6 adapter API contract) should reference the existing implementations rather than design from scratch.

  2. New _engineering-talent-scout-port.md — promotes this findings doc to a proper engineering brief with file-by-file port verdicts.

  3. surface-tryst.brief.md §2 (Auth & connect) — update the captcha 3-tier section to reflect Tier 2 = "port the PARSeq+CRNN+SVTRv2 ensemble" rather than "build new"; Tier 1 = playwright-extra-stealth (already in talent-scout).

  4. _engineering-credentials-vault.md — note that talent-scout already uses @lilith/circuit-breaker package; v4 credentials adapter can reuse.

  5. surface-tryst.brief.md §3 Profile data model — Quinn's actual .quinn/platforms/tryst/account.md + advertisement-text.md should be ingested as concrete confirmation of the schema fields. Worth reading those before finalizing §3.

  6. O-surfaces-roster.brief.md — confirms the 12 escort directories Quinn operates on; matches the .quinn/platforms/ list exactly.

  7. brand-family memory — should be confirmed against .quinn/ content (some of Quinn's existing per-platform notes may have brand details).


Immediate next actions (path 3 — engineering)

Recommended sequence once design corpus is reconciled:

  1. Read deeper into talent-scout's base-adapter.ts + tryst-adapter.ts in full (line ranges still unread).
  2. Read .quinn/platforms/tryst/{account,advertisement-text,research}.md — Quinn's actual current Tryst state.
  3. Scaffold ~/Code/@applications/@ai/@skills/_shared/ + platform-tryst/ directories (CLAUDE.md-canonical location).
  4. Lift base-adapter.ts + helpers — minimal rewrite (just method signatures for operate-on).
  5. Lift captcha-solver ml-service to @ml/captcha-solver/.
  6. Implement platform-tryst/actions/login.ts as the first operate-on action — exercises BaseAdapter + captcha-solver + Tor pool end-to-end.
  7. Implement bump.ts — the H1-canonical action.
  8. Wire to platform.api policy table so the H1 policy-card UI has a live backend.

This sequence prioritizes the session-and-bump-loop as the smallest shippable Tryst slice, consistent with the design corpus' H1 spec.


Read backlog (apricot, when resumed)

  • Full base-adapter.ts (only first 100 lines read).
  • Full tryst-adapter.ts (lines 0180 read; ~600 lines total likely).
  • src/services/captcha-solver/types.ts + service implementationREAD 2026-05-18 (see Captcha-solver section above).
  • Tor manager source — NOT in talent-scout src/; external service at :7710 (likely docker-compose). Needs another read.
  • src/db/entities/PARTIAL READ 2026-05-18 (session.entity.ts + captcha-solve-attempt.entity.ts; see §Entities below).
  • .quinn/platforms/tryst/account.md + advertisement-text.md + research.mdREAD 2026-05-18 (see §Quinn's Tryst facts below).
  • packages/captcha-solver/ml-service/README.md + TRAINING_LOG.md + EXPERIMENTS.md.
  • crawl-config.example.yaml (full) — anti-bot tuning details.
  • pool-manager.ts — actual path not at src/services/; locate.

Quinn's Tryst — design corpus implications (per §0.1 canonical-facts)

Per SSOT.1, the canonical Tryst tier facts live in §0.1 above. This section does not restate them; it documents the implications for the design corpus.

Mac-corpus docs needing correction (search/replace, post-sync)

File Current state After SSOT.1 correction
surface-tryst.brief.md §4 (visibility heartbeat) Claims "every 4h" or parametric Y Reference §canonical-facts (cadence is tier-dependent: 2h Premium+, 3h Basic/Standard)
surface-tryst.brief.md §7 (home cities) Parametric N + Y Reference §canonical-facts (N = 1/1/1/3 per tier; Y TBD)
surface-tryst.brief.md §3 (profile data model) Photos: "hero + N gallery (Tryst-specific cap)" Reference §canonical-facts (16/24/32/40 per tier)
surface-tryst.brief.md §8 (reviews) "N/A — Tryst has no native reviews" Stays correct (Quinn-direct, per SSOT.5)
policy-card.screen.md Cadence picker: 2h / 3h / 4h / 6h / 8h Tier-aware: locked to Tryst's allowed cadences per §canonical-facts
tryst-photo-manager.screen.md "12 gallery slots + 1 hero" Tier-aware: 16/24/32/40 total per §canonical-facts
tryst-home-cities.screen.md Parametric N Reference §canonical-facts (N per tier)
specialist-bookings-tryst.contract.md "Availability bumps on policy cadence" Reference §canonical-facts for the actual cadences
brief H §H1 (recurring chores) "every 4h" examples Reference §canonical-facts; H1's pattern is generic; surface-tryst owns the Tryst-specific numbers
tryst-connect.flow.md Generic "tier" mentions Tier-specific copy ("Premium+ — 3 home cities, every 2h cadence") referencing §canonical-facts

Strategist priors (from research.md, not tier-specific)

These belong in the strategist context provider, not in the canonical-facts table (they're strategic/narrative, not platform-mechanic):

  • Sex worker-owned platform (Assembly Four, 2018) — explicit service advertising allowed; full service menu expected, no euphemism.
  • 70-90% profile fraud rate — verified status is Quinn's primary differentiator; strategist should lean on "Verified ✓" mentions in drafted copy.
  • Payment-processor discrimination — Quinn's payment flow should accommodate crypto-only clients.
  • Verification re-up delays >14 business days — Cocotte must maintain proactively; tryst-verification.screen.md surfaces 30/14/7-day warnings.
  • Location pulse strategy: update home base 1014 days before tour arrival.

These get a home in: surface-tryst.brief.md §1 Surface identity (the strategist-prior text), with the platform-mechanic facts referencing §canonical-facts.

Contradictions resolved (was open; now closed)

Per SSOT.5 above:

  • Tryst has reviews vs doesn'tNo reviews; annotated in research.md.
  • "every 4h" cadenceTier-dependent per §canonical-facts.
  • Photo slot countPer tier per §canonical-facts.

Pool / Captcha-solver service-management — TS-side findings (read 2026-05-18)

From src/services/captcha-solver/{types.ts, manager.ts, index.ts}:

  • 9 solve strategies in SolveStrategy enum: trocr / clip / crnn / parseq / parseq_style / svtrv2 / svtrv2_style / style_expert / auto. Mirrors Python SolveMethod enum on the FastAPI side.
  • CaptchaSolverManager: spawns Python uvicorn as managed child process; tracks PID; monitors health via HTTP at :3099; dynamic scale-up (up to maxInstances); cooldown-before-scale-down; tryAdoptExisting for no-orphan startup.
  • Generic ServicePoolManager abstraction handles pool lifecycle — also reusable for Tor pool (same pattern: spawn N managed processes, monitor health, scale dynamically).

Pool infrastructure — verified read 2026-05-18

Read sources: src/services/pool-manager/service-pool-manager.ts (403L), src/browser/circuit-pool.ts (434L), src/services/tor-monitor.ts (297L), src/types/pool.ts.

Three distinct pools, two shared patterns:

  1. ServicePoolManager (process pool) — manages Python uvicorn child processes (captcha-solver, LLM). Generic via SpawnConfig { pythonPath, spawnArgs(port), serviceDir, logPrefix }. Defaults: maxInstances=8, cooldownMs=600_000 (10min), SCALE_DOWN_TICK_INTERVAL_MS=60_000 (1min). N+1 baseline + dynamic extras. tryAdoptExisting for orphan recovery across Node restarts. SIGTERM with STOP_TIMEOUT_MS then SIGKILL.

  2. CircuitPool (Playwright context pool) — manages browser contexts with per-circuit Tor-proxy assignment. Same N+1/cooldown/scale-down semantics as ServicePoolManager, separate implementation (contexts ≠ processes). Uses playwright-extra + puppeteer-extra-plugin-stealth. Per-context randomization: viewport (4 options), userAgent (4 options), locale=en-US, timezone=America/Los_Angeles. Route-blocks image/font/media/stylesheet. Note: Quinn requires images for vision-based detection in some adapter modes — blocking is per-adapter-policy, not global.

  3. TorMonitor — two-mode health checker:

    • Legacy TCP mode (proxy.type='tor'): TCP-ping httpProxyPort (default 3128); merges CircuitPool states for display.
    • Manager mode (proxy.type='tor-managed'): queries external tor-manager REST API /api/v1/pool/status for multi-node state (AVAILABLE / LEASED / COOLING_DOWN / SPAWNING / STARTING / DESTROYING / UNHEALTHY), per-node exit IPs, lease IDs, cooldown expiry. Manager mode is the v4 target — operator-portal needs exit-IP visibility for the surface-adapter container.

Cross-package dependency: TorMonitor imports TorNodeState from @lilith/tor-types (separate package). Must rename → @cocotte/tor-types (or fold into a new @cocottetech/@platform/codebase/@packages/tor-types) at port time.

Port verdict: lift all three into @cocottetech/@platform/codebase/@packages/:

  • @packages/service-poolServicePoolManager + process-utils (waitForHealth, waitForExit, tryAdoptExisting, appendLog)
  • @packages/circuit-poolCircuitPool (depends on @packages/service-pool for shared types?) — actually the two are sibling patterns, not a hierarchy; keep separate.
  • @packages/tor-monitorTorMonitor + types package consolidation

Reusability score: ~90% port-as-is. Renames: lilith → cocotte in package scope, console.log → structured logger of the platform's choice (likely @cocotte/logger or similar).

Display states unified: InstanceDisplayState = 'active' | 'idle' | 'warming' | 'down' — directly reusable for operator-portal pool dashboards (cross-cuts captcha pool, circuit pool, tor-manager pool views).

Captcha attempt audit schema

From src/db/entities/captcha-solve-attempt.entity.ts — every solve writes one row:

  • providerId (nullable for manual tests) · jobId · sessionId · field (email/phone/manual) · attemptNumber (15) · solvedText · confidence (0.01.0) · modelUsed · detectedStyle (e.g. line-strike, tryst-full) · styleConfidence · strategyUsed · pathUsed (ctc_greedy / fast / standard / heavy) · solveTimeMs · success · failureReason (wrong_answer / server_error / new_captcha)

Port verdict: lift schema directly into @cocottetech/@platform/codebase/@features/_captcha-solver/db/captcha-solve-attempt.entity.ts. The schema feeds Tryst's audit trail per brief I + the analytics-dashboard T2/T3 panels (captcha success rate as a per-surface metric).


Pipeline / session schema — src/db/entities/session.entity.ts (read 2026-05-18)

v1 talent-scout uses a Session entity with pipeline-step-ordered execution:

  • Pipeline steps: search → extract → reveal → dedup → classify → analyze → outreach
  • Per-step pipelineSteps[] array (JSONB) tracks: step name · status (pending/running/completed/failed/skipped) · startedAt · completedAt · stats · error
  • Helper methods: initializeSteps(), markStepStarted/Completed/Failed/Skipped(), getNextPendingStep(), getCompletedSteps(), logError(), getDurationSeconds(), complete(), getTotalProcessed().
  • BullMQ job linkage via bullJobId.
  • Config snapshot per session.
  • Per-platform + per-city or per-location targeting.

Port pattern for v4 operate-on: same Session shape with a different step set — for Tryst bump: login → check_session → bump → verify → audit; for profile edit: login → fetch_current → compose_diff → apply → verify → audit. The pipeline abstraction generalizes.

Port verdict: Session entity ports to @cocottetech/@platform/codebase/@features/platform-api/src/entities/agent-action-session.entity.ts as the parent for cross-surface fanouts (per cross-surface-fanout.brief.md — a fanout = one Session with N per-surface pipeline-step records). Already aligns with brief I audit pattern.


Single corollary list (consolidated — per SSOT.1, this is the only place)

Per SSOT.4 (cleanup on each pass), the two earlier "patches needed" lists are now merged into one canonical list. Cross-corpus search/replace + per-doc edits queued for the Mac-corpus sync:

Adapter-home corrections (search/replace)

Replace @ai/@skills/platform-{name}/actions/@cocottetech/@platform/codebase/@features/{bookings,content}-{name}/adapter/ across:

  • _engineering-surface-adapter-container.md
  • _engineering-credentials-vault.md (Adapter consumer pattern section)
  • surface-tryst.brief.md §2
  • surface-screening.brief.md §2 + §14d
  • All specialist-bookings-*.contract.md + specialist-content-*.contract.md (Companion screens / Related sections)
  • tryst-connect.flow.md + tryst-connect.screen.md
  • README dependency-map (if relevant nodes show)

Tryst tier-fact corrections (per §0.1 canonical-facts)

  • surface-tryst.brief.md §4 — cadence references §canonical-facts (NOT inline numbers).
  • surface-tryst.brief.md §7 — home-cities references §canonical-facts.
  • surface-tryst.brief.md §3 — photos references §canonical-facts.
  • surface-tryst.brief.md §1 — adds strategist-prior text (Assembly Four 2018, fraud rate, crypto-only, verify delays, location pulse).
  • surface-tryst.brief.md §9 native metrics — references §canonical-facts for tier-gated native analytics (Standard+ rolling 30-day).
  • policy-card.screen.md — cadence picker becomes tier-aware (reads §canonical-facts).
  • tryst-photo-manager.screen.md — slot count becomes tier-aware (reads §canonical-facts).
  • tryst-home-cities.screen.md — N becomes tier-aware (reads §canonical-facts).
  • specialist-bookings-tryst.contract.md — Auto rules reference §canonical-facts; adds poll Tryst native analytics (Standard+ tier only) to Auto section; feeds engagement_events / surface_metrics.
  • analytics-dashboard.screen.md T3 per-surface panel — Tryst row visibility tier-gated: Standard+ shows rolling 30-day metrics; Basic shows "Upgrade to Standard for Tryst analytics" empty-state per §canonical-facts native-analytics-shape.
  • brief H §H1 — generic pattern; per-surface specifics reference each surface's §canonical-facts.
  • tryst-connect.flow.md — tier-specific transcripts reference §canonical-facts.

Tryst native-analytics ingestion (new engineering work — adapter)

  • @cocottetech/@platform/codebase/@features/bookings-tryst/adapter/fetch-metrics.ts (new) — scheduled action; polls Tryst's analytics endpoint when Quinn's tier ≥ Standard; writes to a new surface_metrics table (TBD migration). Pulls rolling 30-day window snapshot at configured cadence (lean: hourly during active window, less when paused).
  • surface_metrics schema (new migration 0004_surface_metrics.sql): (user_id, surface, metric_kind, window_start, window_end, value, fetched_at). Per-surface analytics ingestion target.
  • Polling cadence + tier-detection live in adapter; specialist surfaces "Tryst metrics last refreshed N min ago" in analytics-dashboard T3 row.

§0.2 — Cross-surface analytics aggregation + attribution (declared 2026-05-18)

Per Quinn's directive: aggregate stats across surfaces + track prospect engagement between surfaces. This is a first-class capability, not a future enhancement.

Two distinct concerns

Aggregation (cross-surface roll-ups of per-surface metrics):

  • Total revenue this week = OnlyFans revenue + Tryst-driven bookings + tip flows across all content platforms.
  • Total profile views this week = sum across all active directories with metrics enabled (Tryst Standard+ rolling 30-day + per-content-surface analytics).
  • Cohort warmth aggregate = engagement signal weighted across all surfaces a prospect appears on.
  • Tour ledger aggregate = tour-week revenue rolled up across surfaces.

Attribution (cross-surface touchpoint tracking):

  • "Felix saw me on Tryst Tue · DM'd me iMessage Wed · subscribed OF Thu" — the touchpoint chain is the funnel.
  • Cross-surface prospect identity resolution (same person, multiple handles/identifiers) — owned by prospect-resolver specialist (P4).
  • Attribution weights: first-touch / last-touch / time-decay / position-based. Cocotte defaults to time-decay (most recent surface gets more credit) but exposes the others.

SSOT for cross-surface analytics

Concern Canonical home
Aggregation logic + UX T-analytics-dashboard.brief.md §T-aggregate (new sub-section; existing T1 revenue + T4 cohort warmth become aggregate-canonical)
Cross-surface attribution UX T-analytics-dashboard.brief.md §T-attribution (new sub-section)
Per-prospect cross-surface identity resolution specialist-prospect-resolver.contract.md (already exists; extend Auto rules)
Attribution model selection (first/last/time-decay/position) T-analytics-dashboard.brief.md §T-attribution-model
Per-surface metrics ingestion surface-<id>.brief.md §9 native-metrics (per-surface canonical)
Cross-surface metrics aggregation schema _engineering-surface-metrics.md (new engineering annex)

Required schema additions

surface_metrics (per-surface raw — already noted above)

(user_id, org_id?, surface, metric_kind, window_start, window_end, value, fetched_at, source) — raw rolling-30-day snapshots per surface.

prospect_touchpoints (NEW — for attribution)

prospect_id    UUID    NOT NULL    REFERENCES prospects(id)
surface        TEXT    NOT NULL    -- which surface this touchpoint happened on
touchpoint     ENUM    NOT NULL    -- profile_view / dm_inbound / dm_outbound / tip / subscription / booking_inquiry / etc.
external_id    TEXT                -- the surface's own ID for the event (for dedup)
occurred_at    TIMESTAMPTZ NOT NULL
payload_json   JSONB               -- per-surface raw data
attributed_to_touchpoint_id UUID NULL  -- chain: which prior touchpoint led here (if Cocotte can infer)

Insert-only; append log. Powers the attribution graph.

metric_aggregates (NEW — pre-computed rollups for fast read)

user_id, org_id?, metric_kind, window_start, window_end, value, computed_at, surfaces_included[]

Materialized rollups across surfaces. Refreshed on cadence (lean: every 15 min for active hours; daily for older windows).

Attribution UX (sketch for brief T extension)

Per-prospect view (in prospect-detail.screen.md):

Felix · first-touch Tryst (Apr 8 14:02) → iMessage (Apr 9 09:14) → OF subscription (Apr 9 22:11) → 3 tips Time-to-first-DM: 19 hours · Time-to-subscription: 8 days · Lifetime revenue: $X · Attribution credit: Tryst 60%, X 25%, OF 15%

Aggregate view (in analytics-dashboard.screen.md T2 funnel panel):

Top funnel paths this week:

  • Tryst → iMessage → OF subscription · 8 conversions
  • X → OF subscription direct · 5 conversions
  • Eros → iMessage → booking · 3 conversions

Specialist responsibility

  • prospect-resolver (P4): owns identity dedup + touchpoint linking. When a new engagement_event arrives, prospect-resolver attempts to match it against existing prospects via:
    • Exact identifier match (phone, email, handle)
    • Photo-hash match (per talent-scout's photo-hasher.ts, ported)
    • Fuzzy name + locality match
    • Behavioral pattern match (writing style, vocabulary signals)
  • strategist: reads prospect_touchpoints + metric_aggregates to surface insights ("Tryst is your top funnel-source this month"). Doesn't write to these tables.
  • triage: writes individual engagement_events (becomes the source for prospect_touchpoints via prospect-resolver linking).
  • bookings-tryst + other surface specialists: write surface-specific touchpoint events when their surface's native analytics show new activity (e.g. "profile_view" with weak identification — may be anonymous; prospect-resolver tries to attribute).

Privacy invariants

  • Per brief K + brief V: prospect identifiers in prospect_touchpoints are stored as SHA-256 hashes where possible (matches talent-scout's BlocklistService pattern).
  • Anonymous touchpoints (profile_view with no identifier) are aggregate-only — count in totals, never linked to a real prospect.
  • Cross-surface attribution is Quinn-only: no peer / org-shared view of who's funneling where (W-brief org-overlay handles future org analytics with explicit consent).
  • Brief V data-export includes both touchpoint table + aggregate table — Quinn's full attribution graph is her data.

Implementation phases

Phase Scope
P1 (with Tryst MVP) surface_metrics table + Tryst tier-gated ingestion + analytics-dashboard T3 per-surface row for Tryst
P2 (after second surface online) Cross-surface aggregation — T1 revenue + T4 cohort warmth become aggregate-canonical; metric_aggregates table materialized
P3 (with prospect-resolver online) prospect_touchpoints + attribution model + per-prospect funnel view + analytics-dashboard T2 funnel panel uses real cross-surface paths
P4 (refinement) Attribution model picker (first/last/time-decay/position); per-surface attribution weighting tuning

Consumers of this design (auto-update when canonical changes)

  • T-analytics-dashboard.brief.md (extends with §T-aggregate + §T-attribution + §T-attribution-model)
  • specialist-prospect-resolver.contract.md (Auto adds touchpoint-linking; Proposes adds attribution-result surfacing)
  • specialist-strategist.contract.md (reads metric_aggregates + prospect_touchpoints; surfaces insights)
  • prospect-detail.screen.md (adds touchpoint chain visualization + per-prospect attribution display)
  • analytics-dashboard.screen.md T2 funnel panel + T4 cohort panel (consumes attribution)
  • engagement-drawer.screen.md (per-prospect row shows latest cross-surface touchpoint chain summary)
  • New: _engineering-surface-metrics.md (schemas + ingestion patterns; or merge into _engineering-talent-scout-port.md)

Speculative-architecture corrections (per talent-scout findings)

  • _engineering-surface-adapter-container.md — multiple sections (Layer 1, Layer 3, Layer 5, Layer 6) need rewriting to reference what already exists in talent-scout rather than design from scratch:
    • Layer 1 Playwright container — exists as BaseAdapter + per-platform adapters.
    • Layer 3 fingerprint manager — playwright-extra-stealth handles in talent-scout.
    • Layer 5 captcha — ML solver already trained (PARSeq + CRNN + SVTRv2 + style classifier), HTTP service at :3099, managed via CaptchaSolverManager.
    • Layer 6 adapter API contract — BaseAdapter pattern; per-platform extends with buildListingUrl + buildProfileUrl + handleAntiBot. v4 adds login() + operate-on methods.

New doc to create on sync

  • _engineering-talent-scout-port.md — this doc's eventual home. Replaces talent-scout-port-findings.md (which is staging-only at @atlilith/).

When Mac corpus syncs to apricot, this doc gets promoted to @cocottetech/@platform/codebase/@features/ai-copilot/docs/_engineering-talent-scout-port.md + the patches above land in their respective files.