Skip to content
Merged
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
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ and confirms the write by querying the record store out of band:

```
[1/5] Record the demonstration against a real persistence boundary
[2/5] Compile, mining the effect contract from the observed delta
2 system-of-record effect(s) derived from the demonstration's record delta on step_005
[2/5] Compile and propose a fixture-specific effect contract
2 system-of-record effect proposal(s) derived from the fixture's record delta on step_005; review is required for a real deployment
[3/5] Certify against the clinical-write policy
[4/5] Admit and execute under the standard profile
VERIFIED in 4.1s; 0 model calls; the system of record holds 1 record(s)
Expand All @@ -59,11 +59,16 @@ VERIFIED: <out>/run/REPORT.md
metering class billable (this local tutorial was not reported or charged)
profile standard
model calls 0
effects 2/2 confirmed at evidence tier 1 (independent system of record)
effects 2/2 confirmed by an independent system-of-record read (Seal Oracle tier 2)
```

That's real output from 1.34.0, run on macOS on 2026-08-28, with the run
directory shortened and the receipt paths cut. Now break it on purpose:
That output comes from the bundled MockMed fixture. Its compiler can propose
an effect contract because the fixture exposes an observed record delta. A
real deployment must review the proposed contract or declare it directly, then
configure the independent verifier described in
[`docs/EFFECT_KIT.md`](docs/EFFECT_KIT.md). The sample run used version 1.34.0
on macOS on 2026-08-28. The path is shorter here, and the receipt paths are
not shown. Now break it on purpose:

