V1 Web PWA · Journey

Journey: Account deletion and DSAR

A source-reconciled journey record for V1 Web PWA — shipped behavior, state boundaries, failure modes, and the automation evidence available today.

automated · 6 specs verdict: partial· 2026-06-25
9sections11 minread

On this page
Journey at a glance
ActorsSigned-in memory customerSigned-in data-rights customerDSAR operatorLegal operatorOut-of-band data subject
Automation6 E2E spec(s) · verdict: partial· 2026-06-25

Journey flow#

Generated from the authored steps below — click a node to jump to that section.

flowchart TD s1["1. Customer arrives at the privacy controls"] s2["2. Submit a DSAR export request"] s3["3. Submit a deletion request"] s4["4. Authentication and verification boundary"] s5["5. Cross-service fan-out — partially shipped"] s6["6. Attestation per supported erasure consumer"] s7["7. DSAR receipt back to user"] s8["8. Read back the affected scope"] s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 click s1 href "#1-customer-arrives-at-the-privacy-controls" click s2 href "#2-submit-a-dsar-export-request" click s3 href "#3-submit-a-deletion-request" click s4 href "#4-authentication-and-verification-boundary" click s5 href "#5-cross-service-fan-out--partially-shipped" click s6 href "#6-attestation-per-supported-erasure-consumer" click s7 href "#7-dsar-receipt-back-to-user" click s8 href "#8-read-back-the-affected-scope" s8 --> v(["verdict: partial"]) click v href "../results/account-deletion-and-dsar.html" style v stroke:#d6a531,color:#d6a531

This journey joins two shipped customer surfaces and the operator review path. /profile/memory manages Iris memory, produces a downloadable local bundle, and records a BFF export receipt when Iris is available. The data-rights surface at /profile?path=data-rights and its read-only /profile/data summary create and display BFF-backed export and deletion requests. The wider six-domain tombstone fan-out remains only partially implemented. The walk therefore distinguishes a local memory clear, a scoped data-rights deletion, and the still-open cross-service erasure promise instead of treating them as one event.

Personas#

  • Signed-in memory customer exporting, pausing, or clearing Iris memory at /profile/memory
  • Signed-in data-rights customer creating a scoped export or deletion from /profile?path=data-rights and reviewing it at /profile/data
  • DSAR operator in /operator/admin triaging the case and attaching per-tombstone attestation
  • Legal operator taking the dsar-fulfillment admin-inspection role for sensitive-category reads required during fulfillment
  • Out-of-band data subject — a target persona for email-link or operator-signoff verification; no anonymous browser flow ships today

Pre-conditions#

  • Customer is signed in for the shipped browser flows; admin review requires an admin-scoped bearer
  • Iris adapter routes (/v1/iris/adapter/review, /pause, /export) and the data-rights routes (/v1/data-rights/exports, /deletions) are reachable
  • The canonical IrisDataRightsRequest state machine exists in libs/oshun/memory-iris/src/data-rights.ts, but the current BFF export and deletion stores do not yet persist that contract end to end

Steps#

1. Customer arrives at the privacy controls#

2. Submit a DSAR export request#

  • Customer clicks "Export JSON" (data-profile-memory-export=""); optional DSAR case id via data-profile-memory-dsar-input="" flips the button to "Sign DSAR bundle"
  • exportIrisUserMemoryBundle returns { json, fileName }; notice reads "DSAR export signed and ready to download." (or generic export)
  • When the Iris adapter is hydrated, the same action POSTs /v1/iris/adapter/export?role=settings and renders a synced server receipt; if that call fails, the downloadable local bundle remains available with an honest unavailable receipt state
  • On /profile?path=data-rights, a scoped export POSTs /v1/data-rights/exports; /profile/data reads the completed request and its SHA-256 integrity manifest back from the BFF

3. Submit a deletion request#

  • On /profile/memory, the customer opens "Forget everything", types the confirmation phrase, and passes ensureStepUp('forget-all')
  • requestIrisUserMemoryFullClear returns { ok: true, grace }; the data-profile-memory-grace-banner="" shows the seven-day grace window
  • This memory-panel grace request is local profile state; cancelling it clears the banner. It is not evidence that the account-wide BFF deletion fan-out ran
  • On /profile?path=data-rights, the customer can separately POST a scoped deletion to /v1/data-rights/deletions, choosing grace-period or immediate mode; /profile/data reads the scheduled/completed request and any supported erasure attestations back

4. Authentication and verification boundary#

  • Shipped customer data-rights routes require the signed-in bearer; missing authentication returns 401
  • Per-fact / per-category forgets on /profile/memory use the STEP_UP_CHALLENGE phrase via issueIrisUserMemoryStepUpToken — no email round-trip
  • The canonical session-token, email-link, and operator-signoff transitions exist in data-rights.ts, but an email-link browser round-trip is not wired into the current data-rights route

5. Cross-service fan-out — partially shipped#

