Clean successor to V3 (forge: lilith/atlilith). Seeded from local Mac working tree at ~/Code/@projects/@cocottetech/. node_modules and build artifacts excluded via .gitignore. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 KiB
N — Provider coop (inter-org intel sharing)
Goal
v2's client-intel feature is a provider-coop safety-intel network: providers publish reports on clients (rating, safety flags, would-work-again) with COOP_ONLY visibility so other coop members can see the warning before they take a booking. This is structurally different from per-tenant prospect data (lives in this user's / org's database) — it's inter-tenant sharing with consent, attribution choice, and abuse-reporting on the coop itself.
Brief J ports the data layer. This brief designs the UX of being in a coop: opting in, publishing an intel report, reading peers' reports, scoping visibility, staying anonymous (or not), and flagging abuse of the coop.
Designer skim
- Headline UX: Provider-side safety-intel network. Opt-in coops (regional / agency / vertical). Reports attributed by default, optionally anonymous (lower weight). Plain-register voice throughout — coop misreads are expensive.
- Sections (8): N1 data model · N2 onboarding (opt-in) · N3 reading peers · N4 publishing a report · N5 anonymity + attribution · N6 dispute / counter-report · N7 PII hashing + attachment encryption · N8 coop-side moderation.
- Blocking Qs: OPEN-DECISIONS.md → N-Q1 coop discovery, N-Q2 reporter retaliation PII-scan.
In-the-wild copy
Coop intel surfaced before booking (plain — V5 coop rule, never warm):
Two coop reports on this number. One UNSAFE in Berlin coop, Apr 2024. One BOUNDARY_VIOLATION in Industry-TG coop, Oct 2024. Open before replying.
Anonymous-report submit warning (plain):
This report posts anonymous. Peers see "anonymous member." The Berlin moderator still sees you. Continue or attribute.
PII scan before publish (plain):
The notes mention an email Cocotte didn't hash. Want her to redact, or send as-is?
Dispute received (plain):
A subject disputed your Apr 2024 report. Moderator review pending. Your report stays visible.
Constraints
- The coop is a trust network among providers. It is NOT a public review site. Coop intel never reaches clients, never indexes on search engines, and never appears in public CocotteAI surfaces (Brief G web companion, Brief E cross-platform variants).
- Coop membership is opt-in. The default state for any new user/org is not in any coop. Joining is an explicit action with informed consent (per Brief D onboarding doesn't auto-enroll).
- Reports are attributed by default, optionally anonymous. Anonymous reports carry less weight in aggregated scoring (per v2's
safety-score.service.tsport). - The coop is multi-tenant within the platform. Coops are scoped — e.g. a regional coop (Berlin providers), an agency coop (Demimonde members), an industry-vertical coop (TG escorts). One user can be in N coops; each report carries its scope.
- Abuse on the coop is a first-class problem. A coop's value collapses if it's used to settle personal beefs or as a competitive-suppression tool. This brief MUST cover dispute / counter-report / coop-moderation UX.
- Reports surface in chat (Brief A), specialist drawers (Brief B + Brief L's
triagespecialist), and the engagement portal (Brief G). They never surface in the prospect's own surfaces (Brief K's blocklist is per-user; coop intel is per-coop). - Per
voice.brief.md, coop intel surfaces use the plain register — no metaphor. The cost of misread coop data is too high for warmth.
N1 — Inputs / data model
Ported from v2's client-intel/backend-api/src/entities/intel-report.entity.ts, generalized for v4 multi-tenancy:
type CoopReport = {
id: string;
coop_id: string; // which coop this lives in
reporter_user_id: string; // attributed; required even for anonymous reports
reporter_org_id: string | null;
anonymous: boolean; // if true, only the coop moderator sees reporter; peers see "anonymous member"
subject_kind: 'prospect' | 'client_pii_hash' | 'phone' | 'email' | 'directory_handle';
subject_value: string; // hashed where appropriate (PII)
rating: 1 | 2 | 3 | 4 | 5 | null; // 1=avoid, 5=excellent; nullable for flag-only reports
safety_flags: SafetyFlag[]; // UNSAFE | AGGRESSIVE | BOUNDARY_VIOLATION | NON_PAY | TIME_WASTER | SCAM | etc.
would_work_again: boolean | null;
notes: string; // structured: incident summary, evidence references
attachments: AttachmentRef[]; // optional screenshots / receipts; auto-encrypted
created_at: string;
updated_at: string;
withdrawn_at: string | null; // soft-delete; visible to moderators only after withdrawal
};
type Coop = {
id: string;
name: string; // e.g. "Berlin escort coop", "Demimonde internal", "TG providers EU"
kind: 'regional' | 'agency' | 'vertical' | 'private';
scope: { city?: string; country?: string; vertical?: string; org_id?: string };
member_count: number;
moderators: string[]; // user_ids
joined_at: string | null; // null if not a member, ISO string if joined
status: 'invited' | 'pending' | 'member' | 'suspended' | 'left';
config: {
anonymous_reports_allowed: boolean;
min_membership_age_to_report_days: number; // anti-abuse: must be in coop N days before reporting
require_evidence_for_safety_flags: boolean;
};
};
GET /api/v1/coops?user_id=... → list of coops user is in / invited to.
GET /api/v1/coops/:coop_id/reports?subject=... → reports on a specific subject in this coop.
POST /api/v1/coops/:coop_id/reports → publish a new report.
POST /api/v1/coops/:coop_id/reports/:id/dispute → flag a report as inaccurate / abusive.
N2 — Coop membership states (Quinn's perspective)
N2a — Not a member of any coop (default)
- No coop UI surfaces visible.
- A discoverable, dismissable card in the chat-home empty-state inviting Quinn to explore coops she might join — small, non-pushy.
- An "invitations" badge on the settings overlay if any coop has invited her.
N2b — Invited
- Push notification (medium-stakes, plain-register): "Berlin escort coop invited you. 12 members. Tap to read about it."
- Invitation detail sheet: coop name, scope, member count, moderator list, current rules (
config), sample of recent activity (counts only, no contents), accept / decline / "request more info from moderator".
N2c — Pending (Quinn applied, moderator hasn't accepted)
- Banner in coop drawer: "Waiting for moderator approval. Joined queue 2 days ago."
- Quinn can withdraw application without explanation.
N2d — Member
- Coop drawer (see N3) is fully active.
- Reports start surfacing in chat + specialist drawers per N4 routing rules.
N2e — Suspended
- Visible state in coop drawer with reason (from moderator). Quinn can read reports but cannot publish new ones until reinstated.
- This is the moderator's primary anti-abuse tool — softer than removal.
N2f — Left
- Coop is hidden from active surfaces but stays in audit history (Brief I append-only).
- Reports Quinn published while a member stay published (unless she explicitly withdraws each — separate per-report action).
N3 — Coop drawer (peer to Brief B drawers)
A new top-level drawer in the chat surface, accessible from chat-home's top-bar overflow menu and via voice ("show me coop").
Drawer sections (vertically stacked, scrollable):
N3a — Coop selector header
- Horizontal pill list of coops Quinn is in. Tap to switch active coop view.
- "+ invitations (N)" pill if pending invites.
- Settings cog routes to coop-config sheet (per-coop preferences).
N3b — Recent activity
- Last 20 reports in this coop (chronological, newest first).
- Each row: subject (handle / hashed PII display name), rating chip, safety flag chips, attribution ("you", "anonymous member", or named peer).
- Tap row → report detail sheet.
N3c — Subjects I've reported on
- List of subjects Quinn has published reports about, with status (active, edited, withdrawn).
- One-tap to edit Quinn's own report on a subject.
N3d — Watchlist (subjects flagged by peers Quinn wants to track)
- Subjects in this coop with ≥1 high-severity safety flag.
- Each row: subject + aggregate severity + "first reported" timestamp.
- Tap → all reports on that subject + a "first to reach out wins" race indicator if multiple coop members have draft replies to this subject in their own queues (privacy-preserving: shows count, not who).
N3e — Coop info
- Coop name, scope, member count, moderator list, rules (read-only display of
config). - "Leave coop" affordance (with double-confirmation).
- "Report a moderator" affordance (escalates to platform-level moderation, not coop-level — see N6).
N4 — How coop reports surface elsewhere
Coop intel is routed through specialists, not displayed raw in main chat. Specialists per Brief L make decisions informed by coop data, but Quinn only sees the decision + a tap-to-see-coop-evidence affordance.
N4a — triage specialist (the heaviest consumer)
- When a new inbound arrives,
triagequeries coops for any reports on the sender's PII/handle. - If a report exists with
UNSAFE/AGGRESSIVE/BOUNDARY_VIOLATION/SCAM/NON_PAYflag:triageroutes to draft-only with warning. Quinn sees the eligibility decision + a coop-warning chip on the engagement card. - Tap chip → coop report detail sheet (with reporter attribution per N1).
N4b — prospect-resolver (P4)
- Merges coop intel into the prospect's profile in the engagement portal (Brief G).
- Prospect card shows safety summary across coops (count of reports, aggregate severity).
N4c — bookings-{directory} specialists
- Pre-booking gate: if a prospective client matches a high-severity coop report, the directory specialist halts the auto-action and surfaces a high-stakes interrupt per Brief M §M3.
N4d — ai-copilot daily digest
- A short coop section in the digest: "3 new reports in Berlin coop (1 high-severity match against one of your current threads — see triage)."
N5 — Publishing a report (the most carefully designed flow)
The act of publishing intel is deliberate, friction-deliberate, and reversible.
N5a — Entry points
- From engagement card overflow → "Report to coop"
- From prospect detail drawer (Brief B3) → "Coop intel" tab → "Add report"
- From chat — Quinn says "report this client to the Berlin coop" → ai-copilot drafts a structured report and shows it for review
N5b — Compose sheet
- Subject selector — auto-fills from context if entry was from a card/drawer; else manual subject entry with PII-hash on the fly (Quinn types phone → app hashes locally before sending).
- Coop selector — defaults to Quinn's most-active coop; multi-select allowed (one report, multiple coops, with per-coop visibility).
- Rating slider — 1–5, with descriptive labels at each notch.
- Safety flags — multi-select chips (UNSAFE / AGGRESSIVE / BOUNDARY_VIOLATION / NON_PAY / TIME_WASTER / SCAM / OTHER).
- Notes — required text field, character-counted, plain-register prompt copy: "What happened? Be specific. Other providers will rely on this."
- Attachments (optional) — screenshots, receipts; auto-encrypted client-side before upload (per N7 privacy).
- Attribution toggle — "Publish as you" (default) vs "Publish anonymously" (with disclaimer: "Moderators can still see your identity. Anonymous reports carry less weight in aggregate scoring.").
- Preview — full report as it'll appear to peers, plain-register render.
N5c — Confirmation
- High-stakes interrupt per Brief M: "Publishing to Berlin coop. 12 members will see this within minutes. Subject can never see it. You can edit or withdraw it later. Publish?"
- Two buttons: "Publish" (primary, NOT marked safe) + "Hold off" (secondary).
N5d — Post-publish
- Confirmation in chat (plain register): "Published. Subject hash: abc123. 12 peers notified."
- A row enters Quinn's audit drawer (Brief I) — coop reports are first-class
agent_actionsrows. - The report appears in the coop drawer's recent-activity feed within 30s.
N6 — Disputes and abuse on the coop
The coop's existential risk is misuse — competitive suppression, personal vendetta, false-flag reports.
N6a — Disputing a report
- Anyone in the coop can file a dispute on any report, including their own.
- Dispute sheet: structured reason (
inaccurate,personal_dispute,competitive_suppression,revenge,pii_violation,other) + free-text context. - Disputed reports gain a "disputed" badge in the coop drawer + suppress their score weighting until moderator review.
N6b — Moderator review queue
- Coop moderators see disputed reports in a moderator-only drawer section.
- Moderator actions: uphold report (dispute dismissed), withdraw report (soft-delete), suspend reporter (per N2e), or escalate to platform-level moderation (per N6c).
N6c — Platform-level coop moderation
- A specialty role (NOT a per-coop moderator) that handles cross-coop abuse: a member abusing multiple coops, or a moderator being captured by a faction.
- UX surface: a "report a moderator" affordance in N3e + a platform-admin dashboard (out of scope for this brief; engineering brief).
N6d — Coop dissolution
- If a coop is deemed compromised, platform-level moderation can dissolve it — members archive their published reports, coop is hidden from new joins, history preserved in audit.
N7 — Privacy mechanics
N7a — PII hashing
- Phone numbers, emails, handles are hashed client-side before upload using a per-coop salt (so the same phone reported in two coops has different hashes — no cross-coop joining).
- Subject values are never stored in plaintext on platform.db.
N7b — Attachment encryption
- Attachments (screenshots, receipts) are encrypted client-side with per-coop keys; decryption happens client-side when viewing. Platform.api stores ciphertext.
N7c — Data retention
- Withdrawn reports: hidden from peers but kept in moderator-visible archive for 90 days, then hard-deleted.
- Left-coop reports: published reports stay published unless individually withdrawn (per N2f). Quinn's account departure does NOT mass-withdraw.
N7d — Audit trail
- Every coop-report event (publish, edit, withdraw, dispute) is an
agent_actionsrow (Brief I). Quinn can see her full coop history in audit.
States to design
- N2a–N2f membership states (each has a distinct visual + affordance set).
- Coop drawer sections N3a–N3e.
- Compose sheet (N5b) — empty, mid-fill, validation errors, attachment uploading, preview.
- Publish confirmation (N5c) — the high-stakes interrupt.
- Report detail sheet — attributed peer report, anonymous peer report, your own report (with edit/withdraw affordances).
- Coop warning chip on engagement cards (N4a).
- Daily digest coop section (N4d).
- Dispute filing sheet (N6a).
- Moderator review queue (N6b) — coop-moderator role variant of the coop drawer.
- Coop dissolution interrupt (N6d) — informational, no Quinn action required.
Out of scope
- The economics of coop membership (free? paid? per-coop dues?) — product-pricing question.
- Cross-coop intelligence aggregation algorithms (
safety-score.service.tsport-as-is initially; future ML refinements are P5+). - Public-facing or law-enforcement disclosure surfaces — explicitly NOT a feature; coop intel is provider-coop-only.
- Auto-publish via AI — coops are an explicit deliberate Quinn action; no specialist auto-publishes a report. (Specialists can DRAFT a report for Quinn's approval, but never publish autonomously.)
Open questions
- Coop discovery: how does Quinn find coops she's eligible to join? Lean: invitations + platform-curated suggestions based on her scope (city, vertical, org). Avoid public coop catalog (privacy + abuse risk).
- Score weighting of anonymous reports: N1 says anonymous carries less weight; how much less? 50%? 25%? Engineering / data-modeling decision.
- PII hashing salt rotation: per-coop salt is great for privacy but breaks longitudinal joins if the salt changes. Lean: never rotate per-coop salts unless the coop is compromised.
- Cross-platform coop view (Brief E iPad / web companion): coop drawer ports to iPad/web, but report composition is iPhone-first (deliberation + privacy). Lean: peer viewing on all surfaces; composing only on phone for P0.
- Coop-to-coop attestation: a member trusted in coop A wanting to share a report from there with coop B — explicit re-publish action with attribution chain? Future work; defer.
- Reporter protection from retaliation: if a subject identifies themselves in the report's notes accidentally, the platform should warn before publish. Lean: PII-scan the notes field client-side before submit.
Related
- Brief J — port verdict for v2
client-intel(PORT); this brief is the UX consumer. - Brief L §L3c (
triage) + §L3h (prospect-resolver) — the specialists that consume coop intel. - Brief I — every coop-report action is an
agent_actionsrow; coop activity is auditable. - Brief K — Quinn's personal blocklist is per-user; this brief is the coop equivalent. They're complementary: a blocked sender per Brief K doesn't reach Quinn; a coop-flagged sender per Brief N reaches Quinn but with a warning.
- Brief M §M3 — publish confirmation uses the high-stakes interrupt pattern.
voice.brief.md§V2c — coop UI uses plain register; metaphor would be inappropriate for safety-intel.- jobs-to-metaphor memory — coop intel is the rare CocotteAI surface where the cooking metaphor is OFF entirely.