```bash
openadapt-flow tutorial --break-it
Expand Down Expand Up @@ -479,10 +484,15 @@ episode instead. This package keeps the worker and the HTTP client. See
```bash
git clone https://github.com/OpenAdaptAI/openadapt-flow && cd openadapt-flow
pip install -e '.[dev]'
playwright install chromium # optional; otherwise downloaded on first launch
python -m playwright install chromium # optional browser pre-provisioning
pytest -q
```

On Linux, Flow checks the Chromium host libraries before an automatic browser
download. A minimal host may need a one-time system-library install. Flow stops
before the download and prints a command for the exact Python environment that
runs it.

Contributions welcome, see [CONTRIBUTING.md](CONTRIBUTING.md). If you want a
first one that is genuinely useful: pick a module off the mypy type-debt
burn-down list (`[[tool.mypy.overrides]]` in `pyproject.toml`), tighten its
Expand Down
31 changes: 18 additions & 13 deletions docs/EFFECT_KIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ from the reference apps.
`deployment.yaml` wires one `EffectVerifier` (REST / GraphQL / FHIR / SQL /
file / email / document / document-hash, or a registered plugin adapter)
plus its secret-isolated auth. When more than one reviewed read boundary is
available, `candidates:` selects the strongest evidence tier for each resolved
effect before input. It does not downgrade after input. An unavailable
available, `candidates:` selects the strongest verification class for each
resolved effect before input. It does not downgrade after input. An unavailable
selected proof halts or enters reconciliation.
3. **The runtime refuses to guess.** Every verdict is CONFIRMED / REFUTED /
INDETERMINATE; both non-confirmed verdicts HALT. A step that declares
Expand Down Expand Up @@ -175,8 +175,8 @@ resume, attended qualified read-back) refuse it rather than judge it against a
synthesized empty baseline.

**Backward compatibility and the honest boundary.** This kind is **additive
and opt-in**. Flow contracts are operator-authored — there is no derivation
step that could turn the guard on for you — so every contract written before
and opt-in**. Flow contracts are operator-authored. There is no derivation
step that can turn the guard on for you. Thus, every contract written before
this option judges **exactly** as it did before, and its `contract_hash` is
byte-identical (the new fields enter the digest only on the new kind).
The boundary follows directly: **an existing contract does not detect an
Expand Down Expand Up @@ -250,24 +250,29 @@ screen read-back as proof of a consequential write.

For more than one reviewed boundary, use `effects.candidates` instead of
`effects.kind`. Each candidate has the normal `EffectsConfig` fields. Flow
constructs every candidate before actuation, then selects the lowest numeric
`VerificationTier` for each resolved effect; declaration order resolves a tie.
This makes the choice deterministic and reviewable. A missing secret, an
invalid config, or an invalid plugin tier refuses the run before input. The
on-screen candidate is tier 3 only for that exact effect when its read-back
reopens persisted state through a different path. It is tier 4 for a
same-surface read-back. After the action, Flow does not fall back to a weaker
constructs every candidate before actuation, then selects the strongest
`VerificationTier` enum member for each resolved effect; declaration order
resolves a tie. `VerificationTier` is a persisted Flow v1 implementation field.
Do not present its numeric value as a Seal Oracle tier. Public receipts use one
ladder: Oracle tier 0 is visual evidence, tier 1 is a separate read-only
session, tier 2 is a system-of-record read, and tier 3 is a counterparty
acknowledgment. This makes the choice deterministic and reviewable. A missing
secret, invalid config, or invalid plugin verification class refuses the run
before input. The on-screen candidate uses
`PERSISTED_STATE_REACQUISITION` only for that exact effect when its read-back
reopens persisted state through a different path. It uses `IMMEDIATE_SCREEN`
for a same-surface read-back. After the action, Flow does not fall back to a weaker
candidate if the selected verifier is unavailable. It records the unavailable
proof and halts or creates the normal reconciliation task.

```yaml
effects:
candidates:
- kind: document # independent export arrival (tier 1)
- kind: document # independent system-of-record export
root: /secure/exports
file_pattern: "confirmation-*.json"
document_format: json
- kind: onscreen # lower-tier persisted-state read-back
- kind: onscreen # persisted-state read-back
```

The single `kind:` form remains the recommended configuration when one
Expand Down
13 changes: 8 additions & 5 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,14 @@ one-command rollback). See [`REPAIR_LIFECYCLE.md`](REPAIR_LIFECYCLE.md).

The base `openadapt-flow` package stays lightweight for native desktop, RDP,
and Citrix runners. The `browser` extra adds Playwright only for web workflows;
the first browser command then downloads its matching Chromium build once
(about 150 MB), with no separate `playwright install chromium` step. Prefer the
canonical `pip install 'openadapt[browser]'` launcher path for normal use. In
air-gapped or CI environments that pre-provision the browser, set
`OPENADAPT_FLOW_NO_AUTO_INSTALL=1` to disable the auto-download.
the first browser command checks its Linux host libraries and then downloads
the matching Chromium build once (about 150 MB). A minimal Linux host may need
Playwright's one-time system-library install. Flow stops before the browser
download and prints a command bound to the exact Python environment that runs
it. Playwright requests administrator access if the system package manager
needs it. Prefer the canonical `pip install 'openadapt[browser]'` launcher path
for normal use. In air-gapped or CI environments that pre-provision the
browser, set `OPENADAPT_FLOW_NO_AUTO_INSTALL=1` to disable the auto-download.

The weekly clean-machine test runs this complete install-to-uninstall journey
on Linux, macOS, and Windows. See the
Expand Down
14 changes: 12 additions & 2 deletions openadapt_flow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1380,10 +1380,20 @@ def _cmd_tutorial(args: argparse.Namespace) -> int:
print(f" metering class {metering_class} ({local_charge})")
print(f" profile {result.execution_profile}")
print(f" model calls {result.model_calls}")
from openadapt_flow.verification import oracle_tier_from_verification_tier

effect_summary = (
"no qualifying independent proof"
if result.effect_tier is None
else (
"confirmed by an independent system-of-record read "
f"(Seal Oracle tier "
f"{oracle_tier_from_verification_tier(result.effect_tier)})"
)
)
print(
f" effects {result.effects_confirmed}/{result.effects_required} "
f"confirmed at evidence tier {result.effect_tier} "
"(independent system of record)"
f"{effect_summary}"
)
print(f" bundle digest {result.bundle_digest}")
if result.receipt_paths:
Expand Down
16 changes: 13 additions & 3 deletions openadapt_flow/_browser_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import importlib.util
import os
import re
import shlex
import subprocess
import sys
import threading
Expand Down Expand Up @@ -112,6 +113,11 @@ def _opted_out() -> bool:
)


def _playwright_module_command(*args: str) -> str:
"""Return a copyable Playwright command for this exact Python environment."""
return shlex.join([sys.executable, "-m", "playwright", *args])


def _missing_chromium_system_libs() -> list[str]:
"""Return the Chromium shared libraries missing on this Linux machine.

Expand Down Expand Up @@ -139,11 +145,14 @@ def _require_linux_system_libs() -> None:
if not missing:
return
libs = ", ".join(missing)
install_deps = _playwright_module_command("install-deps", "chromium")
raise RuntimeError(
"Chromium cannot launch on this machine yet: required system "
f"libraries are missing ({libs}).\n\n"
"Install them once with:\n\n"
" sudo python -m playwright install-deps chromium\n\n"
"Install them once with the same Python environment that runs "
"OpenAdapt. Playwright requests administrator access if the system "
"package manager needs it:\n\n"
f" {install_deps}\n\n"
"or, on Debian/Ubuntu:\n\n"
f" sudo apt-get install -y {_LINUX_APT_PACKAGES}\n\n"
"Then run your command again. Nothing was downloaded."
Expand Down Expand Up @@ -205,10 +214,11 @@ def _install_chromium() -> None:
check=True,
)
except (subprocess.CalledProcessError, OSError) as exc:
install_browser = _playwright_module_command("install", "chromium")
raise RuntimeError(
"openadapt-flow could not automatically download the Chromium "
"browser it needs. To install it manually, run:\n\n"
" playwright install chromium\n\n"
f" {install_browser}\n\n"
"If you are behind a corporate proxy or firewall that blocks the "
"Playwright download CDN, set HTTPS_PROXY first "
"(for example: export HTTPS_PROXY=http://proxy.example.com:8080) "
Expand Down
7 changes: 4 additions & 3 deletions openadapt_flow/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,14 +567,15 @@ def run_tutorial(
presentation_delay_s=delay,
)

say("[2/5] Compile, mining the effect contract from the observed delta")
say("[2/5] Compile and propose a fixture-specific effect contract")
workflow = compile_recording(
recording_dir, bundle_dir, name=name, mine_effects=True
)
save = consequential_step(workflow)
say(
f" {len(save.effects)} system-of-record effect(s) derived from "
f"the demonstration's record delta on {save.id}"
f" {len(save.effects)} system-of-record effect proposal(s) "
f"derived from the fixture's record delta on {save.id}; review "
"is required for a real deployment"
)

say(f"[3/5] Certify against the {TUTORIAL_POLICY} policy")
Expand Down
19 changes: 19 additions & 0 deletions openadapt_flow/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,25 @@ def is_independent_system_of_record(self) -> bool:
return int(self) <= int(VerificationTier.INDEPENDENT_SESSION)


def oracle_tier_from_verification_tier(
tier: VerificationTier | int,
) -> int:
"""Map the legacy Flow verifier rank to the public Seal oracle ladder.

``VerificationTier`` is a persisted Flow v1 field where lower numbers are
stronger. Public receipts use the Seal ladder, where higher numbers are
stronger. Keep this conversion at the boundary instead of presenting the
two incompatible number systems to an operator.
"""

value = VerificationTier(tier)
if value is VerificationTier.INDEPENDENT_SYSTEM:
return 2
if value is VerificationTier.INDEPENDENT_SESSION:
return 1
return 0


#: Production ``VERIFIED`` requires this floor or stronger (lower int).
#: The Standard *gate* still admits persisted-state read-back so a
#: pixel-only run can execute with halt-on-doubt; the outcome classifier
Expand Down
51 changes: 50 additions & 1 deletion tests/test_browser_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,61 @@ def test_missing_system_libs_abort_before_any_download(monkeypatch):

msg = str(exc.value)
assert "nss3" in msg
assert "playwright install-deps chromium" in msg # exact primary remedy
assert "-m playwright install-deps chromium" in msg
assert "sudo python -m playwright" not in msg
assert "requests administrator access" in msg
assert "apt-get install" in msg # apt alternative line
assert "Nothing was downloaded" in msg
assert calls == []


def test_linux_remedy_quotes_the_exact_python_environment(monkeypatch):
"""The remedy survives spaces and does not depend on a PATH entry."""
monkeypatch.setattr(bs.sys, "executable", "/opt/OpenAdapt Tool/bin/python3")
monkeypatch.setattr(bs, "_missing_chromium_system_libs", lambda: ["nss3"])

with pytest.raises(RuntimeError) as exc:
bs._require_linux_system_libs()

msg = str(exc.value)
assert (
"'/opt/OpenAdapt Tool/bin/python3' -m playwright install-deps chromium" in msg
)
assert "sudo python" not in msg


def test_browser_download_failure_uses_the_exact_python_environment(monkeypatch):
"""The CDN remedy must work when Playwright is not exposed on PATH."""
monkeypatch.setattr(bs.sys, "executable", "/opt/OpenAdapt Tool/bin/python3")
monkeypatch.setattr(bs, "_chromium_present", lambda: False)
monkeypatch.setattr(bs, "_missing_chromium_system_libs", lambda: [])

def fail_download(*_args, **_kwargs):
raise bs.subprocess.CalledProcessError(1, "playwright")

monkeypatch.setattr(bs.subprocess, "run", fail_download)

with pytest.raises(RuntimeError) as exc:
bs._install_chromium()

assert "'/opt/OpenAdapt Tool/bin/python3' -m playwright install chromium" in str(
exc.value
)


def test_public_setup_copy_states_the_linux_dependency_boundary():
"""README and tutorial do not promise an unconditional first download."""
root = Path(__file__).parents[1]
readme = (root / "README.md").read_text()
tutorial = (root / "docs" / "TUTORIAL.md").read_text()

assert "checks the Chromium host libraries before" in readme
assert "A minimal Linux host may need" in tutorial
assert "exact Python environment" in readme
assert "exact Python environment" in tutorial
assert "sudo python -m playwright" not in readme + tutorial


def test_present_system_libs_do_not_block_install(monkeypatch):
"""Empty probe result -> the normal download path proceeds unchanged."""
monkeypatch.setattr(bs, "_chromium_present", lambda: False)
Expand Down
33 changes: 33 additions & 0 deletions tests/test_oracle_vocabulary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""The public oracle ladder must not expose Flow's inverse legacy rank."""

from __future__ import annotations

from pathlib import Path

from openadapt_flow.verification import (
VerificationTier,
oracle_tier_from_verification_tier,
)


def test_legacy_verification_rank_maps_to_seal_oracle_tier() -> None:
assert oracle_tier_from_verification_tier(VerificationTier.INDEPENDENT_SYSTEM) == 2
assert oracle_tier_from_verification_tier(VerificationTier.INDEPENDENT_SESSION) == 1
assert (
oracle_tier_from_verification_tier(
VerificationTier.PERSISTED_STATE_REACQUISITION
)
== 0
)
assert oracle_tier_from_verification_tier(VerificationTier.IMMEDIATE_SCREEN) == 0


def test_public_docs_use_the_seal_oracle_ladder() -> None:
root = Path(__file__).parents[1]
readme = (root / "README.md").read_text(encoding="utf-8")
kit = (root / "docs" / "EFFECT_KIT.md").read_text(encoding="utf-8")

assert "Seal Oracle tier 2" in readme
assert "evidence tier 1 (independent system of record)" not in readme
assert "tier 0 is visual evidence" in kit
assert "tier 2 is a system-of-record read" in kit
6 changes: 3 additions & 3 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.