58 KiB
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:
- Quinn's direct statement to this collective (in chat) — highest authority.
.quinn/platforms/<surface>/playbook docs (research.md, account.md, advertisement-text.md) — canonical for platform mechanics; Quinn's lived-in knowledge.- 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 cadencepolicy-card.screen.md→ references for cadence pickertryst-photo-manager.screen.md→ references for slot counttryst-home-cities.screen.md→ references for N home basesspecialist-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 ~60–70% of the adapter machinery is directly applicable.
Production status (verified)
- Active recent commits —
output/captcha-screenshots/captcha-tryst-*.pngtimestamps recent. - 18 source modules under
src/([operations/talent-scout/docs/architecture.md]). - Full Express API on
:3400with 13+ controllers. - React control-panel UI (
src/ui/). - BullMQ job queues + session/audit-trail entities.
- TypeORM + dedicated
talent-scoutPostgreSQL 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.ts—hasNextPage / 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
maxInstanceswhen 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 atsrc/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 fromextractors/dom/{rates-menu, touring-verification, media-social, contact-reveal, profile-details}-extractor.ts+ pure helpers fromextractors/{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.normalizePhonestrips non-digits AND strips leading1country-code for US (11-digit normalization). DefaulthandleAntiBothandles 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@cocottescope. 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,ProxyProviderabstraction. - Port verdict: rename →
@cocotte/tor-types. The package itself stays in the@torapplication 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(+ sharedtypes.ts, all with.test.tssiblings)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.mdat 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 notesadvertisement-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:
- 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 fordata.mobile / data.email / data.phone. Bypasses DOM timing issues. - Path B — DOM polling (fallback):
waitForFunctionchecks[data-unobfuscate-details-target="output"]until●(obfuscation) chars disappear. Then reads from injectedmailto:/sms:/tel:link if present, else from span text. - Path C — postMessage capture (final fallback): listens to
window.messageevents 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:
failureReasonclassified 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 60–180 ALTCHA + Turnstile + terms-toast (kept below)
Read directly from the adapter code:
ALTCHA verification (Tryst's primary protection)
- Two-step gate:
- Client-side PoW auto-solves (checkbox text changes "Verifying..." → "Verification required!")
- Visual text-captcha dialog appears (distorted text image + code input)
- After correct solve, form POSTs + page redirects to real content
- Adapter has
waitForAltchaPow(page)+solveAltchaChallenge(page)(called fromhandleAntiBot)
Cloudflare Turnstile
- Selector:
[data-sitekey], .cf-turnstile, iframe[src*="turnstile"] - Auto-solved by
playwright-extra-stealthplugin (~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"]→ clickbutton, [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 otherspecialist-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:
-
_engineering-surface-adapter-container.mdneeds 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. -
New
_engineering-talent-scout-port.md— promotes this findings doc to a proper engineering brief with file-by-file port verdicts. -
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). -
_engineering-credentials-vault.md— note that talent-scout already uses@lilith/circuit-breakerpackage; v4 credentials adapter can reuse. -
surface-tryst.brief.md §3 Profile data model— Quinn's actual.quinn/platforms/tryst/account.md+advertisement-text.mdshould be ingested as concrete confirmation of the schema fields. Worth reading those before finalizing §3. -
O-surfaces-roster.brief.md— confirms the 12 escort directories Quinn operates on; matches the .quinn/platforms/ list exactly. -
brand-familymemory — 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:
- Read deeper into talent-scout's
base-adapter.ts+tryst-adapter.tsin full (line ranges still unread). - Read
.quinn/platforms/tryst/{account,advertisement-text,research}.md— Quinn's actual current Tryst state. - Scaffold
~/Code/@applications/@ai/@skills/_shared/+platform-tryst/directories (CLAUDE.md-canonical location). - Lift
base-adapter.ts+ helpers — minimal rewrite (just method signatures for operate-on). - Lift captcha-solver ml-service to
@ml/captcha-solver/. - Implement
platform-tryst/actions/login.tsas the first operate-on action — exercises BaseAdapter + captcha-solver + Tor pool end-to-end. - Implement
bump.ts— the H1-canonical action. - Wire to
platform.apipolicy 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 0–180 read; ~600 lines total likely). — READ 2026-05-18 (see Captcha-solver section above).src/services/captcha-solver/types.ts+ service implementation- Tor manager source — NOT in talent-scout
src/; external service at:7710(likely docker-compose). Needs another read. — PARTIAL READ 2026-05-18 (session.entity.ts + captcha-solve-attempt.entity.ts; see §Entities below).src/db/entities/— READ 2026-05-18 (see §Quinn's Tryst facts below)..quinn/platforms/tryst/account.md+advertisement-text.md+research.mdpackages/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 atsrc/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 10–14 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't→ No reviews; annotated in research.md."every 4h" cadence→ Tier-dependent per §canonical-facts.Photo slot count→ Per 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
SolveStrategyenum:trocr / clip / crnn / parseq / parseq_style / svtrv2 / svtrv2_style / style_expert / auto. Mirrors PythonSolveMethodenum on the FastAPI side. CaptchaSolverManager: spawns Python uvicorn as managed child process; tracks PID; monitors health via HTTP at:3099; dynamic scale-up (up tomaxInstances); cooldown-before-scale-down;tryAdoptExistingfor no-orphan startup.- Generic
ServicePoolManagerabstraction 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:
-
ServicePoolManager(process pool) — manages Python uvicorn child processes (captcha-solver, LLM). Generic viaSpawnConfig { 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.tryAdoptExistingfor orphan recovery across Node restarts. SIGTERM withSTOP_TIMEOUT_MSthen SIGKILL. -
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). Usesplaywright-extra+puppeteer-extra-plugin-stealth. Per-context randomization: viewport (4 options), userAgent (4 options), locale=en-US, timezone=America/Los_Angeles. Route-blocksimage/font/media/stylesheet. Note: Quinn requires images for vision-based detection in some adapter modes — blocking is per-adapter-policy, not global. -
TorMonitor— two-mode health checker:- Legacy TCP mode (
proxy.type='tor'): TCP-pinghttpProxyPort(default 3128); mergesCircuitPoolstates for display. - Manager mode (
proxy.type='tor-managed'): queries externaltor-managerREST API/api/v1/pool/statusfor 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.
- Legacy TCP mode (
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-pool←ServicePoolManager+process-utils(waitForHealth, waitForExit, tryAdoptExisting, appendLog)@packages/circuit-pool←CircuitPool(depends on@packages/service-poolfor shared types?) — actually the two are sibling patterns, not a hierarchy; keep separate.@packages/tor-monitor←TorMonitor+ 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(1–5) ·solvedText·confidence(0.0–1.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§2surface-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; addspoll Tryst native analytics (Standard+ tier only)to Auto section; feeds engagement_events / surface_metrics.analytics-dashboard.screen.mdT3 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 newsurface_metricstable (TBD migration). Pulls rolling 30-day window snapshot at configured cadence (lean: hourly during active window, less when paused).surface_metricsschema (new migration0004_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-resolverspecialist (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: readsprospect_touchpoints+metric_aggregatesto surface insights ("Tryst is your top funnel-source this month"). Doesn't write to these tables.triage: writes individualengagement_events(becomes the source forprospect_touchpointsvia 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_touchpointsare stored as SHA-256 hashes where possible (matches talent-scout'sBlocklistServicepattern). - 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(readsmetric_aggregates+prospect_touchpoints; surfaces insights)prospect-detail.screen.md(adds touchpoint chain visualization + per-prospect attribution display)analytics-dashboard.screen.mdT2 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 —
BaseAdapterpattern; per-platform extends withbuildListingUrl+buildProfileUrl+handleAntiBot. v4 addslogin()+ operate-on methods.
- Layer 1 Playwright container — exists as
New doc to create on sync
_engineering-talent-scout-port.md— this doc's eventual home. Replacestalent-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.