Per V1/ARCHITECTURE.md § Privacy, Consent, Residency, DSAR.

  • The BFF fan-out and signed-receipt path is implemented for the voice_profile consumer and for subject-owned Isis generated_artifact rows
  • Tara, Arete, Veritas, Nyx, Nisaba, and Metis do not yet expose a proven six-service erasure fan-out from this browser journey
  • The canonical verified → in-progress → completed transition sequence is modeled in data-rights.ts but is not the persisted record used by every current data-rights endpoint

6. Attestation per supported erasure consumer#

  • Immediate voice-profile and generated-artifact deletions return signed per-consumer receipts with status, row count, audit key id, and signature
  • The broader @oshun/audit-platform attestation fan-out and a single scope-complete gate across every domain remain open

7. DSAR receipt back to user#

  • Export requests expose a real SHA-256 integrity manifest; supported immediate deletions expose signed erasure receipts
  • Verify: operator/operator-admin.md inbox detail card reflects the closed DSAR case with attestation bundle
  • /profile/data and the profile data-rights surface render the BFF request state and supported receipts in-app
  • Email delivery of the DSAR receipt is not wired in the current harness

8. Read back the affected scope#

  • Customer returns to /profile/data and sees the completed export or scheduled/completed deletion row from the BFF
  • A supported immediate generated-artifact deletion proves the seeded Isis row is gone before the signed receipt is persisted
  • A local "Forget everything" action can empty /profile/memory, but that local state is not presented as proof of account-wide erasure
  • Verify: customer/09-account/profile-memory.md local empty and grace-window states
  • Verify: customer/09-account/profile-safety.md empty-state copy avoids alarming framing post-deletion
  • Verify: customer/00-public/legal-privacy.md data-retention section consistent with what the receipt retained vs purged

Post-conditions#

  • /profile/memory always produces a downloadable bundle; when Iris is reachable it also shows the synced export receipt
  • BFF export/deletion requests remain readable at /profile/data; grace-period deletions can be cancelled before cancelDeadlineAt
  • Supported immediate erasures persist signed per-consumer receipts, and the generated-artifact path proves the subject-owned row was removed
  • No claim is made that the unimplemented six-domain fan-out completed; that remains a release-gate requirement

Failure modes#

  • Iris export adapter unavailable/profile/memory labels the receipt unavailable and still offers the local download; it never fabricates a synced receipt
  • Wrong deletion confirmation token — BFF returns 400; no erasure begins
  • Second active deletion — BFF returns 409 rather than creating competing requests
  • Unsupported erasure category — the request must not imply that a signed per-consumer receipt exists; the six-domain fan-out remains incomplete
  • Verification window elapses — the canonical state machine supports an expired terminal state, but this email/operator verification path is not yet wired into the shipped browser flow
  • Silent re-creation from old conversations — treat any reappearance after a supported hard-delete receipt as P0; the broader cross-service guarantee is not yet proven
  • Full memory clear cancelled within gracecancelIrisUserMemoryFullClear removes the local grace request; BFF data-rights cancellation is a separate endpoint and state transition

