cocottetech/@platform/codebase/@features/ai-copilot/docs/peer-block-report.flow.md
natalie 1b719e1fd7 chore(bootstrap): initial V4 commit
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>
2026-05-18 08:11:41 -07:00

20 KiB

Peer block + report flow

End-to-end sequence for blocking a peer and (optionally) reporting them for review. Anchors brief AE §AE10 — the sanction surfaces ladder (block → mute → coop-mod report → platform-admin report → directory shadowban → coop removal → platform-wide suspension). Pairs with the AE2 connection state machine; siblings are erasure-cooling-off.flow.md (terminal staged action) and vacation-mode.flow.md (declarative mass-pause).

Voice register throughout: plain (voice §V2c). Per AE10 explicit: no metaphor, no hearth-softening, no editorial flourish. Moderation copy reads like a safety system, not a hearth. The brand swaps registers here on purpose — being misread would be costly.

Goal

Give the reporter a low-friction path to (a) hard-cut visibility with another peer (block) and/or (b) escalate that peer's conduct to a review body (coop moderators or platform-admin), with append-only audit on every transition. Block is unilateral and instant; report is adjudicated by humans on the other side. The two are independently invokable: a reporter can block without reporting, report without blocking, or do both in one sweep.

Actors

  • Reporter — the provider invoking the action (in plain register, addressed as "you").
  • Target peer — the peer being blocked / reported. AE2 connection state with the reporter is any of none, requested, connected, muted.
  • Coop moderators — quorum of active mods in a shared coop (per AE4 / brief N). Lean BLR-Q1: 3 of 5 active mods.
  • Platform-admin — out-of-scope brief; signposted recipient for cross-coop or platform-policy violations.

