diff --git a/docs/assets/screenshots/PROVENANCE.txt b/docs/assets/screenshots/PROVENANCE.txt
index c6e0038..11c63fb 100644
--- a/docs/assets/screenshots/PROVENANCE.txt
+++ b/docs/assets/screenshots/PROVENANCE.txt
@@ -2,11 +2,18 @@ Screenshot provenance
=====================
All screenshots below are rendered from OpenAdapt's own tooling in local /
-mock mode. None are mocked-up or hand-drawn. Each capture that shows product
-chrome is a byte-identical copy of a file in OpenAdaptAI/openadapt-web, which
-owns the published visual record for every OpenAdapt surface;
+mock mode. None are mocked-up or hand-drawn.
+
+Most captures that show product chrome are byte-identical copies of a file in
+OpenAdaptAI/openadapt-web, which owns the published visual record. The four
+program-view captures are the exception: openadapt-web publishes those surfaces
+as live components rather than as stills, so these are captured here and their
+entry names the upstream repository and commit instead.
+
docs/assets/visual-palette.json records the measured dominant colours of every
-image on this site.
+image on this site, and tests/test_design_tokens.py refuses a warm ground. An
+image on a retired palette is not something a reader can see, so a capture from
+a build that predates the palette move is caught by measurement or not at all.
program-graph-cloud.png
The compiled-program visualizer in the OpenAdapt Cloud workspace
@@ -18,6 +25,36 @@ program-graph-cli.png
visualizer) for the OpenEMR showcase bundle, headless-Chrome capture of the
self-contained page. Used on concepts/program-visualizer.md.
+program-state-live-relation.png
+ The public synthetic Program State Console in openadapt-web at the final
+ event of its exact retained trace. It compares synthetic recorded and current
+ frames. Used on concepts/program-visualizer.md. SHA-256:
+ 676f0e57c4a19a490d4c9ee9bfccaecfb44dfe99d83febf24fc4f00c2fc1ae13.
+
+program-workbench-cloud.png
+ The public-safe Program map in openadapt-cloud local mock mode. The graph
+ contains exact emitted edges and generic synthetic labels. Used on
+ concepts/program-visualizer.md. SHA-256:
+ 0f77308464f618efcb04814b411eb322af779699d33298ea9f0e57f7edab6d6e.
+
+program-workbench-cli.png
+ A public-synthetic openadapt-flow HTML export for a loop program. The page is
+ self-contained and was captured in a local browser. Used on
+ concepts/program-visualizer.md. SHA-256:
+ aa5ff1e1455b0444f234f398e3f645490b974c3b48c5e38156fd041964fec4d2.
+
+program-workbench-desktop.png
+ The development-only OpenAdapt Desktop preview that renders the production
+ qualification workbench with a public synthetic graph. It has no bound live
+ trace. Captured at 1440x1000 with Playwright Chromium from
+ http://localhost:1421/?surface=program-workbench-preview, against `npm run
+ dev` in OpenAdaptAI/openadapt-desktop at exact commit
+ ebc10b037533f7adc0b12e0664d718d373245108. That is the first commit carrying
+ the canonical design tokens, which matters: the first version of this capture
+ came from a build before that merge and grounded on the retired #f2f1ed.
+ Used on concepts/program-visualizer.md. SHA-256:
+ 4fbc62dd9ee5b48f758aca0ed5a13e072b3e0a4b6fc391847202297673f12e3f.
+
workflow-catalog.png
The workflow catalog and step-level halt map in the OpenAdapt Cloud
workspace, headless-Chrome capture in local mock mode. Used on
@@ -36,11 +73,11 @@ settings-governance.png
dashboard-overview.png
The /dashboard overview of the OpenAdapt Cloud workspace at exact commit
- 021a9caf99a6832bff9f32e14b1b34d42ec2e290, captured at 2560x1440 with
+ ee84df868c56f099f14c3c5cb39c1eef724ba762, captured at 2560x1440 with
Playwright Chromium through openadapt-cloud scripts/capture-public-previews.mjs
against the local Cloud mock UI. The runtime-mode banner and the account chip
are hidden during capture because both report the environment the capture ran
in, not the product. The fixture is synthetic OpenEMR patient registration.
Byte-identical source: OpenAdaptAI/openadapt-web,
public/product-preview/dashboard-workflows.png. SHA-256:
- f9f894a60e44b266f16b8d138d8747e1a1fb639bf92370db6adbf4d4aab5bde4.
+ d93431712b5b36addf77fb5600e8b5f208291b80776940c1c76f9c7b4f6b4679.
diff --git a/docs/assets/screenshots/dashboard-overview.png b/docs/assets/screenshots/dashboard-overview.png
index d2e1e14..f7c81e7 100644
Binary files a/docs/assets/screenshots/dashboard-overview.png and b/docs/assets/screenshots/dashboard-overview.png differ
diff --git a/docs/assets/screenshots/program-state-live-relation.png b/docs/assets/screenshots/program-state-live-relation.png
new file mode 100644
index 0000000..b647069
Binary files /dev/null and b/docs/assets/screenshots/program-state-live-relation.png differ
diff --git a/docs/assets/screenshots/program-workbench-cli.png b/docs/assets/screenshots/program-workbench-cli.png
new file mode 100644
index 0000000..4b81c34
Binary files /dev/null and b/docs/assets/screenshots/program-workbench-cli.png differ
diff --git a/docs/assets/screenshots/program-workbench-cloud.png b/docs/assets/screenshots/program-workbench-cloud.png
new file mode 100644
index 0000000..c26c02c
Binary files /dev/null and b/docs/assets/screenshots/program-workbench-cloud.png differ
diff --git a/docs/assets/screenshots/program-workbench-desktop.png b/docs/assets/screenshots/program-workbench-desktop.png
new file mode 100644
index 0000000..828af9f
Binary files /dev/null and b/docs/assets/screenshots/program-workbench-desktop.png differ
diff --git a/docs/assets/visual-palette.json b/docs/assets/visual-palette.json
index 2c135c4..8273f75 100644
--- a/docs/assets/visual-palette.json
+++ b/docs/assets/visual-palette.json
@@ -28,13 +28,13 @@
"screenshots/dashboard-overview.png": {
"source_repository": "OpenAdaptAI/openadapt-web",
"source_path": "public/product-preview/dashboard-workflows.png",
- "sha256": "f9f894a60e44b266f16b8d138d8747e1a1fb639bf92370db6adbf4d4aab5bde4",
+ "sha256": "d93431712b5b36addf77fb5600e8b5f208291b80776940c1c76f9c7b4f6b4679",
"width": 2560,
"height": 1440,
"dominant_colors": [
{
"hex": "#ffffff",
- "share_percent": 46.48
+ "share_percent": 46.49
},
{
"hex": "#f5f7fa",
@@ -216,6 +216,94 @@
"share_percent": 11.5
}
]
+ },
+ "screenshots/program-state-live-relation.png": {
+ "source_repository": "OpenAdaptAI/openadapt-web",
+ "source_path": null,
+ "note": "The public-synthetic Program State Console, captured for these docs rather than vendored: openadapt-web publishes the component, not a still of it. The console is built from the canonical tokens, so its ground is cool by construction.",
+ "sha256": "676f0e57c4a19a490d4c9ee9bfccaecfb44dfe99d83febf24fc4f00c2fc1ae13",
+ "width": 1440,
+ "height": 1000,
+ "dominant_colors": [
+ {
+ "hex": "#f6f7fb",
+ "share_percent": 23.08
+ },
+ {
+ "hex": "#f9fafc",
+ "share_percent": 7.55
+ },
+ {
+ "hex": "#0c1220",
+ "share_percent": 3.92
+ }
+ ]
+ },
+ "screenshots/program-workbench-cloud.png": {
+ "source_repository": "OpenAdaptAI/openadapt-cloud",
+ "source_path": null,
+ "note": "The remote-safe Program map in Cloud local mock mode, captured for these docs. The workbench canvas is the canonical dark inset, which is why the dominant colour is #0c1220 rather than a page ground.",
+ "sha256": "0f77308464f618efcb04814b411eb322af779699d33298ea9f0e57f7edab6d6e",
+ "width": 1280,
+ "height": 720,
+ "dominant_colors": [
+ {
+ "hex": "#0c1220",
+ "share_percent": 19.7
+ },
+ {
+ "hex": "#f6f7fb",
+ "share_percent": 13.78
+ },
+ {
+ "hex": "#121a27",
+ "share_percent": 11.0
+ }
+ ]
+ },
+ "screenshots/program-workbench-cli.png": {
+ "source_repository": "OpenAdaptAI/openadapt-flow",
+ "source_path": null,
+ "note": "A self-contained `openadapt flow visualize` HTML export. It is generated output rather than product chrome, and its ground is plain white.",
+ "sha256": "aa5ff1e1455b0444f234f398e3f645490b974c3b48c5e38156fd041964fec4d2",
+ "width": 1280,
+ "height": 720,
+ "dominant_colors": [
+ {
+ "hex": "#ffffff",
+ "share_percent": 26.46
+ },
+ {
+ "hex": "#0c1220",
+ "share_percent": 14.34
+ },
+ {
+ "hex": "#131b28",
+ "share_percent": 9.43
+ }
+ ]
+ },
+ "screenshots/program-workbench-desktop.png": {
+ "source_repository": "OpenAdaptAI/openadapt-desktop",
+ "source_path": null,
+ "note": "The development-only qualification workbench preview at ?surface=program-workbench-preview, captured at 1440x1000 from openadapt-desktop ebc10b037533f7adc0b12e0664d718d373245108, which is the first commit carrying the canonical tokens (its #139). The first capture came from a build before that merge and grounded on the retired #f2f1ed.",
+ "sha256": "4fbc62dd9ee5b48f758aca0ed5a13e072b3e0a4b6fc391847202297673f12e3f",
+ "width": 1440,
+ "height": 1000,
+ "dominant_colors": [
+ {
+ "hex": "#f5f7fa",
+ "share_percent": 35.05
+ },
+ {
+ "hex": "#ffffff",
+ "share_percent": 13.71
+ },
+ {
+ "hex": "#0b1220",
+ "share_percent": 11.29
+ }
+ ]
}
}
}
diff --git a/docs/concepts/program-visualizer.md b/docs/concepts/program-visualizer.md
index c8483ce..14c6de2 100644
--- a/docs/concepts/program-visualizer.md
+++ b/docs/concepts/program-visualizer.md
@@ -1,61 +1,193 @@
-# The program visualizer
+# Read a compiled program and its live evidence
-A compiled bundle is a program, not a recording. Before you trust it in a
-deployment, read it: what steps it takes, how it finds each target, where it
-refuses, where it can stop. The visualizer renders that graph from the bundle
-itself, so what you review is the artifact that actually runs.
+A compiled bundle is a program, not a video. Before you approve it, inspect the
+steps it can take, the evidence each action needs, and the paths that stop the
+run. During a run, inspect a second object: the exact program occurrence and the
+current evidence that the runtime bound to it.
-## What the graph shows
+
+ { width="1180" }
+ The public synthetic console plays one retained event trace. The program stays fixed while the current observation, target resolution, identity check, action, screen check, and effect check advance.
+
-For each step the visualizer surfaces the parts that decide whether a run is safe:
+## Two timelines, four views
-- **Steps, in order.** The action at each step and the target it acts on.
-- **The resolution ladder.** The ordered rungs a step tries to re-find its
- target at replay time (structural element match, template, OCR, landmark
- geometry, and optionally a grounding model). The ladder shows how much drift a
- step can absorb before it halts. See
- [the capability ladder](capability-ladder.md).
-- **Armed identity gates.** Which steps re-verify identity before acting, and
- which do not. An unarmed step has no identity check at all, and the graph
- shows that rather than leaving it implied. See
- [the identity gate](identity-gate.md).
-- **Effect checks.** Which writes carry a typed effect verified against the
- system of record. See [effect verification](effect-verification.md).
-- **Halt points.** Every place the run may stop: a low-confidence match on an
- irreversible step, a failed postcondition, a refuted write, an ambiguous
- target. This is the run's stop map.
+The workbench keeps two timelines separate.
-For a program bundle it also shows the structure the linear case hides: loops
-and their bounds, guarded transitions, and exception paths.
+1. The **program artifact** moves from recorded evidence to a compiled version.
+ A repair creates a new candidate version. It doesn't change the version that
+ is running.
+2. **One live run** moves through current observation, target resolution,
+ identity, action, and verification. Each event has an exact sequence number.
-## One graph, three renderings
+The four views answer different questions:
-The CLI reads the bundle and emits one of three formats from the same
-[graph spec](../reference/bundle-format.md):
+| View | Question | Source |
+|---|---|---|
+| Program map | What can this version do? | Compiled nodes and exact edges |
+| Live relation | How does the current observation relate to the retained evidence? | One frame-bound runtime occurrence |
+| Evidence lanes | Which target, identity, screen, and effect controls exist or passed? | Declared contract or exact run verdict |
+| Repair | What would change in a new version? | A separate candidate and its review state |
+
+Do not combine a declared control with a live verdict. A compiled node can say
+that an identity gate is armed. Only a bound run event can say that the current
+identity passed.
+
+## Read the program map
+
+The program map follows the emitted edge targets. It does not arrange the nodes
+as if the bundle were a linear list. Branches, loop returns, exception paths,
+and terminal states remain visible.
+
+For each node, inspect these lanes:
+
+- **Target evidence.** The retained structural locator, template, OCR anchor,
+ relational landmarks, or geometry that the resolver may use.
+- **Identity.** The pre-action identity contract and whether it is armed.
+- **Screen check.** The postcondition that the runtime checks after the action.
+- **Effect check.** The independent system-of-record check for a declared
+ business effect.
+- **Stop rules.** The conditions that make the runtime halt before or after a
+ delivery boundary.
+
+
+ { width="1180" }
+ Cloud shows the shared graph projection. Select a node to inspect its declared resolution, identity, screen, and effect lanes.
+
+
+A terminal node named `End of declared steps` means that graph traversal ended.
+It does not prove `VERIFIED`. The run outcome still depends on the exact
+authorization, identity, postcondition, and effect evidence required by the
+execution profile.
+
+## Follow one live occurrence
+
+The same program node can run more than once inside a loop. A useful live view
+must identify one occurrence. It should retain the graph id, state id, program
+scope, incoming edge, and event sequence when the runtime provides them. The UI
+must not identify an occurrence from a row number or animation time.
+
+The current marker advances only when the runtime emits an event. Controlled
+playback is useful for review, but it must replay the retained sequence. It must
+not infer a missing phase.
+
+The comparison view can show:
+
+- the retained recorded frame and its asset reference;
+- the exact current frame and decoded frame index;
+- typed facts such as role, text, layout delta, candidate count, and identity;
+- the resolver decision and the evidence rung that supported it;
+- the exact target rectangle when the runtime bound it to the displayed frame;
+- the post-action screen check and independent effect verdict.
+
+If the runtime did not retain a frame binding, show the status without a target
+rectangle. Do not carry a rectangle forward, interpolate it, or rebuild it from
+a selector in the viewer.
+
+## Show disagreement without hiding it
+
+A single similarity score is not enough. It can hide a disagreement that
+matters. Show the typed facts that drove the decision.
+
+| Difference | Useful display | Safe runtime meaning |
+|---|---|---|
+| Layout moved | Recorded and current rectangles, plus the measured delta | The target can still resolve if retained evidence supports one current candidate |
+| Text changed | Recorded text, current text, and match verdict | Continue only if the declared text rule passes |
+| Role changed | Recorded and current structural role | Treat a required role mismatch as refuted evidence |
+| Multiple candidates | Candidate count and ambiguity reason | Halt before action |
+| Identity conflict | Bound identity signals and their verdicts | Halt before action |
+| Fresh frame changed | Pre-action observation and revalidation verdict | Halt or reconcile according to the delivery state |
+| Effect is uncertain | Independent effect evidence and transaction outcome | Return `RECONCILIATION_REQUIRED`; never retry blindly |
+
+The viewer can use a neutral difference overlay for review. It cannot turn a
+pixel difference into target resolution, identity proof, or effect proof.
+
+## Keep repair separate from execution
+
+A halted run can retain useful repair evidence. A repair view should show the
+source version, the proposed locator or contract change, the changed nodes and
+edges, the reason for the proposal, and the trials required before promotion.
+
+The proposal is a new artifact. It does not edit the running version. Review,
+qualification, and admission remain separate steps. If a repair candidate is
+not bound to the exact halt evidence, the UI should not present it as the fix
+for that run.
+
+## Use the right projection
+
+OpenAdapt uses the same graph contract across its surfaces, but each surface
+has a different data boundary.
+
+
+ { width="1180" }
+ Desktop can show local operator detail. It says when no exact live trace is bound, so a static qualification contract cannot look like runtime evidence.
+
+
+| Surface | Projection | Intended use |
+|---|---|---|
+| Desktop | `operator-local` | Local qualification and private evidence review |
+| Cloud | `remote-safe` | Tenant-bound topology and reviewed remote-safe labels |
+| Website | `public-synthetic` | Interactive product explanation with synthetic evidence |
+| Exported report | `sanitized-derivative` | Hash-bound review artifact with an explicit egress decision |
+
+A projection removes fields from a view. It does not sanitize the source bundle.
+Keep the original inside its trusted boundary.
+
+## Render the graph offline
+
+The CLI reads the bundle and writes HTML, Mermaid, or JSON from the same graph
+specification. HTML is self-contained. It uses a deterministic edge layout and
+does not load a graph library or a network asset.
```bash
-openadapt flow visualize bundle -o graph.html # self-contained page
-openadapt flow visualize bundle --format mermaid # flowchart source
-openadapt flow visualize bundle --format json # the shared graph spec
+openadapt flow visualize bundle -o graph.html
+openadapt flow visualize bundle --profile remote-safe -o review.html
+openadapt flow visualize bundle --profile public-synthetic -o public.html
+openadapt flow visualize bundle --format mermaid
+openadapt flow visualize bundle --format json
```
-- **HTML** is a self-contained page you can open offline and hand to a reviewer.
-- **Mermaid** is flowchart source you can paste into a Markdown file or design
- doc.
-- **JSON** is the shared program-graph spec. The Cloud and desktop surfaces
- render the same spec, so a reviewer in Cloud and an engineer at the CLI see
- the same graph.
-
-## Reading is safe
+
+ { width="1180" }
+ The offline view keeps the topology and declared controls. A non-local profile removes recorded values, target text, selectors, URLs, guard text, and local provenance.
+
`visualize` never runs the workflow. It reads the bundle and describes it with
-no side effects, so you can point it at any bundle, including one that would
-refuse to certify. That is the intended order: visualize the program,
-[lint](policy-and-certify.md) its coverage gaps, certify it against a policy,
-then run it.
+no side effects. You can inspect a bundle that will later refuse certification.
+The normal order is to visualize the program, lint its contract, qualify the
+exact version, and then run it.
+
+## Rendering choices
+
+The current renderer uses semantic HTML for nodes, SVG for exact edges, and a
+small deterministic layout function. The website uses controlled CSS motion for
+its retained event playback. This keeps the offline export self-contained and
+keeps every node available to a screen reader.
+
+| Option | Strength | Cost or limit | Decision |
+|---|---|---|---|
+| HTML, SVG, and local layout | Small payload, offline output, accessible nodes, exact edge control | The layout code must handle each supported graph form | Use now |
+| React Flow | Strong pan, zoom, selection, and future editing tools | Adds a client dependency and editor concepts that a read-only review does not need | Consider for a future repair editor |
+| ELK or Dagre | Better automatic layout for large nested graphs | Adds bundle weight and can move nodes between releases when layout settings change | Add when real graphs exceed the local layout limits |
+| Mermaid | Easy export into documents | Limited evidence inspector and live-state interaction | Keep as an export format |
+| Canvas or WebGL | Handles very large graphs | Weaker native accessibility and more work for text selection and printing | Do not use for the normal review surface |
+| A pixel-difference library | Useful local review overlay | A difference score is not resolution, identity, or effect evidence | Permit only as a local review aid |
+| A motion library | Useful for complex transitions | More runtime code; animation time can be mistaken for event time | Use controlled CSS motion until a real need appears |
+
+The layout switch point should come from measured graph size and interaction
+latency. It should not come from the wish to make the graph look more animated.
+
+## Visual review checklist
-## In Cloud
+Before you approve a program or a run view, check these items:
-The same graph appears in the Cloud workspace on a workflow's page, so a
-reviewer who never touches the CLI can read the compiled program, its gates, and
-its halt points before approving a version for deployment.
+- Every displayed connection comes from an emitted edge.
+- Every live state comes from an exact runtime event.
+- A loop occurrence has enough scope data to distinguish it from another pass.
+- Recorded and current media have explicit asset and frame references.
+- The target rectangle is bound to the displayed frame or is absent.
+- A declared gate does not appear as a passed gate.
+- A halted run shows whether delivery was attempted or remains unknown.
+- A repair appears as a candidate version with a review path.
+- A terminal graph state does not appear as a verified business outcome.
+- A remote or public view contains only fields allowed by its projection.
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index a8648fd..42b977f 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -687,6 +687,7 @@ See [Visualize 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
```
@@ -695,6 +696,7 @@ openadapt flow visualize bundle --format json # the shared graph spec
|---|---|
| `bundle` (positional) | Workflow bundle 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