Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 31 additions & 29 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,10 @@ Every recording has a **review status** that persists in the index database. Thi
│ scrubbed │ │
│ copy) │ │
└─────┬─────┘ │
┌─────────────────────────┐
│ CLEARED FOR EGRESS │ ← Data can now be sent to:
│ │ storage backends, VLM APIs,
│ │ annotation pipelines, FL, etc.
│ CLEARED FOR EGRESS │ ← Reviewed scrubbed copy only
└─────────────────────────┘
```

Expand All @@ -382,19 +380,17 @@ Every recording has a **review status** that persists in the index database. Thi
| `captured` | Raw recording just created. Pending review. | **No** — blocked from all egress |
| `scrubbed` | Scrub pass completed, awaiting user review | **No** — still pending human approval |
| `reviewed` | User reviewed scrubbed copy and approved | **Yes** — scrubbed copy only |
| `dismissed` | User skipped scrubbing, accepted PII risks | **Yes** — raw data, user's choice |
| `dismissed` | User skipped scrubbing and kept the raw capture local | **No** — dismissal never grants egress |
| `deleted` | User deleted the recording | N/A |

#### Where data can leave the machine (ALL gated by review state)

| Egress Path | What is sent | Gated? |
|-------------|-------------|--------|
| S3 / R2 / HF Hub / MinIO upload | Full recording archive | Yes — must be `reviewed` or `dismissed` |
| OpenAI Vision API (annotation) | Individual screenshots | Yes — must be `reviewed` or `dismissed` |
| Anthropic Claude API (annotation) | Individual screenshots | Yes — must be `reviewed` or `dismissed` |
| Google Gemini API (annotation) | Individual screenshots | Yes — must be `reviewed` or `dismissed` |
| Federated learning (gradient upload) | Model gradients (derived from data) | Yes — must be `reviewed` or `dismissed` |
| Magic Wormhole (P2P sharing) | Full recording | Yes — must be `reviewed` or `dismissed` |
| OpenAdapt hosted ingest | Flow-approved immutable sanitized archive plus its exact manifest | Yes — Flow review, approval, and hash verification |
| S3 / R2 / MinIO upload | Reviewed sanitized derivative archive | Yes — must be `reviewed` |
| Model or annotation service | Reviewed sanitized derivative content | Yes — must be `reviewed` |
| Peer-to-peer sharing | Reviewed sanitized derivative archive | Yes — must be `reviewed` |
| Error reporting / telemetry | Could contain screenshot fragments | Yes — stripped of all capture data |

**Implementation**: A single `check_egress_allowed(capture_id) -> bool` function that every outbound code path calls. If the recording is in `captured` or `scrubbed` state, the function raises `EgressBlockedError` with a user-facing message: "This recording hasn't been reviewed yet. Open the review panel to approve it for sharing."
Expand Down Expand Up @@ -423,9 +419,9 @@ The tray icon can show a badge count of pending reviews. Periodic reminders (con
2. **User opens review panel** (from tray menu, capture browser, or reminder notification)
3. **User chooses action**:
- **"Run Scrubbing"** → scrub worker creates parallel scrubbed copy → state becomes `scrubbed` → review UI shows before/after diff → user approves → state becomes `reviewed`
- **"Dismiss (skip scrubbing)"** → warning: "Your raw recordings may contain passwords, personal information, or sensitive data. They will be uploadable as-is." → user confirms → state becomes `dismissed`
- **"Dismiss (skip scrubbing)"** → warning: "Your raw recording remains local and cannot be uploaded." → user confirms → state becomes `dismissed`
- **"Delete"** → recording deleted from disk → state becomes `deleted`
4. **Once `reviewed` or `dismissed`** → recording is cleared for any egress path (upload, VLM annotation, sharing, etc.)
4. **Once `reviewed`** → only the reviewed scrubbed copy is eligible for an approved egress path.

This means:
- The raw capture is never modified
Expand Down Expand Up @@ -496,9 +492,11 @@ The `scrub_manifest.json` enables the review UI to show exactly what changed:
}
```

#### 5.5 Scrubbing is Optional
#### 5.5 Scrubbing Is Required for Egress

Scrubbing is **recommended but not mandatory**. Users who are uploading their own personal recordings and don't care about PII can skip the scrub step entirely — they just confirm they've reviewed the raw data and consent to upload it as-is. The consent dialog makes this explicit (see Section 8).
A user can dismiss a local review without scrubbing. That choice keeps the raw
recording local. Every egress path requires a separate scrubbed derivative and
an explicit review of that derivative.

---