Preconditions

  • Reporter is signed in, posture is discoverable or open (incognito reporters can still block; reporting from incognito is allowed but the reporter's identity surfaces to mods, not to the target — see BLR-Q2).
  • Reporter has some connection-state with target (requested, connected, muted, or merely none with shared-surface contact: salon co-membership, coop co-membership, feed appearance, or a directory profile view within the last 30 days).
  • For coop-mod reports: reporter and target share at least one coop.
  • For platform-admin reports: no shared-coop requirement.

Flow

  1. Trigger. Long-press on (a) a peer row in coop-drawer / peer-roster, (b) a peer-DM message bubble, (c) a salon message bubble, or (d) a directory profile result row. Long-press summons the peer action sheet.
  2. Action sheet. Plain-register sheet with three rows:
    • Mute (per-peer or per-salon — scope picker on tap)
    • Block (per-peer)
    • Report (per-peer) Mute and block are instant; report opens a sub-flow. Cancel dismisses.
  3. Block confirmation modal. If reporter taps Block:
    Block {Target}.
    
    {Target} will not see your profile, feed, salons, or DMs.
    You will not see theirs. Existing peer-DM thread closes.
    
    [ Cancel ]              [ Block ]
    
    Single tap commits. No typed-confirm — block is reversible, unlike erasure.
  4. Block applied — fan-out. On confirm, ai-copilot writes peer_connections row state → blocked (per AE2 state machine). Side-effects, all idempotent:
    • Existing peer-DM thread → closed (rows preserved, surface hidden on both ends).
    • Salon membership co-presence → reporter's view of target's messages is suppressed on read; target's view of reporter likewise (symmetric occlusion regardless of who blocked whom).
    • Feed aggregation (AE9) → target's contributions excluded from reporter's rollups.
    • Endorsements (AE7) between the pair → frozen, not deleted; not re-displayable while blocked.
    • Directory (AE5) → target's profile no longer surfaces to reporter; reporter's profile no longer surfaces to target.
  5. Audit row — block. agent_actions insert: action_type='peer_blocked', target_kind='peer', target_id={target_user_id}, stakes='high', confidence=null (human action), outcome_json={ scope: 'all_AE', reversible: true }. Append-only.
  6. Target notification — none on block. Per AE10 explicit: target is not notified of a block. The occlusion is silent on the target's side; their view of the reporter simply ceases without an event marker. This is intentional — notifying creates retaliation risk.
  7. Report sub-flow — recipient picker. If reporter taps Report (instead of or in addition to Block):
    Report {Target}.
    
    Who reviews this?
    ( ) Coop moderators — {coop_name}
    ( ) Platform admin — cross-coop or policy
    
    [ Cancel ]              [ Continue ]
    
    If reporter and target share zero coops, the coop row is disabled with the inline note "No shared coop. Route to platform admin." Cross-coop violations route platform-admin by default.
  8. Reason picker. Five categories, single-select (per AE10 sanction-ladder mapping):
    • Harassment
    • Spam
    • Impersonation
    • Boundary violation
    • Other "Other" requires the optional-notes field (Step 9) be non-empty.
  9. Optional notes — PII-gated. Free-text ≤ 500 chars. As the reporter types, ai-copilot runs the K3 PII scan client-side (govt names, precise location, contact bytes). On a K3 hit, the submit button disables and a plain-register inline note appears:

    One of those words won't pass. Rewrite without it. No surfacing of which word — per brief AD §AD7-style fallback, the system never names the language of the trigger. Reporter re-drafts; gate re-runs on each keystroke debounce.

  10. Submit. On submit, ai-copilot writes peer_reports row: reporter_id, target_id, recipient_kind (coop_mods | platform_admin), recipient_id (coop_id or null), category, notes, state='pending', submitted_at. Dispatch event peer.report.submitted to Redis pub/sub; coop-mod queue subscribes for coop_mods recipient, platform-admin queue for the other.
  11. Audit row — report. agent_actions insert: action_type='peer_reported', target_id={target}, stakes='high', confidence=null, outcome_json={ category, recipient_kind, recipient_id, report_id }. Per AE10 voice rule, the chat-home receipt is plain:

    Reported {Target} to {coop_mods | platform admin}. Category: {category}. Review opens within 24 hours.

  12. Target notification — yes on report. Per AE10 explicit (contrast with block): target receives a plain-register notification on report submission:

    Someone reported you to {coop_mods | platform admin} for {category}. Review pending. You will hear from {recipient} when a decision lands. Reporter identity is not disclosed to target (BLR-Q2 lean: anonymous to target, identified to mods). Target's peer-DM dispatch to the reporter is paused for the duration of review.

  13. Quorum review (coop-mods recipient). Cooperative mod state machine: pending → quorum_met → decision. Mods receive the report in their coop-mod queue. Quorum threshold (BLR-Q1 lean: 3 of 5 active mods) must align on a decision. If quorum is met within 14 days, state moves to decision with one of: dismissed, directory_shadowban, coop_removal. Higher sanctions (platform-wide suspension) require platform-admin escalation regardless of mod recommendation.
  14. Platform-admin review (platform-admin recipient). Out-of-scope brief; signpost only. Platform-admin can apply any sanction ladder rung up to platform-wide suspension. Same state machine, different actor.
  15. Sanction surface — applied. When a decision lands, ai-copilot writes the sanction to the target's user row + peer_sanctions table and emits a plain-register chat-home notification to the target (not the reporter — reporter sees the outcome receipt in Step 17):
    • dismissed — "Report against you was dismissed. No action."
    • directory_shadowban — "Your directory profile is hidden platform-wide. Other peers cannot find you through search or feed. Existing connections persist. Appeal within 14 days."
    • coop_removal — "Removed from {coop_name} by mod quorum for {category}. Other coops unaffected. Appeal within 14 days."
    • platform_suspension — "Account suspended. Peer-facing surfaces and ai-copilot dispatch are off. Appeal within 14 days. Erasure (brief V) remains available."
  16. Appeals window — 14 days. Per BLR-Q3 lean. Target can submit an appeal via Settings → S5 → "Active sanctions" row. Appeal goes to platform-admin regardless of original recipient (coop mods cannot adjudicate appeals to their own decisions). State moves to appeal_pending.
  17. Outcome receipt — reporter. Reporter sees a plain-register chat-home line on decision:

    Coop mods reviewed your report on {Target}. Decision: {decision}. Your block stays in place regardless.

  18. Appeal decided. Platform-admin returns a verdict: appeal_upheld (sanction reversed, target restored) or appeal_denied (sanction stands). Either way: append-only — the original sanction audit row is not deleted; a supplementary appeal_decided row joins it. State moves to appeal_decided. Target sees a plain-register notification; reporter sees one as well.

State transitions

Step Entity State column
3 → 4 peer_connections.state * → blocked (block-pending → block-applied within same dispatch)
5 agent_actions row written: peer_blocked
10 peer_reports.state null → report-submitted
13 peer_reports.state report-submitted → under-mod-review → quorum-met
13 peer_reports.state quorum-met → sanctioned OR quorum-met → dismissed
15 peer_sanctions.state null → applied
16 peer_sanctions.state applied → appeal-pending
18 peer_sanctions.state appeal-pending → appeal-decided (with appeal_outcomeupheld/denied)
18 peer_connections.state unchanged by appeal — block is reporter's unilateral action, separate from sanction lifecycle

Block and report run on independent state machines. A reporter can unblock at any time without affecting an in-flight report; a sanction can be reversed on appeal without auto-unblocking the reporter (the reporter chose the block; only the reporter ends it).

Voice notes

Every Cocotte-side line is plain register. No "drawer," no "simmering," no "stockpot," no "kept warm." No softening adverbs ("just," "simply," "a little"). No corporate-apology shapes ("we regret," "unfortunately"). Short sentences. Exact nouns. Exact counts. The brand reads like a safety system here because moderation is a safety system.

Sample lines, verbatim, for the surface to use:

  1. "{Target} will not see your profile, feed, salons, or DMs."
  2. "Reported {Target} to coop mods. Category: harassment. Review opens within 24 hours."
  3. "Someone reported you to coop mods for boundary violation. Review pending."
  4. "One of those words won't pass. Rewrite without it."
  5. "Coop mods reviewed your report on {Target}. Decision: directory shadowban. Your block stays in place."
  6. "Removed from {coop_name} by mod quorum for spam. Other coops unaffected. Appeal within 14 days."
  7. "Your directory profile is hidden platform-wide. Existing connections persist."
  8. "Appeal denied. Sanction stands. Erasure (Settings → Privacy & data) remains available."
  9. "Block lifted. {Target} can see your profile again. Salon and DM access does not auto-restore — re-connect if you want it."
  10. "Report withdrawn. Coop mods will see the withdrawal in their queue. The audit row stays."

Edge cases

  • Target already blocked — re-invoking Block is idempotent. The action sheet's Block row is replaced with "Unblock" when target is already in blocked state; tapping it opens a parallel one-tap modal. No duplicate peer_blocked audit row; tapping Block when already blocked is a no-op (silently).
  • Cross-coop report routing — if reporter and target share zero coops, the recipient picker forces platform-admin. If they share multiple coops, the picker enumerates each coop as a distinct row; reporter picks one. Cross-coop violations (target's conduct affects coops the reporter isn't in) always route platform-admin.
  • Both peers in shared coop block each other — symmetric occlusion. Neither sees the other anywhere on AE surfaces — coop peer-roster, salons (both still members; messages mutually suppressed on read), feed, directory, DM. Coop mods still see both peers normally.
  • Reporter withdraws within 24h grace — Settings → S5 → "Reports filed" → tap report row → "Withdraw" button (visible only while state ∈ {report-submitted, under-mod-review} and within 24h of submit). On withdraw: peer_reports.state → withdrawn; coop-mod queue receives the withdrawal event; audit row peer_report_withdrawn joins the report row (original is not deleted). Reporter's block, if separately applied in Step 3, remains.
  • Target appeals successfully — sanction reverses on user row + peer_sanctions. Audit not deleted: original peer_sanctioned row stays, supplemented by appeal_decided row with appeal_outcome='upheld'. Per brief I append-only — supplements never deletes. The that-it-happened shape survives even when the consequence reverses.
  • Mod quorum incomplete after 14d — auto-escalates to platform-admin. peer_reports.state → escalated_to_platform_admin, dispatched to the platform-admin queue. Audit row peer_report_escalated written. Reporter and target both see plain-register notifications: "Coop mods did not reach quorum in 14 days. Platform admin is now reviewing."
  • K3 leak in report notes — per Step 9, the gate re-draft loop fires on each keystroke debounce. Per brief AD §AD7, the system never names which word triggered the gate, never names the language, never offers a thesaurus hint. The reporter re-drafts blind; the gate either passes or it doesn't. This is intentional — naming the trigger leaks the K3 ruleset and trains evasion.
  • Reporter erases account during open report — per brief V §V2f coop carve-out: filed reports stay published. The report does not delete on erasure; it persists in the coop-mod queue. The reporter's peer_reports.reporter_id is preserved in audit (redacted shape, see brief I §I append-only), but display-name on the mod queue resolves to "(erased reporter)" from the redaction state.
  • Target erases account during open report — sanction lifecycle short-circuits to target_erased. Mod queue closes the report. Audit row preserves the report's that-it-happened shape per V2e redaction.
  • Salon-message report vs peer-DM report — same flow; the report row carries an additional context_kind ∈ {salon_message, peer_dm, profile, feed, directory} and context_id field so mods see the bytes-in-context they need to review.

Audit

Every transition in this flow emits exactly one agent_actions row. Action types in order of possible firing:

  • peer_blocked — stakes high, confidence null
  • peer_unblocked — stakes high, confidence null
  • peer_reported — stakes high, confidence null
  • peer_report_withdrawn — stakes medium, confidence null
  • peer_report_escalated — stakes high, confidence null (system-initiated on 14d quorum miss)
  • peer_mod_decision — stakes high, confidence null
  • peer_sanctioned — stakes high, confidence null
  • peer_sanction_appealed — stakes high, confidence null
  • appeal_decided — stakes high, confidence null

confidence is null on every row in this flow: per the project's stance, confidence is a model-output column, not a human-decision column. Moderation actions are intentional human decisions or rule-driven system decisions; the model does not score them.

Append-only per brief I §I. Appeals supplement, never delete. A sanction reversed on appeal leaves both the original peer_sanctioned row and the appeal_decided row in audit, joined by appeal_id. Replay shows the full arc — what happened, what was contested, what changed.

  • brief AE §AE10 — sanction-surfaces ladder + plain-register stance. §AE2 — connection state machine that block plugs into. §AE11 — opt-in postures and how block + incognito interact.
  • brief K — K3 PII gate on the optional-notes field (Step 9).
  • brief I — append-only audit; this flow's Audit section implements against §I.
  • brief V §V2f — coop carve-out: filed reports survive reporter's erasure. Sibling flow erasure-cooling-off.flow.md shares the audit-redaction-not-deletion pattern.
  • brief M — degraded modes when the coop-mod queue is backed up or the platform-admin queue is offline. Block remains available locally regardless; report dispatch retries with backoff and surfaces a plain-register "Report queued — dispatch delayed" line if the queue is unreachable for >5min.
  • brief AD §AD7 — never-names-the-language fallback shape adopted for K3 leaks in report notes.
  • brief N — coop structure and mod-quorum mechanics.
  • coop-drawer.screen.md — entry point for peer-row long-press.
  • audit-row-detail.screen.md — sanction + audit surface that renders the rows this flow emits.
  • vacation-mode.flow.md, erasure-cooling-off.flow.md — sibling flow patterns this doc inherits structure from.
  • voice §V2c — plain register throughout.

Out of scope

  • Criminal-law reporting — not the platform-admin's purview. If a report category implies criminal conduct (threats of violence, CSAM, trafficking, etc.), platform-admin's response surface includes signposting to external resources, but actual law-enforcement liaison is out of scope at P0.
  • Real-world identity verification of reports — mods and platform-admin review the bytes-in-context (salon message, peer-DM, profile) plus the reporter's notes. No KYC of the reporter to "qualify" the report, no in-person verification of claimed incidents. The platform adjudicates platform conduct only.
  • Mod-recruitment / mod-rotation mechanics — handled in brief N (coop). This flow assumes mods exist and quorum is computable.
  • Mute mechanics in detail — Mute is mentioned in the action sheet (Step 2) but has its own lighter flow (no audit row for per-peer mute; per-salon mute writes a salon-membership-modification row). Out of scope here; covered in peer-mute.flow.md (TBD).

Open questions

  • BLR-Q1 — mod-quorum threshold. Lean: 3 of 5 active mods, or unanimous if a coop has fewer than 5 active mods. Active = posted or moderated within last 30d. [blocking] — required for AE4/AE10 to ship.
  • BLR-Q2 — anonymous reports allowed? Lean: no — reporter identity surfaces to mods (so mods can detect coordinated/retaliatory reports), but stays anonymous to target. Reporter sees the lean clearly at report time so they don't expect anonymity-from-mods. [blocking] — affects schema + mod queue UI.
  • BLR-Q3 — appeal window length. Lean: 14 days, matching erasure cooling-off cadence and coop_removal precedent. [nice-to-have] — defensible at 7d or 30d; 14d is the brand-coherent middle.
  • BLR-Q4 — should the action sheet (Step 2) include a "Combined block + report" single-tap row to reduce friction in the common case? Lean: yes for P1, not P0; keep the flows independently auditable at P0 and bundle later once the per-flow telemetry is in. [exploratory]
  • BLR-Q5 — should target be told category of report (Step 12) or only that a report exists? Lean: tell category — vagueness ("someone reported you") reads more menacing than the specific charge, and the brand's stance is plain register over softened. [nice-to-have]
  • BLR-Q6 — withdrawal grace period: 24h (current lean) vs 7d? Lean: 24h, matching the "reflexive misclick" window without enabling reporter-side weaponization (file → wait → withdraw → re-file as harassment vector). [nice-to-have]