E2E coverage#

  • Backed by apps/oshun/web/e2e/profile-customer-data-deletion.spec.ts, apps/oshun/web/e2e/profile-data-rights.spec.ts, apps/oshun/web/e2e/profile-customer-data-export.spec.ts, apps/oshun/web/e2e/profile-data-readback.spec.ts — the deletion spec asserts the UI controls (scope/mode/token selectors), schedules an account deletion through the confirmation modal, cancels it via the active banner, and (2026-06-23) drives the real signed-receipt UI path: Playwright boots the BFF with Redis-backed deletion fan-out, the browser submits an immediate voice category deletion, the BFF runs the voice_profile consumer and attaches a dev-key Ed25519 receipt, and the profile UI renders the resulting data-profile-deletion-attestation="voice_profile" row from the live BFF response (status=not_found, zero rows, audit key id, truncated signature). (2026-06-26) The same browser surface now seeds a subject-owned Isis generated_outputs row, submits an immediate generated_artifacts deletion, renders the live signed generated_artifact receipt (status=erased, rowsRemoved=1, audit key id, truncated signature), and verifies the Isis row is hard-deleted. The older page-boundary snapshot case remains only as a negative render guard: an attested tombstone renders a row and an unattested tombstone renders none. The data-rights spec exercises scoped export, deletion scheduling, cancellation within the grace window (the button reverts and a "Deletion request cancelled" toast fires), and the immediate-mode path (switch to immediate erasure → confirm → "Account deletion completed"); the export spec validates the full category/format list, a submitted export appearing in history as completed, and rejection when no category is selected. The readback spec (2026-06-24) verifies the standalone /profile/data page against the same real BFF contracts: authenticated empty export/deletion states, /profile?path=data-rights handoff, and server-rendered completed export + scheduled deletion rows seeded by live POSTs. (2026-06-27) It also warms /profile/data under the real Chromium service worker, verifies the route document is cached after online live-BFF render, switches the browser offline, reloads /profile/data from the SW cache, and asserts the completed export + scheduled deletion rows remain visible while navigator.onLine is false and without falling back to the generic offline shell card.
  • Backed by apps/oshun/web/e2e/data-rights-dsar-bff.spec.ts — real HTTP integration against the live BFF store-backed privacy core (steps 2, 3, 7): customer export (POST/GET /v1/data-rights/exports → 201 completed with a real sha256 integrityManifest surfaced back in the snapshot; malformed/empty-category bodies → 400; anonymous → 401); customer deletion (POST/GET /v1/data-rights/deletions → grace-period request scheduled with a cancelDeadlineAt and materialised tombstones that persist on GET-by-id; immediate voice category deletion over Redis fan-out → completed with a signed voice_profile tombstone attestation persisted on GET-by-id; (2026-06-25) immediate generated_artifacts category deletion seeds a real Isis generated_outputs row for the subject, runs the live Redis-backed generated_artifact eraser, returns a signed erased attestation with rowsRemoved=1, persists that receipt on GET-by-id, and verifies the Isis row is hard-deleted; wrong confirmationToken → 400; a second active deletion → 409; anonymous → 401); the admin DSAR review lifecycle (POST /v1/admin/privacy/requests/:id/review-action driving generate_export_package → record_export_download → complete with specific state transitions, generate refused on a terminal request → 409 and on a delete-type request → 409); and a consent-review → privacy audit-log round-trip (/admin/privacy/consent-records/:id/review then /admin/privacy/audit-log). Admin 403 (admin_scope_missing) is asserted for a non-admin caller.
  • apps/oshun/web/e2e/account-deletion-dsar.spec.ts — the memory-surface counterpart to the profile-dashboard specs above: drives the grace-window banner on /profile/memory (a grace-period deletion request renders the banner; cancel clears it — round-trip) and the DSAR-mode case-id input on the memory export panel (step 2).
  • Coverage depth: deep — see coverage.md. The export/deletion/DSAR lifecycle is driven over the real store-backed BFF: scoped export → 201 completed with a real sha256 integrityManifest; grace-period deletion → scheduled + cancelDeadlineAt + materialised tombstones that persist on GET-by-id; immediate voice-profile deletion runs through the real Redis fan-out and persists a signed receipt; immediate generated-artifact deletion uses the real Isis generated_outputs table in both the BFF API leg and the browser /profile?path=data-rights leg, proving the subject-owned row is removed before persisting/rendering the signed receipt; the admin DSAR review state machine (generate_export_package → record_export_download → complete, with 409 refusals on terminal/delete-type requests); and the signed per-service erasure-receipt rows render from live BFF data. State transitions + post-conditions + multiple failure modes (wrong confirmationToken → 400, second active deletion → 409, anonymous → 401, admin scope-missing → 403, grace-window cancel round-trip) are all asserted, so the journey clears the deep bar. The remaining gaps are cross-service or identity-verification product gaps, not missing assertions over shipped browser/BFF behavior.
  • Uncovered:
    • Steps 5–6, the literal per-domain six-service tombstone fan-out is only partially driven. The BFF-owned Redis fan-out and signed receipt are now covered for voice_profile and the local Isis generated_artifact store, but Tara/Arete/Veritas/Nyx/Nisaba/Metis service-specific erasure plus @oshun/audit-platform attestation remains open.
    • Step 4 identity verification via the email-link round-trip is not driven (no real mailer in the harness; the BFF data-rights flow auto-completes without an email hop).
    • The customer-facing /profile/memory export + forget UI is not driven by this spec (it is a pure-BFF integration test); that web surface is covered by memory-edit-pause-forget.md.
    • Failure modes around domain RPC failure mid fan-out and tombstone propagation drift remain uncovered (same fan-out gap as above).

Per-view files touched#

Cross-references#

  • V1/features.md § User-Facing Memory Controls; § Customer memory UX flows; § Admin inspection state machine; § Customer-Facing Privacy Surface; § Admin DSAR and Operator-Access Workflow
  • V1/ARCHITECTURE.md § Privacy, Consent, Residency, DSAR (the DSAR / deletion sequence mermaid is canonical)
  • libs/oshun/memory-iris/src/data-rights.tsIrisDataRightsRequest, ALLOWED_TRANSITIONS, default verify/deadline windows
  • libs/oshun/memory-iris/src/admin-inspection/state-machine.ts — only dsar-fulfillment may transition from requested for the legal role
  • Sibling journeys: memory-edit-pause-forget.md, incident-triage.md

Open questions / known gaps#

  • Unify the BFF export/deletion records with the canonical IrisDataRightsRequest state machine, or document why they remain separate contracts
  • Wire and test the email-link / operator-signoff verification paths for an out-of-band requester
  • Decide the email template id for the DSAR receipt and link it from customer/13-system/system-emails.md
  • Confirm /profile/safety tombstones along with the rest or runs a tighter retention than MemoryEntry
  • Define telemetry event names: dsar_export_built, dsar_export_signed, dsar_delete_requested, dsar_grace_cancelled (none exist today in apps/oshun/web/src/analytics/)
  • Specify the per-tombstone attestation export format (CSV vs signed JSON bundle); needs an operator-runbook entry