# Export-build flow End-to-end: from "give me everything" through downloaded tar.gz on Quinn's disk. Pairs with [brief V](./V-data-portability-erasure.brief.md) §V1 (Quinn-side full export). Voice register throughout: **plain** ([voice](./00-system-voice.md) §V2c). This is operational and legally consequential — no metaphor, exact nouns, exact counts. ## Triggers (two entry points) | # | Entry | Confirmation | Latency target | |---|---|---|---| | 1 | Settings → S8 Privacy & data → **"Build an export"** | Multi-step picker (scope → format → size → confirm) | <1s to landing | | 2 | Voice: **"Hey copilot, export everything"** | ai-copilot opens the same picker pre-filled with V1a defaults; Quinn taps confirm | <2s including STT | Both converge on the same `export-build-dispatch` action. The picker is the same component either way; voice trigger just pre-seeds defaults. ## Step 1 — scope picker (9 sections) Landing card, plain register. Renders the V1a table as togglable rows. All "✓ on" by default per V1a; required row is non-toggleable. ``` Build an export. Pick what to include. Section Default Toggle ────────────────────────────────────────────────────── Profile + persona ✓ on required All prospects + conversations ✓ on [✓] Content plans, posts, assets ✓ on [✓] Audit log (agent_actions) ✓ on [✓] Journal entries ✓ on [✓] Settings + policies ✓ on [✓] Tour legs + hotel history ✓ on [✓] Coop reports you filed ✓ on [✓] Encrypted attachments off [ ] (large; decrypts at download) Estimated size: 4.7 GB [ Cancel ] [ Next: format ] ``` - "Profile + persona" cannot be toggled off — it's the export's identity anchor. - "Coop reports you filed" row has a sub-line: *peer reports about subjects are not included — someone else's data.* - "Encrypted attachments" sub-line: *decrypts client-side on download; otherwise stays encrypted.* - "Brief/doc content" from V1a is omitted from the user-facing picker (platform artifacts, not Quinn data) — surfaces only behind Advanced. ## Step 2 — format picker ``` Pick a format. ( • ) JSON archive (tar.gz) programmatic re-import, full fidelity ( ) PDF report human-readable, for legal/regulatory submissions ( ) Per-prospect Markdown scoped to a single prospect — use SAR flow instead [ Back ] [ Next: review ] ``` - JSON archive is the default per V1b. - Selecting "Per-prospect Markdown" disables Next and surfaces a one-line redirect: *that's the SAR flow — see Settings S8 → Field a prospect SAR.* - PDF runs the same worker; output is a single PDF instead of tar.gz. Size estimate recomputes. ## Step 3 — size estimate (live updates) Bottom-of-card estimate refreshes on every toggle and format change. Computed from current row counts × per-section average byte budget. Displayed as size + estimated build time: > Estimated size: 4.7 GB · build time ~12 minutes. - Updates debounce at 200ms to avoid flicker. - Encrypted-attachments toggle is the dominant driver — flipping it on can move the estimate from ~50 MB to multi-GB. - If the estimate exceeds 10 GB, the card surfaces a sub-line: *large export — keep your device on Wi-Fi to download.* ## Step 4 — worker dispatch On confirm: 1. platform.api enqueues a job on the black-resident `export-worker` queue with scope + format payload. 2. Worker writes a row in `agent_actions` with `action_type='export_started'` and `outcome.scope_hash` for idempotency. 3. ai-copilot replies in chat (read-only confirmation, no notification): > Building your export. About 12 minutes for 4.7 GB. We'll notify when it's ready. 4. A progress fraction `(rows_processed / rows_total)` updates in Redis pub/sub keyed by `export-job:{job_id}`. 5. Quinn can leave the surface — the job runs server-side regardless of app state. ## Step 5 — build progress (visible counter) While the job runs, Settings → S8 shows a live row: ``` Export in progress · 4.7 GB · 43% · ~7 min remaining [ Cancel ] ``` - Counter polls Redis every 2s while the surface is open; closes the channel when Quinn leaves S8. - Cancel is allowed up to the bundle-archive step; cancelled jobs write `action_type='export_cancelled'` and discard partial output. - A single in-flight export per user. A second trigger while one is running surfaces: *one export already building — wait for it or cancel.* ## Step 6 — ready notification When the worker finishes, notifier dispatches a **high-stakes push** per [brief C](./C-notifications.brief.md). **Overrides quiet hours** — the export expiry timer makes timeliness real. > Your export is ready. Download link expires in 7 days. - Fallback hierarchy per [`degraded-mode.flow.md`](./degraded-mode.flow.md) §M4: iMessage → push → in-app banner. - The in-app banner persists in S8 even if push was acknowledged — Quinn can find the download without chasing the notification. - Tap deeplinks to the download sheet (Step 7). ## Step 7 — download with privacy reminder Tap → download sheet, plain register: ``` Your export is ready. Size: 4.7 GB Format: JSON archive (tar.gz) Expires: in 7 days (May 25, 2026) This export contains everything in your CocotteAI account, including prospect identifiers and tour-leg revenue. Store it somewhere encrypted. [ Cancel ] [ Download ] ``` - Download streams from the worker's output store (MinIO, signed URL with 7-day expiry). - Encrypted-attachments section decrypts **client-side** during download per V1c — bundle bytes leaving MinIO are still encrypted; the iOS/web client decrypts as it writes to disk. - After download, sheet updates: *Downloaded May 18, 14:09. Link still valid for 6 days.* Quinn can re-download until expiry. - After 7 days, MinIO object is deleted; signed URL expires; sheet shows: *Expired. Build a new export.* ## Step 8 — audit row writes Two audit rows for a successful export: 1. `action_type='export_started'` on dispatch (Step 4) — captures scope hash, format, estimated size. 2. `action_type='export_built'` on worker completion — captures actual size, actual row counts per section, output object key, expiry timestamp. A third row `action_type='export_downloaded'` writes on first download tap (Step 7); subsequent re-downloads within the 7-day window do **not** double-log (one row per export, not per fetch). All rows preserved per [brief I](./I-audit-trust-replay.brief.md) append-only. Visible in audit drawer; surface in daily digest under "things tended to." ## Edge cases - **Worker fails mid-build**: worker writes `action_type='export_failed'` with `failure_reason`. Chat receipt: *Export didn't finish — {reason}. Nothing was sent anywhere. Try again or pick a smaller scope.* No partial bundle is exposed; MinIO object discarded. - **Attachment decryption fails on a subset**: worker continues; manifest inside the bundle lists undecryptable items with their original encrypted key references. Chat receipt notes the count: *Export ready. 3 attachments couldn't be decrypted and are included encrypted — keys are in your vault.* - **Expiry without download**: after 7 days the signed URL expires and the MinIO object is deleted. S8 row updates: *Expired May 25 — never downloaded. Build a new one?* Audit row `action_type='export_expired'` writes; no notification (low-stakes, expected outcome). - **Partial-section error**: one section (e.g. tour legs) fails to serialize while others succeed. Worker writes the rest, marks the section as `{status: 'error', rows_attempted: N, rows_written: 0}` in the bundle manifest, and surfaces in the receipt: *Export ready. Tour-leg section failed — everything else is in the bundle. Open audit to retry that section alone.* - **Trigger while kill-switch active** (see [`kill-switch.flow.md`](./kill-switch.flow.md)): export-build is allowed — it's a read flow, not a dispatch. Banner *All specialists paused* stays; the worker runs. - **Trigger while platform.api is in Path C degraded**: picker still renders (cached counts); dispatch is queued in SyncEngine and fires on recovery. Quinn sees: *Will start building when memory's back.* ## Privacy reminders (V1d invariants) The bundle is built with these invariants enforced at the worker, not the UI — UI cannot override: - **Govt name never** in the JSON archive. If KYC artifacts exist anywhere in scope (e.g. TS4Rent verification metadata), the worker writes the rows with `govt_name: null` and a `redacted_reason: 'kyc_vault_separate'` marker. KYC vault export is a separate Quinn-typed legal-pretext flow gated behind Advanced + extra confirm. - **Hotel addresses never** in any export shared back to a prospect (the V3 SAR path) or in PDF reports unless Quinn explicitly opts in per-section. The full V1 export to Quinn herself does include addresses (it's her data) — but the worker tags those rows so a downstream re-export to a prospect can't accidentally leak them. - **Coop peer reports never** included — only Quinn's own filed reports. Peer reports about subjects belong to other providers (per [brief N](./N-provider-coop.brief.md)). - The worker validates these invariants post-serialize before sealing the tar.gz. A violation aborts the export, writes `action_type='export_invariant_violation'`, and surfaces in chat as a plain failure: *Export aborted — internal safety check failed. Not your fault. Engineering paged.* ## Voice / TTS specifics Voice trigger "**Hey copilot, export everything**" routes through the same keyword spotter family as kill-switch (fast path, no full LLM intent classifier). ai-copilot's spoken confirmation: > Build an export with the usual sections. Confirm? Quinn says "yes" → dispatch with V1a defaults + JSON format. Anything else → cancelled with: *Holding off. Open S8 if you want to pick sections.* Ready notification's TTS rendering (if voice-output is enabled per S2) is the plain-register sentence verbatim — no metaphor, no flourish: > Your export is ready. Download link expires in seven days. ## Related - [brief V](./V-data-portability-erasure.brief.md) §V1 — design source. - [brief C](./C-notifications.brief.md) — high-stakes notification, quiet-hours override. - [brief S](./S-settings-ia.brief.md) §S8 — entry point in Privacy & data. - [brief I](./I-audit-trust-replay.brief.md) — append-only `agent_actions` rows + digest rendering. - [`degraded-mode.flow.md`](./degraded-mode.flow.md) — fallback hierarchy + Path C behavior during dispatch. - [voice](./00-system-voice.md) §V2c — plain register throughout.