cocottetech/@platform/codebase/@features/ai-copilot/docs/export-build.flow.md

173 lines
11 KiB
Markdown
Raw Normal View History

# 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.