Expand Down Expand Up @@ -613,7 +611,7 @@ class StorageBackend(Protocol):
def estimate_cost(self, size_bytes: int) -> float | None: ...
```

All backends share the same upload pipeline:
Customer-owned storage adapters share the legacy upload pipeline:

```
[User approves in review UI] → Compress (tar.zst) → Queue → Upload Worker
Expand All @@ -625,6 +623,12 @@ All backends share the same upload pipeline:
- Wormhole: P2P direct
```

Hosted ingest is not one of these generic adapters. `push` delegates to Flow,
which inventories and sanitizes every file, pauses for review, freezes the
approved exact bytes, and sends the archive with its sanitization manifest.
The old `HostedIngestBackend` remains importable for compatibility but refuses
direct uploads. `upload --backend hosted_ingest` routes to governed `push`.

### 7.4 Recommended Backend Combinations

| Profile | Backends | Target User |
Expand Down Expand Up @@ -832,11 +836,8 @@ You are clearing [N] recording sessions ([X.X] GB) for
sharing with external services.

This data includes screenshots of your desktop and records
of your mouse and keyboard actions. [If scrubbing was applied:
"PII scrubbing was applied — review the highlighted regions
above to verify nothing sensitive remains." / If dismissed:
"PII scrubbing was not applied. The raw recordings will be
shared as-is."]
of your mouse and keyboard actions. PII scrubbing was applied.
Review the highlighted regions above to verify nothing sensitive remains.

Once cleared, this data may be sent to:

Expand Down Expand Up @@ -866,7 +867,7 @@ Once cleared, this data may be sent to:
this data will also be open-source

By clicking "Clear for Sharing", you confirm:
1. You have reviewed the [scrubbed/raw] recordings above
1. You have reviewed the scrubbed derivative above
2. You consent to this data being sent to the services
listed above
3. [If any public destination: "You understand this data
Expand All @@ -890,20 +891,21 @@ Key design choices:

### 8.4 Dismiss Flow (Skip Scrubbing)

Users CAN skip scrubbing entirely via the "Dismiss" action in the review panel. This still shows the full consent dialog:
Users can skip scrubbing through the "Dismiss" action. This action does not
grant consent and does not enable egress:

1. User clicks "Dismiss (skip scrubbing)" on a pending recording
2. Warning dialog: "Your recordings will be shared WITHOUT PII removal. Screenshots may contain passwords, personal information, or sensitive data visible on your screen."
3. The consent dialog (8.3) is shown with the full list of configured destinations, with the note "PII scrubbing was not applied. The raw recordings will be shared as-is."
4. User must explicitly confirm → state becomes `dismissed` → recording is cleared for egress
2. Warning dialog: "Your raw recording remains local and cannot be uploaded."
3. User confirms the local dismissal.
4. The state becomes `dismissed`, and all egress checks continue to refuse it.

### 8.5 Batch Operations

For users with many accumulated pending recordings:

- **"Review All"**: Opens a batch review panel. User can scrub all, review summary of redactions across all recordings, and clear them in one action.
- **"Dismiss All"**: Shows the warning + consent dialog once, covering all pending recordings. Good for users who don't care about PII (e.g., recording on a dedicated test machine).
- **Per-app policies** (future): "Auto-dismiss recordings from [app name]" — for users who know certain apps never show PII. Requires explicit opt-in per app.
- **"Dismiss All"**: Keeps all selected raw recordings local and blocked from egress.
- **Per-app policies**: A policy can automate a sanitization step. It cannot mark raw recordings as uploadable.

---

Expand Down
32 changes: 25 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ teaching, escalation, and terminal receipts.
| Python sidecar IPC | JSON-lines handler backed by a shared `EngineDispatcher` (recording, compile/replay/run/teach, auth, sync/push, review, config) | Beta; unit and e2e tests with mocked boundaries |
| Tray IPC socket server | Token-authenticated loopback TCP server plus a `~/.openadapt/desktop_ipc.json` discovery file for `openadapt-tray` | Beta; not yet validated end to end against the shipped tray |
| Desktop-to-flow handoff | `FlowBridge` launches the pinned Flow runtime embedded in the frozen sidecar as an isolated subprocess | Self-contained; no separate Python or Flow installation |
| Hosted auth and push | Browser-PKCE and paste-token sign-in, keychain-stored credential, bundle push, and halted-run break reports to the hosted control plane | Beta |
| Hosted auth and governed handoff | Browser-PKCE and paste-token sign-in; host-bound keychain credentials; exact `openadapt.push-result/v1` review, accepted-ingest, and uncertain-delivery state; local handoff retention; and halted-run break reports | Beta implementation candidate; distribution requires a release-qualified Flow build and live Cloud acceptance before Desktop updates its exact runtime pin |
| Attended phone decisions | One-use QR pairing, protected local evidence, typed allowed actions, runner revalidation, receipts, device revocation, and an optional outbound hosted lane | Beta; device pairing does not replace the deployment's authenticated operator principal |
| Build artifacts | Wheel/sdist, a self-contained PyInstaller engine+Flow runtime, and DMG/MSI/NSIS/DEB/AppImage native jobs | Native jobs prove the frozen browser lifecycle, structurally install/uninstall, and label every platform, architecture, and signing state |
| Native installers | Distinct `desktop-v*` draft-prerelease workflow with final-byte checksums and GitHub provenance, auto-triggered at each engine release | Beta distribution lane; signing state is encoded in every filename and workflow qualification remains specific |
Expand Down Expand Up @@ -221,9 +221,10 @@ pinned sources, hashes, and modification status are recorded in
each surface may then truthfully claim) is in
[docs/CODE_SIGNING.md](docs/CODE_SIGNING.md).

Do not treat the legacy `upload` command or optional upload extras as the
supported hosted path; they predate the current workflow-bundle and
break-report contract.
The legacy `upload --backend hosted_ingest` command is a compatibility alias
for the supported governed `push` path. It does not call the old direct ingest
adapter. Customer-owned storage upload is paused until it uses Flow's complete
inventory, image-capable sanitization, and exact in-app review contract.

## Architecture

Expand Down Expand Up @@ -281,18 +282,35 @@ The Python engine exposes these Beta commands:
| `openadapt-desktop record` | Capture a local session |
| `openadapt-desktop list` / `info` | Inspect capture metadata |
| `openadapt-desktop scrub` | Run configured PII scrubbing |
| `openadapt-desktop review` / `approve` / `dismiss` | Operate the local review state machine |
| `openadapt-desktop review` / `approve` / `dismiss` | Operate the local review state machine; dismissal keeps raw data local |
| `openadapt-desktop compile` / `replay` / `run` | Invoke the bundled, pinned `openadapt-flow` runtime on a capture or bundle |
| `openadapt-desktop login` / `push` / `report-break` | Authenticate to the hosted control plane, push a bundle, report a halted run |
| `openadapt-desktop storage` / `health` / `cleanup` | Inspect and maintain local storage |
| `openadapt-desktop backends` / `upload` | Inspect or invoke legacy upload adapters |
| `openadapt-desktop backends` / `upload` | Inspect legacy customer-owned storage adapters; hosted uses governed `push`, and customer-owned upload remains paused behind a fail-closed release gate |
| `openadapt-desktop config` / `doctor` | Inspect local configuration and dependencies |

Raw recordings are local by default. Any egress path still requires careful
review of the selected adapter, configuration, logs, and data-classification
policy. This repository does not by itself establish a HIPAA-compliant or
production-safe deployment.

The governed `push` implementation delegates to Flow's exact-hash sanitized
derivative contract. It consumes the closed `openadapt.push-result/v1` schema
and retains the exact review or ingest handoff locally. A recording acceptance
requires the server-owned `artifact_ingest_id` and a governed next action. A
bundle acceptance additionally requires the server-owned workflow identity,
the runtime-attestation binding, and the exact trusted dashboard path. An
unknown child or delivery outcome requires reconciliation and never becomes an
automatic retry. The command never falls back to a direct Desktop upload when
Flow is missing or returns an error. The former direct hosted-ingest backend
now refuses every upload. The
This path does not enter a native release until the exact pinned Flow artifact
and the managed Cloud runtime pass the same live acceptance contract. The legacy
customer-owned adapter queue remains paused for this release; its exit
condition is a Flow-owned complete inventory, image-capable scrub, and exact
in-app review. The dormant queue also selects the reviewed scrubbed path again
immediately before egress; a dismissed raw capture is not uploadable.

## Development

Prerequisites are Python 3.11+ and [`uv`](https://docs.astral.sh/uv/). The main
Expand All @@ -316,7 +334,7 @@ current public product boundary.
| [`openadapt-flow`](https://github.com/OpenAdaptAI/openadapt-flow) | Canonical workflow compiler, runtime, certification, and governed repair engine |
| [`OpenAdapt`](https://github.com/OpenAdaptAI/OpenAdapt) | Flagship launcher and meta-repository |
| [`openadapt-tray`](https://github.com/OpenAdaptAI/openadapt-tray) | Experimental system-tray status and launcher companion for this cockpit |
| [`openadapt-capture`](https://github.com/OpenAdaptAI/openadapt-capture) | Experimental capture component used by this Python engine |
| [`openadapt-capture`](https://github.com/OpenAdaptAI/openadapt-capture) | Beta canonical native screen, mouse, keyboard, timing, window-scoping, and media-capture component |
| [`openadapt-privacy`](https://github.com/OpenAdaptAI/openadapt-privacy) | Experimental PII detection and redaction component |

Documentation for the wider stack lives at
Expand Down
Loading