Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1191e11
feat: encrypt recordings, bundles and checkpoints at rest by default
abrichr Nov 3, 2026
9a0b2f4
chore(desktop): release 0.26.0
abrichr Nov 3, 2026
8ba9b14
fix: at-rest review fixes: recording key, missing-key error, mode che…
abrichr Nov 3, 2026
9b5ff48
fix: interim at-rest policy: checkpoints by default, everything in st…
abrichr Nov 3, 2026
89af039
fix: bundle reads use the recording-only key; tests for redacted agen…
abrichr Nov 3, 2026
b068105
style: ruff format
abrichr Nov 3, 2026
1def793
feat: one artifact reader; visualize, explain, emit-skill, for-each, …
abrichr Nov 3, 2026
b87ee28
feat: compose, process, variants, route and process-contract visualiz…
abrichr Nov 3, 2026
db1b181
fix: sealed-input gate reads NAME=PATH arguments and catches a missin…
abrichr Nov 3, 2026
cd941aa
fix: at-rest review: strict allow-list, sealed runs for sealed bundle…
abrichr Nov 3, 2026
e838e68
feat: teach and push read sealed input and keep the library and revie…
abrichr Nov 3, 2026
1175083
fix: the encrypted flag holds only with an encrypted envelope on disk
abrichr Nov 3, 2026
78cd244
fix: strict mode runs other sealed-ready commands on encrypted input …
abrichr Nov 4, 2026
03a34a8
test: Desktop notice queries and the compile-agent e2e use a test scr…
abrichr Nov 4, 2026
7e5e49b
Merge main
abrichr Nov 4, 2026
aa7e782
Merge main
abrichr Nov 4, 2026
53321e3
Merge remote-tracking branch 'origin/main' into HEAD
abrichr Nov 4, 2026
e9e0ab4
Merge commit '53321e3ae' into HEAD
abrichr Nov 4, 2026
1fd65c3
Merge remote-tracking branch 'origin/main' into HEAD
abrichr Nov 4, 2026
bf4caff
Merge commit '1fd65c313' into HEAD
abrichr Nov 4, 2026
03b3f33
Merge remote-tracking branch 'origin/main' into HEAD
abrichr Nov 4, 2026
88832e0
Merge commit '03b3f337f' into HEAD
abrichr Nov 4, 2026
47ffb42
Merge commit '88832e086' into HEAD
abrichr Nov 4, 2026
1d7a3aa
fix: teach and push seal plain input themselves under strict mode
abrichr Nov 4, 2026
bb478b5
Keep sealed run contents out of teach refusals and refuse an --out th…
abrichr Nov 4, 2026
017b1fd
Withhold the details of a push refusal caused by sealed content
abrichr Nov 4, 2026
20b420a
Withhold decrypted input from every refusal
abrichr Nov 4, 2026
70a9cb2
Show names from private input only as a short hash
abrichr Nov 4, 2026
fce3ec8
Withhold teach and push refusals for private input unless built from …
abrichr Nov 4, 2026
1e13e50
Find sealed paths at any depth in arguments, and treat unknown values…
abrichr Nov 4, 2026
3c27d04
Check every string argument against the filesystem for sealed input; …
abrichr Nov 4, 2026
35c15d7
Merge remote-tracking branch 'origin/main' into HEAD
abrichr Nov 4, 2026
ffec567
Treat a name Windows cannot hold as no such path in the sealed-input …
abrichr Nov 4, 2026
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
2 changes: 1 addition & 1 deletion CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ improvising or retrying when something is off.
| Each package installs on its own within a size budget, checked in CI (privacy without desktop, capture without plotting) | On main ([#1270](https://github.com/OpenAdaptAI/OpenAdapt/pull/1270), [#1271](https://github.com/OpenAdaptAI/OpenAdapt/pull/1271), [#1279](https://github.com/OpenAdaptAI/OpenAdapt/pull/1279)) | CI | `.github/workflows/packages.yml` |
| See a compiled program as a map (every step, exact edge, loop and stop rule, with each step's checks) and play a recorded run over it; one React package that Desktop, app.openadapt.ai and openadapt.ai share | On main ([packages/program-graph](packages/program-graph/README.md)) | Real compiled OpenEMR referral program and a recorded scoreboard run, both on synthetic data | `@openadapt/program-graph`; `openadapt-flow visualize --format json` |
| Named sections for every compiled program ("Who: title, first name, last name", "Contact", "Create New Patient"), found without a model from the save, the recorded form structure, the fields and any narration; a person can rename, merge or split them on the review screen | On main | Unit tests; the certified OpenEMR referral bundle and a real recording of it on a local test copy with made-up patients | `openadapt-flow sections`; [SECTIONS.md](packages/flow/docs/SECTIONS.md) |
| Encrypt PHI at rest: every bundle file that can hold patient data under one sealed index (unknown files refused), web recordings from the first frame, and the run directories of sealed bundles; compile, induce, scaffold-verifier, qualify, admit, run, resume, undo, visualize, explain, emit-skill, for-each, sections, check mark, compose, process, variants and route read them in memory through one artifact reader and refuse without the key | On main | Synthetic (no-plaintext audit-hook test over record, compile and a VERIFIED run; per-frame cost measured) | `openadapt-flow seal`, `record`; [phi_at_rest.md](packages/flow/docs/phi_at_rest.md) |
| Encrypt PHI at rest: every bundle file that can hold patient data under one sealed index (unknown files refused), web recordings from the first frame, and the run directories of sealed bundles; compile, induce, scaffold-verifier, qualify, admit, run, resume, undo, visualize, explain, emit-skill, for-each, sections, check mark, compose, process, variants, route, teach and push read them in memory through one artifact reader and refuse without the key; teach keeps its skill library and taught bundle sealed, and push keeps its sanitized review copy and approved archive encrypted until the upload | On main | Synthetic (no-plaintext audit-hook test over record, compile and a VERIFIED run; per-frame cost measured) | `openadapt-flow seal`, `record`; [phi_at_rest.md](packages/flow/docs/phi_at_rest.md) |
| Encryption at rest under a configured or locally generated key (0600): durable checkpoints by default; with `OPENADAPT_FLOW_AT_REST=strict`, every recording, bundle and run directory too. Each plaintext write is logged on stderr and in a ledger that Desktop shows as a notice | On main | Synthetic (key, opt-out, strict-mode and run-directory tests) | `openadapt_flow.at_rest`; [phi_at_rest.md](packages/flow/docs/phi_at_rest.md#default-opt-out-and-strict-mode) |
| Signed receipts (Seals), countersigned and timestamped, offline by default | Released | Unit tests, real TSA tokens | `openadapt-flow seal`, `countersign`, `local-seal` |
| Offline Seals signed with a key the customer holds: a key file, the OS keychain, or a PKCS#11 token or HSM; pinned keys with rotation and revocation; no fallback to a generated key | On main ([#1392](https://github.com/OpenAdaptAI/OpenAdapt/pull/1392)) | Unit tests with synthetic data (keychain and PKCS#11 through stand-ins) | `openadapt-flow local-seal --key`, `local-seal key create`, `pin`, `revoke`; `openadapt seal verify FOLDER` checks every sealed run in a folder; [OFFLINE_SEALS.md](packages/flow/docs/OFFLINE_SEALS.md#customer-held-keys) |
Expand Down
2 changes: 1 addition & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ improvising or retrying when something is off.
| Each package installs on its own within a size budget, checked in CI (privacy without desktop, capture without plotting) | On main ([#1270](https://github.com/OpenAdaptAI/OpenAdapt/pull/1270), [#1271](https://github.com/OpenAdaptAI/OpenAdapt/pull/1271), [#1279](https://github.com/OpenAdaptAI/OpenAdapt/pull/1279)) | CI | `.github/workflows/packages.yml` |
| See a compiled program as a map (every step, exact edge, loop and stop rule, with each step's checks) and play a recorded run over it; one React package that Desktop, app.openadapt.ai and openadapt.ai share | On main ([packages/program-graph](https://github.com/OpenAdaptAI/OpenAdapt/blob/main/packages/program-graph/README.md)) | Real compiled OpenEMR referral program and a recorded scoreboard run, both on synthetic data | `@openadapt/program-graph`; `openadapt-flow visualize --format json` |
| Named sections for every compiled program ("Who: title, first name, last name", "Contact", "Create New Patient"), found without a model from the save, the recorded form structure, the fields and any narration; a person can rename, merge or split them on the review screen | On main | Unit tests; the certified OpenEMR referral bundle and a real recording of it on a local test copy with made-up patients | `openadapt-flow sections`; [SECTIONS.md](https://github.com/OpenAdaptAI/OpenAdapt/blob/main/packages/flow/docs/SECTIONS.md) |
| Encrypt PHI at rest: every bundle file that can hold patient data under one sealed index (unknown files refused), web recordings from the first frame, and the run directories of sealed bundles; compile, induce, scaffold-verifier, qualify, admit, run, resume, undo, visualize, explain, emit-skill, for-each, sections, check mark, compose, process, variants and route read them in memory through one artifact reader and refuse without the key | On main | Synthetic (no-plaintext audit-hook test over record, compile and a VERIFIED run; per-frame cost measured) | `openadapt-flow seal`, `record`; [phi_at_rest.md](https://github.com/OpenAdaptAI/OpenAdapt/blob/main/packages/flow/docs/phi_at_rest.md) |
| Encrypt PHI at rest: every bundle file that can hold patient data under one sealed index (unknown files refused), web recordings from the first frame, and the run directories of sealed bundles; compile, induce, scaffold-verifier, qualify, admit, run, resume, undo, visualize, explain, emit-skill, for-each, sections, check mark, compose, process, variants, route, teach and push read them in memory through one artifact reader and refuse without the key; teach keeps its skill library and taught bundle sealed, and push keeps its sanitized review copy and approved archive encrypted until the upload | On main | Synthetic (no-plaintext audit-hook test over record, compile and a VERIFIED run; per-frame cost measured) | `openadapt-flow seal`, `record`; [phi_at_rest.md](https://github.com/OpenAdaptAI/OpenAdapt/blob/main/packages/flow/docs/phi_at_rest.md) |
| Encryption at rest under a configured or locally generated key (0600): durable checkpoints by default; with `OPENADAPT_FLOW_AT_REST=strict`, every recording, bundle and run directory too. Each plaintext write is logged on stderr and in a ledger that Desktop shows as a notice | On main | Synthetic (key, opt-out, strict-mode and run-directory tests) | `openadapt_flow.at_rest`; [phi_at_rest.md](https://github.com/OpenAdaptAI/OpenAdapt/blob/main/packages/flow/docs/phi_at_rest.md#default-opt-out-and-strict-mode) |
| Signed receipts (Seals), countersigned and timestamped, offline by default | Released | Unit tests, real TSA tokens | `openadapt-flow seal`, `countersign`, `local-seal` |
| Offline Seals signed with a key the customer holds: a key file, the OS keychain, or a PKCS#11 token or HSM; pinned keys with rotation and revocation; no fallback to a generated key | On main ([#1392](https://github.com/OpenAdaptAI/OpenAdapt/pull/1392)) | Unit tests with synthetic data (keychain and PKCS#11 through stand-ins) | `openadapt-flow local-seal --key`, `local-seal key create`, `pin`, `revoke`; `openadapt seal verify FOLDER` checks every sealed run in a folder; [OFFLINE_SEALS.md](https://github.com/OpenAdaptAI/OpenAdapt/blob/main/packages/flow/docs/OFFLINE_SEALS.md#customer-held-keys) |
Expand Down
70 changes: 59 additions & 11 deletions packages/flow/docs/phi_at_rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ with your other secrets.
| Web recordings | Encrypted with a configured key; otherwise logged | Encrypted |
| Bundles from `compile`, `induce`, `admit` | Encrypted from encrypted input; otherwise logged | Encrypted |
| Run directories | Encrypted for a sealed bundle; otherwise logged | Encrypted |
| Desktop recordings, approval requests, `--record-video`, and the output of `visualize`, `explain`, `teach`, `emit-skill`, `push`, `compose`, `process`, `variants`, `route`, `for-each`, `shadow`, `compile-agent`, `guard`, `bench`, `sanitize`, `redact-report`, `jit`, `--save-healed-to`, `--workers` and `induce --report` | Logged | Held back before the command runs |
| The bundle, skill library and compiled fix of `teach` | Encrypted from encrypted input; otherwise logged | Encrypted |
| The sanitized review copy and approved archive of `push` | Encrypted from encrypted input; otherwise logged | Encrypted |
| Desktop recordings, approval requests, `--record-video`, and the output of `visualize`, `explain`, `emit-skill`, `compose`, `process`, `variants`, `route`, `for-each`, `shadow`, `compile-agent`, `guard`, `bench`, `sanitize`, `redact-report`, `jit`, `--save-healed-to`, `--workers` and `induce --report` | Logged | Held back before the command runs |

**Logged** means stderr names the path and one line goes to
`~/.openadapt/plaintext-at-rest.jsonl`; when that line can't be written, the
Expand Down Expand Up @@ -136,8 +138,9 @@ prior certification; certify the sealed destination.

## Inventory

`openadapt_flow.sealed_set.BUNDLE_FILES` and `RECORDING_FILES` are the source
of truth; a test keeps this table in step with them.
`openadapt_flow.sealed_set.BUNDLE_FILES`, `RECORDING_FILES`, `LIBRARY_FILES`
and `SANITIZED_FILES` are the source of truth; a test keeps these tables in
step with them.

### Bundle

Expand Down Expand Up @@ -169,6 +172,24 @@ of truth; a test keeps this table in step with them.
| `distill.json`, `.openadapt-approval.json`, `.openadapt-sanitization.json` | Derived reports and approvals | Encrypted |
| Anything else | Unknown | Refused |

### Skill library

| File | Holds | At rest |
| --- | --- | --- |
| `skills.json` | Every learned program version (target labels, typed literals, screen text, crop paths) and the execution traces it was learned from | Encrypted |
| Anything else | Unknown | Refused |

### Sanitized review copy

| File | Holds | At rest |
| --- | --- | --- |
| `.openadapt-sanitization.json` | The redaction manifest: file names, hashes and redaction records | Encrypted |
| `.openadapt-approval.json` | The reviewer's approval | Encrypted |
| `*` | The scrubbed copy of each recording or bundle file, before review | Encrypted |

The approved archive beside it (`<name>.approved.zip`) is one AES-256-GCM
container under the same key.

### Run directory of a sealed bundle

Every file is encrypted except these structural ones: `.durable_run.claim`
Expand All @@ -190,9 +211,8 @@ The web recorder seals every frame, event line, the metadata and the network
capture in memory and writes only ciphertext, from the first frame. The event
log is resealed whole on each append, so the file on disk is always one
complete ciphertext. The recording is finalized (indexed) when it finishes.
`record --verify` keeps its system-of-record reads inside it, encrypted;
`record --with-check` says it cannot mark a check in an encrypted recording
yet and marks nothing.
`record --verify` keeps its system-of-record reads inside it, encrypted, and
`record --with-check` marks its check inside it, encrypted.

Web recordings are encrypted under a configured key, and under the local key
in strict mode. `--no-encrypt` records one in plaintext, with a warning and a
Expand Down Expand Up @@ -225,9 +245,10 @@ takes tens to hundreds of milliseconds, so encryption adds about 1% or less.
`scaffold-verifier`, `seal`, `certify`, `qualify`, `admit`, `run`, `replay`,
`approve`, `resume`, `undo`, `visualize` (including a process contract),
`explain`, `emit-skill`, `for-each`, `sections`, `check mark`, `compose`,
`process`, `variants` and `route` read sealed bundles, run directories and
encrypted recordings in memory, given the key, and keep what they write inside
a sealed set sealed. Each reads artifact files through one reader,
`process`, `variants`, `route`, `teach`, `push`, `sanitize`,
`review-sanitized` and `approve-sanitized` read sealed bundles, run
directories and encrypted recordings in memory, given the key, and keep what
they write inside a sealed set sealed. Each reads artifact files through one reader,
`openadapt_flow.artifacts`:

- `compile` of an encrypted recording writes a sealed bundle, under
Expand Down Expand Up @@ -261,6 +282,33 @@ a sealed set sealed. Each reads artifact files through one reader,
(`composition.json`, `process-contract.json`, `variant-set.json`) holds
names, digests and pointers; by default it is named on stderr and in the
ledger, and strict mode refuses the command before it runs.
- `teach` of a sealed run, fix recording or bundle, and every `teach` in
strict mode, writes the taught bundle sealed (crops, program and repair
candidate), under the same key rules as `compile`. Its skill library
(`<out>.skills`, or `--library`) is a sealed set of its own, and the fix
recording compiles into `_teach_compiled`, a sealed bundle inside the
recording that the recording's index skips. A library or `--out` directory
that already holds plaintext is refused, so nothing learned from encrypted
input lands beside plaintext. An `--out` or library that is, contains, or
is inside any input (by real path or by inode, so a symlink alias counts)
is refused before anything is written.
- `push` and `sanitize` of an encrypted recording or bundle, and every
`push` in strict mode, write the sanitized review copy as a sealed set of
kind `sanitized`, staged in a private (0700) directory beside it and removed
if anything fails. Scrubbing, the second-pass check and the approval rescan
run on the decrypted bytes in memory and are exactly as strict as for
plaintext. `review-sanitized` shows the original and the copy from memory
and writes a reviewer's redactions back encrypted; `approve-sanitized`
encrypts the approved archive; the upload decrypts that archive in memory and
sends the exact approved bytes.

Refusals never quote what they read. A validation error's quoted input value
is dropped from every refusal the CLI prints. With sealed input or in strict
mode, a `teach` or `push` refusal names only the error's type, unless its text
was built only from the command line (the paths you passed, such as an `--out`
that overlaps an input or a run that came from another bundle). An unexpected
error prints its type instead of a traceback, and a success line names a skill
or workflow only by a short hash.

Without the key, each of these refuses before it reads anything and names the
variable to set. Any other command given a sealed path refuses with a message
Expand Down Expand Up @@ -291,8 +339,8 @@ marks as logged. Also:
and no `seal`: only `workflow.json` and `templates/` are encrypted there.
- The offline Seal of a run (`local-seal`) is not written for an encrypted
run directory yet; the run says so on stderr.
- Commands outside the list above, such as `visualize`, `explain`, `teach`,
`emit-skill` and `push`, refuse sealed input rather than read it.
- Commands outside the list above refuse sealed input rather than decrypt it
to disk.

## The identity template is not encryption

Expand Down
Loading
Loading