cocottetech/@platform/codebase/@features/ai-copilot/docs/erasure-cooling-off.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

9.7 KiB

Erasure cooling-off flow

End-to-end: from "erase my account" through the 30-day window through commit-or-revert. Pairs with brief V §V2 staged erasure. The kill switch (kill-switch.flow.md) is the sibling staged-action pattern — same banner discipline, same pause semantics, but erasure is terminal where the kill switch is reversible by design.

Voice register throughout: plain (voice §V2c). No metaphor. The cooling-off period is the brand's stance — the system would rather wait 30 days than honor a misclick.

Triggers (Settings S8 only — no voice; too irreversible)

One entry: Settings → S8 Privacy & data → "Erase account or data". No Home Screen quick action, no chat command, no voice trigger, no shortcut. Erasure is terminal; accidental-utterance risk rules voice out. Compare kill switch's three entry points — that flow is reversible, this one isn't.

Step 1 — scope picker

Quinn picks what to erase (per V2a):

Erase data.

What should we erase?
( ) Entire account
( ) A specific surface (Tryst, OF, X, …)
( ) A date range
( ) A specific prospect
( ) Journal entries

[ Cancel ]               [ Continue ]
  • Each option lands on its own preview screen.
  • "Entire account" is the most consequential; the others can be repeated without ending the account.

Step 2 — erasure preview (row counts per scope)

Before the typed-confirm, show what will go. Counts come from a read-only preview query against platform.db:

Erasing your entire account will drop:

· 247 prospects
· 4,891 messages
· 1,203 audit rows (redacted, not deleted — see below)
· 47 content posts
· 18 tour legs
· 392 journal entries
· encrypted attachments (rotated-key-discarded)
· KYC vault contents

[ Back ]               [ Continue ]

