diff --git a/docs/commercial/execute-api.md b/docs/commercial/execute-api.md index 5d24a3e..ab4914f 100644 --- a/docs/commercial/execute-api.md +++ b/docs/commercial/execute-api.md @@ -236,6 +236,21 @@ This pattern also covers a response loss after your endpoint accepted an event: OpenAdapt can retry, and your `event_id` record makes the second delivery safe. +## Process children are separate executions + +A [ProcessContract](../concepts/process-contract.md) parent sequences +independently admitted capabilities. Each child is one `POST /v1/executions` +with that child's `qualification_id`, `workflow_version`, `workflow_digest`, +and its own `idempotency_key`. The parent doesn't collapse them into one +request. + +Copy a handoff parameter into the next child's `parameters` only when the +predecessor receipt `outcome` is `verified` and the predecessor's effect +contract bound that fact. Don't replay the parent. If a predecessor returns +`reconciliation_required`, stop and reconcile; don't dispatch the successor. + +See [Sequence work across two applications](../guides/compose-multi-application.md). + ## Integration checklist - Keep the service token and webhook secret on the server. diff --git a/docs/concepts/index.md b/docs/concepts/index.md index 3c86c80..c94af03 100644 --- a/docs/concepts/index.md +++ b/docs/concepts/index.md @@ -69,6 +69,11 @@ jump to what you need. `lint` reports gaps, `certify` refuses an unsafe bundle before it deploys. +- [__Process contracts__](process-contract.md) + + A parent receipt over independently admitted capabilities. Handoffs copy + effect facts. Compose sequences recordings; process points at admissions. + - [__Backends: where it runs__](backends.md) Browser (Playwright), native Windows (UIA), native macOS, native Linux diff --git a/docs/concepts/process-contract.md b/docs/concepts/process-contract.md new file mode 100644 index 0000000..b79e706 --- /dev/null +++ b/docs/concepts/process-contract.md @@ -0,0 +1,46 @@ +# Process contracts + +A ProcessContract is a parent receipt over independently admitted capabilities. +Schema `openadapt.process-contract/v0`. Each child already carries a live +`openadapt.qualification-admission/v1` envelope: one workflow version, one +bundle digest, a counted campaign, identity and effect contract digests, a +30-day lifetime, signed Ed25519. The parent names those admissions by +`admission_id`. It doesn't copy recordings. + +`admission_id` is a UUID on the child's envelope, distinct from +`runtime_validation_id`. Sitting next to another child doesn't extend anyone's +validity interval or digest binding. A child whose admission is expired, +revoked, or bound to a different digest is refused before Execute is called. + +## Handoffs are effect facts + +A handoff copies a parameter from child A into child B only when A's run ended +`VERIFIED` and A's Effect CONFIRMed that parameter. Window titles and URLs are +not evidence. A missing, empty, or unbound fact stops the parent before B +starts. + +The parent receipt records, for each child, the `admission_id`, workflow +version, bundle digest, terminal outcome, and model-call count. For each +handoff it records the source, the target, and that the source was `VERIFIED`. +Parent `VERIFIED` requires every child `VERIFIED` and zero model calls. + +## The recording parent and the admission parent + +`openadapt flow compose` writes `composition.json` +(`openadapt.composition/v1`) and copies compiled child bundles. Use it after +you recorded each surface, before those children are admitted. `certify` and +`run` execute that directory. `replay` refuses it. + +`openadapt flow process` writes `process-contract.json` and points at +admissions. It copies nothing. Pointing it at a `composition.json` directory +is refused, because those children aren't admitted. Each child runs through +[OpenAdapt Execute](../commercial/execute-api.md) with that child's envelope. +`replay` of the process parent is refused. + +A compiled bundle still has its own ProgramGraph. `visualize` on a process +parent shows admitted children, handoff edges, and a terminal labeled End of +declared steps. Open the child bundle for its steps. See +[Read a compiled program](program-visualizer.md). + +RFC-0001 is the name of this contract. Qualify each child first: +[Qualify a workflow](../guides/qualify-a-workflow.md). diff --git a/docs/concepts/program-visualizer.md b/docs/concepts/program-visualizer.md index 14c6de2..5ede4c5 100644 --- a/docs/concepts/program-visualizer.md +++ b/docs/concepts/program-visualizer.md @@ -60,6 +60,29 @@ It does not prove `VERIFIED`. The run outcome still depends on the exact authorization, identity, postcondition, and effect evidence required by the execution profile. +## Parents of children stay parents + +`visualize` on a compiled bundle emits that bundle's ProgramGraph: steps, +ladders, gates, halt points. + +`visualize` on a compose directory (`composition.json`, schema +`openadapt.composition/v1`) draws each compiled child as one node. Handoff +edges are labeled with the effect-bound parameter names they copy, never a +window title or a URL. Sequence edges follow `--after`, or `--child` order. + +`visualize` on a process-contract directory (`process-contract.json`, schema +`openadapt.process-contract/v0`) draws each independently admitted child +(name, `admission_id`) and the same kind of handoff edge. + +Both parents end at a terminal titled End of declared steps, not Success. +Traversal ended. The parent is `VERIFIED` only when every child's receipt says +so. + +The parent view doesn't inline a child's steps. Open the child bundle for its +program map. See +[Sequence work across two applications](../guides/compose-multi-application.md) +and [Process contracts](process-contract.md). + ## Follow one live occurrence The same program node can run more than once inside a loop. A useful live view diff --git a/docs/get-started/index.md b/docs/get-started/index.md index d9eae4b..1c2f7b2 100644 --- a/docs/get-started/index.md +++ b/docs/get-started/index.md @@ -167,10 +167,23 @@ for the real environment. ## Where to go next -To compile several recordings, read -[Induce a program](../guides/induce-a-program.md). [Durable -runs](../concepts/durable-runtime.md) explains how an operator can resume from -the last verified checkpoint after a halt. +To compile several recordings of the same task, read +[Induce a program](../guides/induce-a-program.md). A task that starts in one +application and finishes in another is two recordings. Don't record them as +one. Sequence the compiled bundles with `compose`, or after each child is +admitted, with a process parent: + +```bash +openadapt flow compose \ + --child intake=./intake-bundle \ + --child posting=./posting-bundle \ + --handoff intake.patient_id=posting.patient_id \ + --out composed +``` + +See [Sequence work across two applications](../guides/compose-multi-application.md). +[Durable runs](../concepts/durable-runtime.md) explains how an operator can +resume from the last verified checkpoint after a halt.
diff --git a/docs/guides/compose-multi-application.md b/docs/guides/compose-multi-application.md new file mode 100644 index 0000000..7632550 --- /dev/null +++ b/docs/guides/compose-multi-application.md @@ -0,0 +1,123 @@ +--- +description: >- + Record one program per application, sequence the compiled recordings with + compose, then after qualification and admission author a ProcessContract + parent. +--- + +# Sequence work across two applications + +One recording binds one surface. If the work starts in a browser intake form +and finishes in a native posting app, record both. + +`for-each` loops one bundle over a worklist. `induce` recovers a program from +several traces of the same task on the same backend. The two parents that +sequence work *across* surfaces are `compose` (compiled recordings) and +`process` (independently admitted capabilities). + +## Record one program per surface + +Record and compile each application the way you already do: + +```bash +openadapt flow record --backend web --url https://intake.example --out rec-intake +openadapt flow compile rec-intake --out intake-bundle --name intake + +openadapt flow record --backend windows --out rec-posting +openadapt flow compile rec-posting --out posting-bundle --name posting +``` + +Decide the handoff before you sequence anything. The parent will copy +`patient_id` only if intake's effect contract already bound it. A window title +isn't that fact. Compose won't retarget the intake recording onto the +posting surface. + +## Sequence the recordings with compose + +`openadapt flow compose` writes a parent directory of compiled children: + +```bash +openadapt flow compose \ + --child intake=./intake-bundle \ + --child posting=./posting-bundle \ + --handoff intake.patient_id=posting.patient_id \ + --out composed +openadapt flow certify composed --policy clinical-write +openadapt flow run composed --config deploy.yaml +``` + +Default order is `--child` order. `--after NAME=PRED` declares a DAG; a cycle +is refused at authoring. Child B starts only after child A ends `VERIFIED`, or +a halt class you named with `--allow-halt NAME=OUTCOME`. + +Handoffs copy parameter values that A's confirmed effect contract already +bound. Missing evidence stops the run. The parent won't guess a URL. + +The on-disk form is `composed/composition.json` (schema +`openadapt.composition/v1`) plus `composed/children//`. Subflows and +worklists stay inside each child. `replay` refuses this directory: + +```text +replay refuses a composition artifact; use `openadapt-flow run` +``` + +`visualize` on `composed` shows the child bundles, the handoff edges, and a +terminal labeled End of declared steps. Open `intake-bundle` if you need +intake's program graph. + +```bash +openadapt flow visualize composed -o composed.html +openadapt flow visualize intake-bundle -o intake.html +``` + +Flags and the `certify` / `run` path are in the +[CLI reference](../reference/cli.md#compose). + +## Qualify and admit each child + +Compose sequences recordings. A ProcessContract parent will refuse those +copies. [Qualify each child](qualify-a-workflow.md) on its own surface, run +its counted campaign, and keep the signed +`openadapt.qualification-admission/v1` envelope. The envelope's +`admission_id` is a UUID. It is distinct from `runtime_validation_id`. + +A recording you compiled five minutes ago and forgot to qualify is not a +process child. + +## Sequence admitted capabilities with process + +After both envelopes exist, author the process parent: + +```bash +openadapt flow process \ + --child intake=./intake-bundle \ + --admission intake=./intake-admission.json \ + --child posting=./posting-bundle \ + --admission posting=./posting-admission.json \ + --handoff intake.patient_id=posting.patient_id \ + --out process-parent +openadapt flow visualize process-parent -o process.html +``` + +`--handoff`, `--after`, and `--allow-halt` have the same shape as `compose`. +`--child NAME=BUNDLE` is the admitted bundle. `--admission NAME=ENVELOPE` is +that child's signed `openadapt.qualification-admission/v1` file. A compose +child path under `composition.json` is not an envelope. The parent file is +`process-parent/process-contract.json`, schema +`openadapt.process-contract/v0`. It points at the envelopes. It doesn't copy +recordings. It doesn't become a ProgramGraph. + +Pointing `process` at a `composition.json` directory is refused. That check is +what keeps unqualified recordings out of a process receipt. + +Each child runs through [OpenAdapt Execute](../commercial/execute-api.md) +with that child's envelope, qualification binding, and its own idempotency +key. `replay` of the process parent is refused. + +`visualize` on the process directory shows admitted children, handoff edges, +and End of declared steps. That label means the declared sequence ended. It +doesn't mean `VERIFIED`. Parent `VERIFIED` requires every child `VERIFIED` +and zero model calls. + +Open the child bundle when you need its steps. The parent view won't inline +them. diff --git a/docs/guides/index.md b/docs/guides/index.md index 092c950..0e45e2b 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -19,6 +19,11 @@ How-to guides for real deployments. Each assumes you have Recover a parameterized program from several demonstrations, and run it over a worklist. +- [__Sequence work across two applications__](compose-multi-application.md) + + Record one program per surface, sequence the compiled recordings with + `compose`, then after admission author a ProcessContract parent. + - [__Write and enforce a policy__](policy-and-certification.md) Use `lint` to find gaps and `certify` to refuse a bundle that violates the diff --git a/docs/reference/cli.md b/docs/reference/cli.md index eaed15c..22721e9 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -18,6 +18,8 @@ is a subcommand of `openadapt flow`. | [`compile`](#compile) | Compile a recording into a workflow bundle | 0 | | [`induce`](#induce) | Induce a parameterized program from **multiple** recordings | 0 if certified, 2 if underdetermined | | [`for-each`](#for-each) | Author a data-driven **loop** bundle: run one demonstration once per worklist record | 0 on success, nonzero on a mapping error | +| [`compose`](#compose) | Author a parent sequencer from compiled child bundles and a handoff contract | 0 on success, nonzero on an authoring refusal | +| [`process`](#process) | Author a ProcessContract parent over independently admitted capabilities | 0 on success, nonzero on an authoring refusal | | [`replay`](#replay) | Replay a bundle, locally and deterministically | 0 on success, 1 on failure | | [`run`](#run) | Execute a bundle through the regulated admission gate | 0 success, 1 execution halt, 2 refusal | | [`resume`](#resume) | Resume a durably-paused run from its last checkpoint | 0 on success, 1/3 otherwise | @@ -33,7 +35,7 @@ is a subcommand of `openadapt flow`. | [`push`](#push) | Explicitly upload a recording or bundle to a control plane | 0/1 | | [`validate-hosted`](#validate-hosted) | Bind local validation evidence to a one-time hosted challenge | 0/1 | | [`report-break`](#report-break) | Send a scrubbed, schema-minimized halt descriptor | 0/1 | -| [`visualize`](#visualize) | Render a bundle's program graph (steps, ladder, gates, halts) | 0 | +| [`visualize`](#visualize) | Render a compiled bundle, a compose parent, or a process parent | 0 | | [`bench`](#bench) | Replay a bundle N times against the sample app and aggregate | 0 if all pass | | [`benchmark`](#benchmark) | Compare compiled replay vs a computer-use agent | 0 | | [`emit-skill`](#emit) | Emit an Agent Skills folder for a bundle | 0 | @@ -201,13 +203,83 @@ no demonstrated default, a ragged worklist, or a worklist longer than the bound all **fail loudly** and write no bundle. Once authored, drive the loop with [`replay --worklist`](#replay) or [`run --worklist`](#run). +## compose + +Author a parent sequencer from two or more compiled child bundles and a handoff +contract. Each child stays bound to the surface you recorded it on. The output +is a directory with `composition.json` (`openadapt.composition/v1`) plus copied +children. See +[Sequence work across two applications](../guides/compose-multi-application.md). + +```bash +openadapt flow compose \ + --child intake=./intake-bundle \ + --child posting=./posting-bundle \ + --handoff intake.patient_id=posting.patient_id \ + --out composed +``` + +| Argument / flag | Description | +|---|---| +| `--child NAME=PATH` | Named compiled child bundle (repeat; at least two). | +| `--handoff FROM.source=TO.target` | Copy a predecessor's effect-bound parameter into a successor parameter. Repeatable. Missing evidence stops the run. | +| `--after NAME=PRED[,PRED]` | Explicit DAG predecessors for NAME. Omit to run children in `--child` order. | +| `--allow-halt NAME=OUTCOME` | Let NAME start when a predecessor ended OUTCOME instead of `VERIFIED`. `VERIFIED` is always allowed. | +| `--out` (required) | Output composition directory | +| `--name` | Composition name | + +`certify` and `run` execute this directory. Each child keeps its recorded +surface. `replay` refuses a composition artifact; use `run`. A single child, a +cycle, an unknown handoff target, and a source that is not effect-bound are +refused at authoring. + +## process + +Author a ProcessContract parent over independently admitted capabilities. +Schema `openadapt.process-contract/v0`. `--child NAME=BUNDLE` is the admitted +bundle. `--admission NAME=ENVELOPE` is that child's signed +`openadapt.qualification-admission/v1` file. A compose child under +`composition.json` is not an envelope. The parent points at those admissions. +It doesn't copy recordings. See +[Process contracts](../concepts/process-contract.md). + +```bash +openadapt flow process \ + --child intake=./intake-bundle \ + --admission intake=./intake-admission.json \ + --child posting=./posting-bundle \ + --admission posting=./posting-admission.json \ + --handoff intake.patient_id=posting.patient_id \ + --out process-parent +``` + +| Argument / flag | Description | +|---|---| +| `--child NAME=BUNDLE` | Named admitted child bundle (repeat; at least two). | +| `--admission NAME=ENVELOPE` | Signed qualification-admission file for NAME. Required for every `--child`. | +| `--handoff FROM.source=TO.target` | Copy a predecessor's effect-bound parameter into a successor parameter. Repeatable. Missing evidence stops the run. | +| `--after NAME=PRED[,PRED]` | Explicit DAG predecessors for NAME. Omit to run children in `--child` order. | +| `--allow-halt NAME=OUTCOME` | Let NAME start when a predecessor ended OUTCOME instead of `VERIFIED`. `VERIFIED` is always allowed. | +| `--out` (required) | Output process-contract directory | +| `--name` | Process name | + +`--handoff`, `--after`, and `--allow-halt` have the same shape as +[`compose`](#compose). Pointing `process` at a `composition.json` directory is +refused. Each child runs through +[OpenAdapt Execute](../commercial/execute-api.md) with that child's envelope. +`replay` of the process parent is refused. `visualize` on the directory shows +admitted children, handoff edges, and a terminal labeled End of declared +steps. + ## replay Replay a bundle against the substrate chosen by the [backend selector](#backend). On the default `web` backend, `--url` names the target app and, with no `--url`, replay serves the bundled sample app. For Windows, macOS, Linux, RDP, or Citrix, select its backend and exact target flags instead of `--url`. The example below -replays the web substrate. +replays the web substrate. `replay` refuses a compose parent and a +ProcessContract parent; composed recordings run with [`run`](#run), and process +children run through [Execute](../commercial/execute-api.md). ```bash openadapt flow replay bundle --url https://your.app --param note="Follow-up" @@ -252,8 +324,9 @@ The same executor as [`replay`](#replay), behind a regulated admission gate: the bundle must pass policy, identity coverage, effect coverage, approval, encryption, and manifest-integrity checks before any action executes. Backend, effect verification, API actuation, durable runtime, and policy come from -`--config`. The demo-only `--drift` teaching aid is not offered here. See -[Run a deployment](../guides/run-a-deployment.md). +`--config`. The demo-only `--drift` teaching aid is not offered here. If the +path is a compose parent, `run` executes each child in order under the handoff +contract. See [Run a deployment](../guides/run-a-deployment.md). ```bash openadapt flow run bundle --config deployment.yaml @@ -261,7 +334,7 @@ openadapt flow run bundle --config deployment.yaml | Flag | Description | |---|---| -| `bundle` (positional) | Workflow bundle directory | +| `bundle` (positional) | Workflow bundle directory, or a compose parent directory | | `--url` | Target app URL (default: `backend.url` from `--config`) | | `--run-dir` | Run output directory (default `runs/replay-`) | | `--param K=V` | Parameter substitution. Repeatable. | @@ -386,12 +459,13 @@ openadapt flow certify bundle --config deployment.yaml | Flag | Description | |---|---| -| `bundle` (positional) | Workflow bundle directory | +| `bundle` (positional) | Workflow bundle directory, or a compose parent directory | | `--policy` | Policy YAML path, or a built-in name (`permissive`, `clinical-write`). Defaults to `policy.policy` from `--config`. | | `--config YAML` | [Deployment config](deployment-config.md) to read the policy from when `--policy` is omitted, so one file both certifies and runs the bundle | Provide `--policy` or a `--config` that sets `policy.policy`; certify errors if -neither supplies a policy. Exits 2 when the bundle fails certification — the +neither supplies a policy. If the path is a compose parent, certify evaluates +the policy on each child. Exits 2 when the bundle fails certification: the gate refusing an unsafe bundle, not an error in your setup ([exit codes](run-outcomes.md#cli-exit-codes)). @@ -679,28 +753,35 @@ sanitation protocol, and destination-aware boundary. ## visualize See what a demonstration compiled **into**, before it runs. `visualize` reads a -bundle and renders its program graph: the ordered steps, the resolution ladder -each step will try, where an identity gate is armed, which writes carry an effect -check, and every point the run can halt. It writes one of three formats from the -same graph spec, so the CLI, Cloud, and desktop surfaces all show the same thing. -See [Visualize a compiled program](../concepts/program-visualizer.md). +compiled bundle and renders its program graph: the ordered steps, the resolution +ladder each step will try, where an identity gate is armed, which writes carry +an effect check, and every point the run can halt. Point it at a compose +directory (`composition.json`) and it shows child bundles plus handoff edges. +Point it at a process-contract directory and it shows admitted children plus +handoff edges. The parent terminal is labeled End of declared steps. That isn't +`VERIFIED`. See +[Read a compiled program](../concepts/program-visualizer.md). ```bash openadapt flow visualize bundle -o graph.html # self-contained page openadapt flow visualize bundle --profile remote-safe -o review.html openadapt flow visualize bundle --format mermaid # flowchart source, to stdout openadapt flow visualize bundle --format json # the shared graph spec +openadapt flow visualize composed -o composed.html +openadapt flow visualize process-parent -o process.html ``` | Flag | Description | |---|---| -| `bundle` (positional) | Workflow bundle directory | +| `bundle` (positional) | Workflow bundle directory, compose parent directory, or process-contract directory | | `--format {html,mermaid,json}` | `html` (default): a self-contained, offline-openable page. `mermaid`: flowchart source for Markdown and docs. `json`: the shared program-graph spec every surface renders. | | `--profile {operator-local,remote-safe,public-synthetic,sanitized-derivative}` | Select the fields allowed in the output. Non-local profiles keep topology and remove recorded values, target text, selectors, URLs, guard text, and local provenance. A projection does not sanitize the source bundle. | | `-o`, `--out FILE` | Write to a file instead of stdout (parent directories are created) | Reading is offline and side-effect-free: `visualize` never runs the workflow, so -it is safe to point at any bundle, including one that would refuse to certify. +it is safe to point at any bundle, including one that would refuse to certify. A +compose parent and a process parent aren't one ProgramGraph; open a child +bundle for that child's steps. ## bench diff --git a/mkdocs.yml b/mkdocs.yml index d0de22c..f76d0ef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -140,10 +140,12 @@ nav: - Multi-trace induction: concepts/multi-trace-induction.md - Run a workflow for each record: guides/data-driven-loops.md - Visualize a compiled program: concepts/program-visualizer.md + - A task that spans two apps: guides/compose-multi-application.md - Parameters and secrets: guides/parameters-and-secrets.md - The capability ladder: concepts/capability-ladder.md - Write and enforce a policy: guides/policy-and-certification.md - Qualify a workflow: guides/qualify-a-workflow.md + - Process contracts: concepts/process-contract.md - Policy and certify: concepts/policy-and-certify.md - Effect verification: concepts/effect-verification.md - The identity gate: concepts/identity-gate.md