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>
13 KiB
sar-composer.screen
Quinn fulfills a GDPR / CCPA subject access request a prospect emailed her. Pairs with brief V §V3 — the prospect-side SAR flow. The composer scopes, redacts, and sends an export of a single prospect's data, on the prospect's behalf, with platform internals redacted by default.
Voice register: plain throughout — per voice.md §V2c. SARs are legally consequential. No metaphor; exact nouns, short sentences.
Layout (full-screen composer)
┌─────────────────────────────────────────────────┐
│ ◄ Privacy Preview │ 56pt top bar
├─────────────────────────────────────────────────┤
│ │
│ Subject access request │ title (plain)
│ A prospect has asked what data you hold on them.│
│ │
│ ─── Prospect ────────────────────────────── │
│ ╭───────────────────────────────────────╮ │
│ │ Search by phone, email, or handle │ │ prospect lookup
│ ╰───────────────────────────────────────╯ │
│ │
│ Felix · +49 30 12345678 · 14 messages · 8 wks │ lookup result row
│ │
│ ─── Scope ────────────────────────────────── │
│ ● All data on this prospect │
│ ○ Date range [ 2026-02-01 ] → [ 2026-05-18 ] │
│ │
│ ─── Format ───────────────────────────────── │
│ ● Markdown bundle (human-readable) │
│ ○ JSON archive │
│ │
│ ─── Delivery ─────────────────────────────── │
│ ● Prepared link (you send it manually) │
│ ○ Auto-email via mail-sync to felix@example.com │
│ (verified 2026-04-02) │
│ │
│ ─── Redaction preview ────────────────────── │
│ 9 fields · 4 included · 5 redacted · review ▾ │ expandable table
│ │
│ ─── Refuse instead ──────────────────────── │
│ [ Refuse this SAR ] │ secondary action
│ │
└─────────────────────────────────────────────────┘
↓ Preview routes to ready-to-send sheet ↓
Ready-to-send sheet (state 6)
┌─────────────────────────────────────────────────┐
│ Send SAR to Felix │
│ │
│ 14 message exchanges over 8 weeks. │
│ Markdown bundle · 28 KB. │
│ Delivery: prepared link. │
│ 5 fields redacted (platform internals). │
│ │
│ [ Hold off ] [ Generate link ] │
└─────────────────────────────────────────────────┘
Component table
| Component | Notes |
|---|---|
| Top bar | Back to Privacy (S8); Preview disabled until prospect + scope set. |
| Prospect lookup | Free-text search against hashed phone / email / display name. Returns at most 5 candidates with message count + first-contact date. |
| Lookup result row | Tap to select. Selected row pinned above search field. |
| Scope picker | All-data default; date-range secondary with two date fields. |
| Format picker | Markdown bundle default (per V3a — human-readable for SAR). JSON archive secondary. |
| Delivery picker | Prepared-link default (Quinn previews + sends). Auto-email disabled until prospect has a verified email on file. |
| Redaction preview | Collapsed summary line; tap to expand the per-field table (next section). |
| Refuse action | Routes to refusal path (state 8). Secondary visual weight. |
| Preview button | Activates when prospect + scope + format + delivery set. |
States
- Default — empty composer; only prospect lookup active; everything below greyed.
- Prospect-lookup-results — search returned candidates; tap to select. Multi-match disambiguation if 2+ rows match the hash.
- Scope-narrowed — date-range chosen; message-count estimate updates in real time ("8 messages in window").
- Redaction-preview-rendered — expanded table (below) visible; per-row override controls active.
- Auto-redaction-flipped-by-Quinn — Quinn flipped an "included" row to "redacted" (one-tap, allowed) or attempted to flip "redacted" → "included" (gated by override modal — typed-confirm + reason).
- Ready-to-send — Preview tapped; confirmation sheet shown above.
- Sent confirmation — receipt strip in chat-home: "SAR sent to felix@example.com via Proton. 14 message exchanges over 8 weeks. Redacted by default." Audit row recorded with
action_type='sar_fulfilled'. - Refusal path — refusal sheet replaces composer body. Two required fields: reason (text) + date of refusal-grounds (e.g. "active dispute since 2026-04-10").
- Refusal-reason-typed — both fields filled; "Log refusal" button activates. On submit, audit row recorded with
action_type='sar_refused'and the reason payload; banner reminds Quinn the prospect can escalate to a regulator.
Redaction preview table
Per V3b. Renders inside the expanded "Redaction preview" section.
| Field | Status | Reason | Override |
|---|---|---|---|
| Their messages to Quinn | included | their own data | flip to redacted (one-tap) |
| Quinn's replies to them | included | their conversation | flip to redacted (one-tap) |
| Their attachments | included | their own data | flip to redacted (one-tap) |
| Timestamps + thread structure | included | their conversation shape | flip to redacted (one-tap) |
| Quinn's drafts that never sent | redacted | Quinn's drafts, not theirs | override gated |
| Specialist / agent IDs that handled them | redacted | platform internal | override gated (K platform-internals invariant — see Privacy invariants) |
| Quinn's audit decisions about them | partial | event types kept; internal reasoning redacted | override gated |
| Coop reports about them | redacted | shared coop data — separate request path (brief N) | not overrideable |
| Prospect_id + internal flags | redacted | platform metadata | not overrideable |
| Quinn's persona data | redacted | not their data | not overrideable |
| Hotel addresses if mentioned in thread | redacted | brief K §K3f-2 hard rule | not overrideable |
| Quinn's govt name if referenced | redacted | brief K §K3c-1 hard rule | not overrideable |
Override-gated rows open a typed-confirm modal: "Include this in the export. Type include to confirm." Not-overrideable rows show a lock glyph + tooltip pointing to the governing brief.
In-the-wild copy
Composer header (plain):
A prospect has asked what data you hold on them. Build the export, review what's included, and send.
Auto-redaction explainer (plain, in redaction-preview header):
5 fields are redacted by default. They cover platform internals, your drafts, and other people's data. You can include a field manually — it'll ask you to confirm.
Refusal warning (plain — per V3d):
Refuse this SAR. You'll need to give a reason and date. The refusal logs in audit. The prospect can escalate to a regulator. Continue?
Send receipt (plain — per V3c):
SAR sent to felix@example.com via Proton. 14 message exchanges over 8 weeks. Redacted by default.
Refusal receipt (plain):
Refusal logged. Reason on file. The prospect was notified of the refusal and their escalation right.
Privacy invariants
Cross-cut with brief K, brief N, brief Q. Enforced at the redaction layer regardless of Quinn override attempts.
- K3c-1 govt-name never — Quinn's legal name is never in a SAR export. Lock glyph; not overrideable. Applies even if the prospect happens to know the name.
- K3f-2 hotel address never — tour hotel addresses are scrubbed from any thread text included in the export. Not overrideable.
- Brief N coop reports separate path — peer reports about the prospect never enter a SAR. The prospect must request from each coop separately. Not overrideable.
- Platform internals always redacted — specialist IDs, agent_actions reasoning fields, prospect_id, internal flags. Override-gated, not free-flip — opening platform internals requires the typed-confirm modal and a logged reason.
- Quinn's drafts redacted by default — drafts that never sent are Quinn's thinking, not the prospect's data. Override-gated.
Edge cases
- Prospect cannot be looked up — search returns no match. Banner: "No prospect matches that identifier. Search again, or refuse the SAR citing 'no records'." Routes to refusal path with a pre-filled reason.
- Multiple prospects match — hash collision or shared identifier across personas. Composer shows a disambiguation list with message count + first-contact date + which surface. Quinn picks one; can also pick "all matches" if she's confident they're the same human (rare; gated by a typed-confirm).
- Prospect blocked in K1 still gets SAR — being on Quinn's personal blocklist does not relieve the legal obligation. Composer shows a yellow banner: "This prospect is on your blocklist. SAR is still required by law. Refusal needs separate lawful basis." Send still allowed.
- Delivery failure — auto-email bounces or prepared-link generation fails. Banner per brief M §M2: "Couldn't generate the link. Retry or switch delivery channel?" Composer state preserved; no audit row yet (the action hasn't happened).
- Prospect later disputes redaction — the prospect replies "you redacted too much, send me everything." Composer entry from prospect drawer re-opens with the prior SAR's redaction state pre-loaded; Quinn can flip override-gated rows and re-send as an amended SAR. Original SAR's audit row stays; amended SAR gets
action_type='sar_amended'with a back-reference. - SAR draft kept open across vigils — swipe-down on composer prompts "Save as draft?" Drafts live in the prospect drawer entry until sent or discarded.
- Prospect identifier hashed differently than stored — per-coop salt rotations don't affect SAR (SAR uses Quinn's account-local data, not coop hashes). The prospect lookup is over Quinn's own messaging history.
Voice / TTS
- Section reading order: title → prospect → scope → format → delivery → redaction preview → refuse.
- Required-field hints announce on focus.
- Redaction preview rows read as "field: status — reason" (e.g. "their messages to Quinn: included; their own data").
- TTS prosodic shift to slower / lower on the refusal warning copy and on the typed-confirm modals — plain register stays plain when spoken aloud.
- VoiceOver: prospect identifier in the lookup result reads partially masked by default ("Felix, phone ending 5678") unless Quinn taps reveal.
Related
- brief V §V3 — parent design (composer + redaction preview + send + refusal).
- brief B §B3 — prospect drawer entry point ("Send subject access request export").
- brief K §K3c, §K3f — identity invariants enforced in the redaction layer.
- brief N — coop reports separate request path; never inside a SAR.
- brief S §S8 — Settings entry point ("Field a prospect SAR").
- brief I —
sar_fulfilled/sar_refused/sar_amendedaudit rows. - publish-report.screen.md — sibling composer pattern (high-stakes, plain register, preview-then-confirm).
voice.md§V2c — plain register throughout.