Scope-narrower variants show only the relevant counts. If a count comes back zero, the row is suppressed (don't render "0 prospects").

Step 3 — typed-confirm screen

The friction step. Quinn types an exact-lowercase string:

Type erase my account to confirm.

[ ......................................... ]

30-day cooling-off begins on confirm. You can revert any
time in those 30 days.

[ Cancel ]               [ Confirm ]
  • Match is exact lowercaseerase my account. No capitalization tolerance, no trimming variants. The button stays disabled until the match is exact.
  • For scope-narrower variants, the string changes: erase prospect, erase tryst data, erase journal. Same exact-lowercase discipline.

Step 4 — coop carve-out reminder (V2f)

Before commit-to-cooling-off, a one-screen reminder (only fires for "Entire account" or "Specific prospect" scopes):

Reports you filed in coops stay published.

Quinn cannot unilaterally erase reports peers have already
replicated. If you want everything gone from the coop
network, withdraw your coop reports first.

[ Withdraw coop reports first ]      [ Proceed anyway ]

"Withdraw coop reports first" deeplinks to brief N's coop-by-coop withdrawal flow. Coming back from that flow returns Quinn here.

Step 5 — cooling-off entered (30 days; V-Q2 default)

On the final confirm, the user row gets erasure_scheduled_at = now() and erasure_commits_at = now() + 30 days. Every specialist subscribes to erasure.scheduled on Redis pub/sub and:

  • Freezes policy dispatch (same semantics as kill switch §Step 2).
  • Drains in-flight actions where abort is safe.
  • Writes an agent_actions row action_type='erasure_scheduled' with scope payload.

Chat-home stays readable; nothing dispatches. Compare kill switch: identical pause behavior, but the timer is 30 days not "until resume."

Step 6 — banner across every surface during cooling-off

Persistent until day 30 or revert. Plain register:

Erasure scheduled · 28 days · revert

  • Above the chat input, above every drawer header, above the web companion's top nav.
  • Countdown updates daily (not real-time — daily tick is enough; avoids the surface flickering).
  • Tap "revert" opens Step 8.

Step 7 — daily reminder push (C-overriding quiet hours)

One push per day during cooling-off. High-stakes, quiet-hours-overriding (per brief C):

Erasure in 28 days. Tap to revert or review.

  • Fires at 09:00 in Quinn's timezone, every day, with the day count decrementing.
  • On day 29 the copy hardens: Erasure tomorrow. Last chance to revert.
  • Quinn can't mute this push without revert — the override is part of the safety story.

Step 8 — revert affordance (any time)

Single-tap from the banner or from the daily push deeplink:

Revert erasure.

Your account stays. 28 days reverted with 2 days remaining.

[ Hold ]               [ Revert ]

On revert:

  1. erasure_scheduled_at and erasure_commits_at cleared.
  2. erasure.reverted published on Redis; specialists unfreeze and resume policy dispatch through the standard approval gates (no auto-bypass).
  3. Banner clears across every surface.
  4. agent_actions row action_type='erasure_reverted' written.
  5. Daily reminder push cancelled.

Step 9 — commit at day 30

When erasure_commits_at <= now(), the erasure worker fires. Transaction order is dependents-first to keep FKs sane: engagement_eventscontent_postscontent_plansprospectspersonasusers (soft — deleted_at stamped; the row stays for FK integrity on residual redacted audit rows).

Each table's drop is a single SQL transaction; failure rolls back that table only (see Edge cases). KYC vault wipes in a separate transaction against its separate storage. Encrypted attachments rotate-key-discarded — the key is destroyed, the ciphertext stays unreadable forever.

Step 10 — audit redaction not deletion (V2e)

agent_actions is append-only (per brief I §I append-only). Erasure redacts the rows; it doesn't drop them. Each affected row becomes:

{
  id: "...",
  specialist_id: "...",       // preserved
  action_type: "...",         // preserved
  target_kind: "...",         // preserved
  target_id: null,            // nulled
  outcome_json: { redacted: true, original_kind: "..." }
}

The that-it-happened shape survives. The payload is gone. Audit replay still works structurally; payload-inspection doesn't.

Step 11 — ai-copilot memory + ContextProvider cache flush

After the row drops:

  1. ai-copilot per-user memory store wiped (vector embeddings, summary docs, conversation memory).
  2. Every ContextProvider implementation's per-user cache invalidated (Redis pub/sub cache.invalidate.user with the user_id).
  3. mac-sync send-queue purged of pending iMessage notifications for Quinn.
  4. mail-sync stops syncing the affected mailboxes.

Without this step a stale cache could re-materialize a "deleted" prospect on the next ContextProvider read.

Step 12 — final email receipt

A single email to Quinn's verified address (mail-sync delivers):

Your CocotteAI account was erased on 2026-06-17.

247 prospects, 4,891 messages, 47 content posts, 18 tour legs, 392 journal entries dropped. Audit rows redacted. Encrypted attachments destroyed. KYC vault wiped.

Coop reports you filed remain published unless you withdrew them separately.

This is the last email you'll receive from CocotteAI.

After send, the user row's email field is nulled. No further mail can be addressed.

Edge cases

  • Revert at day 29 — fully supported; the worker checks erasure_commits_at <= now() at fire time, so a day-29 revert beats the day-30 worker as long as it lands before the cron tick.
  • Partial commit if worker fails — each table-drop is its own transaction. If content_posts drops succeed but prospects drop fails, the worker writes an erasure_partial audit row, surfaces it to chat plain ("Erasure incomplete — 4 of 6 tables done. Engineering paged."), and retries the failed table on next tick. Banner switches to Erasure incomplete · engineering paged.
  • User re-signs-in during cooling-off — sign-in succeeds; the banner shows on first surface render; chat-home stays read-only-functional. Re-signing-in is not interpreted as intent to revert.
  • Coop-report-withdrawal-flow detour — if Quinn detours through brief N's withdrawal flow from Step 4, returning lands her back at Step 4 (not at the top). State persists.
  • New auto-actions arriving during cooling-off — refused at dispatch with a plain log: "Skipped — erasure scheduled." No surface notification; the daily push is enough.

Voice / TTS

Erasure is never voice-triggered. If Quinn says "Hey copilot, erase my account," ai-copilot replies in plain register:

Erasure isn't a voice action. Open Settings → Privacy & data to start it.

During cooling-off, voice queries are answered normally; the banner is the visible state, not a spoken interrupt. The day-29 push fires audibly per Quinn's notification settings, but TTS doesn't read the countdown unsolicited.

  • brief V §V2 — staged erasure design (V2a scope, V2b typed-confirm, V2c cooling-off, V2d commit, V2e redaction, V2f coop carve-out).
  • brief I — append-only audit; this flow's Step 10 implements V2e against it.
  • brief N — coop carve-out; Step 4 deeplinks here.
  • brief C — daily reminder pushes that override quiet hours.
  • brief K §K5 — kill switch as parallel staged-action pattern (reversible sibling to this terminal flow).
  • kill-switch.flow.md — same banner discipline, same pause semantics, different terminality.
  • voice §V2c — plain register throughout.