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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
[![Python](https://img.shields.io/pypi/pyversions/openadapt-flow)](https://pypi.org/project/openadapt-flow/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)

Show OpenAdapt any repeated GUI task — in the browser, in a native desktop
app, or inside a Citrix or virtual-desktop session — demonstrated once. It
compiles the demonstration into a governed, deterministic, locally executable
program that replays exactly, with zero model calls on a healthy run. When the
interface drifts, it re-resolves from recorded evidence or proposes a governed
repair, and it halts instead of guessing when verification fails.
OpenAdapt is built for repeated workflows behind browser, legacy desktop, and
remote-display interfaces. It compiles demonstrated GUI workflows into
deterministic, locally executable programs. Each target application and
environment is qualified separately. Healthy runs make no model calls. When
interfaces drift, OpenAdapt re-resolves from retained evidence or proposes a
governed repair and halts when verification fails.

![One demonstration, two UIs, same compiled workflow — the right side self-heals under a theme it has never seen](docs/showcase/demo.gif)

Expand Down
23 changes: 4 additions & 19 deletions openadapt_flow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,17 +352,11 @@ def _build_and_run_replayer(
)


def _finish_replay(run_dir: Path, report, *, synthetic_demo: bool = False) -> int:
"""Render the run report, print the outcome, and map it to an exit code.

``synthetic_demo`` is True only for the bundled-MockMed demo replay with no
operator ``--param`` overrides (see ``render_run_report``); it softens the
first-run plaintext-PHI warning for known-synthetic demo data and nothing
else.
"""
def _finish_replay(run_dir: Path, report) -> int:
"""Render the run report, print the outcome, and map it to an exit code."""
from openadapt_flow.report import render_run_report

report_md = render_run_report(run_dir, synthetic_demo=synthetic_demo)
report_md = render_run_report(run_dir)
outcome = "success" if report.success else "FAILED"
print(f"Replay {outcome}: {report_md}")
if report.screenshots_may_leave_box:
Expand Down Expand Up @@ -736,16 +730,7 @@ def _cmd_replay(args: argparse.Namespace) -> int:
if stop is not None:
stop()

# Soften the first-run plaintext-PHI warning ONLY when the CLI itself
# served the bundled synthetic MockMed demo (no --url) and no operator
# values flowed in (--param / --params-file / worklists) — every
# identity-like value is then the recorded fake demo data. Real targets
# or operator-supplied values keep the full warning.
return _finish_replay(
run_dir,
report,
synthetic_demo=(stop is not None and not params and not worklists),
)
return _finish_replay(run_dir, report)


def _cmd_run(args: argparse.Namespace) -> int:
Expand Down
20 changes: 3 additions & 17 deletions openadapt_flow/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _report_has_identity_like_text(report: RunReport) -> bool:
return any((r.intent or "").strip() for r in report.results)


def _warn_if_plaintext_phi(report: RunReport, *, synthetic_demo: bool = False) -> None:
def _warn_if_plaintext_phi(report: RunReport) -> None:
"""Warn (once) when REPORT.md will contain plaintext identity-like text.

Fires only when scrubbing is *not* active (default ``auto`` with the
Expand All @@ -47,17 +47,7 @@ def _warn_if_plaintext_phi(report: RunReport, *, synthetic_demo: bool = False) -
``on`` fails closed upstream before reaching here. Not a behavior change —
the report is still written; this only makes the plaintext write visible.
``warnings`` dedups per call site, so it is effectively one-time per process.

``synthetic_demo=True`` suppresses the warning. The ONLY caller that sets
it is the ``replay`` CLI, and only when the CLI itself served the bundled
MockMed demo app in-process (no ``--url``) AND the operator supplied no
``--param`` overrides — i.e. the identity-like free text is the recorded
synthetic demo data, so alarming a first-run user about PHI would be
noise. Any replay against a real app, or with operator-supplied values,
keeps the full warning.
"""
if synthetic_demo:
return
if _scrub_mode() == "off" or _text_scrubbing_enabled():
return
if not _report_has_identity_like_text(report):
Expand Down Expand Up @@ -111,16 +101,12 @@ def _before_after_table(result: StepResult) -> list[str]:
]


def render_run_report(run_dir: Path | str, *, synthetic_demo: bool = False) -> Path:
def render_run_report(run_dir: Path | str) -> Path:
"""Render ``REPORT.md`` inside ``run_dir`` from its ``report.json``.

Args:
run_dir: Run directory containing ``report.json`` (and the
``steps/`` / ``heals/`` image folders it references).
synthetic_demo: True ONLY when the caller knows every identity-like
free-text field is bundled synthetic demo data (the CLI's
bundled-MockMed replay with no operator ``--param`` overrides);
suppresses the plaintext-PHI warning, nothing else.

Returns:
Path to the written ``REPORT.md``.
Expand All @@ -132,7 +118,7 @@ def render_run_report(run_dir: Path | str, *, synthetic_demo: bool = False) -> P
report = RunReport.model_validate_json(
(run / "report.json").read_text(encoding="utf-8")
)
_warn_if_plaintext_phi(report, synthetic_demo=synthetic_demo)
_warn_if_plaintext_phi(report)

ok_count = sum(1 for r in report.results if r.ok)
icon = "✅" if report.success else "❌"
Expand Down
17 changes: 13 additions & 4 deletions scripts/check_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
backticked ``a/b.ext`` token in ``README.md`` / ``DESIGN.md`` /
``docs/LIMITS.md``, and every such path in a ``.github/workflows/*.yml``
comment, actually exists.
* **banned phrases** — stale claims the README must never carry again
(``vision-only``, ``adapters to come``, ``864 tests``).
* **banned phrases** — stale or unbounded claims the README must never carry
again (including substrate universals and single-demonstration guarantees).
* **test count** — the README deliberately carries NO hardcoded test count (a
number that rots on every test added). If someone reintroduces one, it is
checked against ``pytest --collect-only`` and must match within a tolerance.
Expand Down Expand Up @@ -46,8 +46,17 @@
DOC_FILES = ["README.md", "DESIGN.md", "docs/LIMITS.md"]
README = "README.md"

# Stale claims the README must never carry again. Lowercased substring match.
BANNED_PHRASES = ["vision-only", "adapters to come", "864 tests"]
# Stale or unbounded claims the README must never carry again. Lowercased
# substring match. Substrate maturity belongs in the evidence matrix; the
# opening must not silently turn intended scope into universal availability.
BANNED_PHRASES = [
"vision-only",
"adapters to come",
"864 tests",
"any repeated gui task",
"demonstrated once",
"replays exactly",
]

# A referenced path is only checked when it ends in one of these (prose slashes
# like "O/0" or "20/20" carry no extension and are ignored), or is a directory
Expand Down
3 changes: 1 addition & 2 deletions tests/test_backend_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,7 @@ def fake_run(backend, **kwargs):

monkeypatch.setattr(m, "_build_and_run_replayer", fake_run)
monkeypatch.setattr(
"openadapt_flow.report.render_run_report",
lambda run_dir, **_kw: "REPORT.md",
"openadapt_flow.report.render_run_report", lambda run_dir: "REPORT.md"
)
monkeypatch.setattr("openadapt_flow.ir.Workflow.load", lambda bundle: object())

Expand Down
8 changes: 2 additions & 6 deletions tests/test_cli_new_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,7 @@ def run(self, workflow, **kwargs):
monkeypatch.setattr(bs, "ensure_chromium_installed", lambda: None)
monkeypatch.setattr(grounder_mod, "build_grounder", lambda fallback=None: None)
monkeypatch.setattr(remote_mod, "appliance_from_env", lambda: None)
monkeypatch.setattr(
report_mod, "render_run_report", lambda run_dir, **_kw: "REPORT.md"
)
monkeypatch.setattr(report_mod, "render_run_report", lambda run_dir: "REPORT.md")
monkeypatch.setattr(runtime_mod, "Replayer", _FakeReplayer)


Expand Down Expand Up @@ -530,9 +528,7 @@ def __init__(self, backend, **kwargs):
monkeypatch.setattr(
durable_mod, "resume", lambda run_dir, replayer, key=None: _FakeReport()
)
monkeypatch.setattr(
report_mod, "render_run_report", lambda run_dir, **_kw: "REPORT.md"
)
monkeypatch.setattr(report_mod, "render_run_report", lambda run_dir: "REPORT.md")

rc = main(
[
Expand Down
9 changes: 8 additions & 1 deletion tests/test_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ def test_lock_version_match_ok():
# banned phrases are caught
# --------------------------------------------------------------------------- #
def test_banned_phrase_detected():
for phrase in ("vision-only", "adapters to come", "864 tests"):
for phrase in (
"vision-only",
"adapters to come",
"864 tests",
"any repeated GUI task",
"demonstrated once",
"replays exactly",
):
errors = cc.check_banned_phrases(f"prose {phrase} more prose")
assert errors, f"{phrase!r} not flagged"

Expand Down
74 changes: 60 additions & 14 deletions tests/test_privacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from __future__ import annotations

import json
from pathlib import Path

import pytest
Expand Down Expand Up @@ -298,25 +297,72 @@ def test_report_md_no_warning_when_scrub_off(monkeypatch, tmp_path: Path):
render_run_report(run_dir) # must not raise


def test_report_md_no_warning_for_bundled_synthetic_demo(monkeypatch, tmp_path: Path):
"""``synthetic_demo=True`` (bundled MockMed, no operator values) => silent.
def test_default_mockmed_replay_warns_for_arbitrary_phi_bundle(
monkeypatch, tmp_path: Path
):
"""Default MockMed routing cannot declare an arbitrary bundle synthetic.

The replay CLI sets this only when it served the bundled synthetic MockMed
demo itself (no ``--url``) and no ``--param``/worklist values flowed in,
so the identity-like text is known-fake demo data.
``replay`` accepts any bundle while defaulting the target URL to MockMed.
The bundle's provenance is therefore unknown, even with no parameter or
worklist overrides, and its identity-like report text must still warn.
"""
from contextlib import contextmanager
from types import SimpleNamespace

import openadapt_flow.__main__ as cli
from openadapt_flow.report import PlaintextPHIWarning

monkeypatch.setattr(privacy, "_build_provider", lambda: None)
privacy.reset_scrubbers()
bundle = tmp_path / "arbitrary-phi-bundle"
bundle.mkdir()
run_dir = tmp_path / "run"
run_dir.mkdir()
_phi_report().save(run_dir)
observed: dict[str, object] = {"served": False, "stopped": False}

import warnings as _w
def fake_load(path):
observed["bundle"] = Path(path)
return object()

with _w.catch_warnings():
_w.simplefilter("error", PlaintextPHIWarning)
md = render_run_report(run_dir, synthetic_demo=True).read_text()
# Only the warning is softened — the report content is unchanged.
assert "John Smith" in md
def fake_serve(*, port):
assert port == 0
observed["served"] = True

def stop() -> None:
observed["stopped"] = True

return "http://127.0.0.1:45678/", stop

def fake_run(_backend, **kwargs):
report = _phi_report()
report.save(kwargs["run_dir"])
return report

page = SimpleNamespace(
url="http://127.0.0.1:45678/", video=None, goto=lambda _url: None
)
browser = SimpleNamespace(new_page=lambda **_kw: page, close=lambda: None)
playwright = SimpleNamespace(chromium=SimpleNamespace(launch=lambda **_kw: browser))

@contextmanager
def fake_sync_playwright():
yield playwright

monkeypatch.setattr("openadapt_flow.ir.Workflow.load", fake_load)
monkeypatch.setattr("openadapt_flow.mockmed.server.serve", fake_serve)
monkeypatch.setattr(
"openadapt_flow._browser_setup.ensure_chromium_installed", lambda: None
)
monkeypatch.setattr("playwright.sync_api.sync_playwright", fake_sync_playwright)
monkeypatch.setattr(
"openadapt_flow.backends.factory.build_backend", lambda *_a, **_k: object()
)
monkeypatch.setattr(cli, "_build_and_run_replayer", fake_run)

args = cli.build_parser().parse_args(
["replay", str(bundle), "--run-dir", str(run_dir)]
)

with pytest.warns(PlaintextPHIWarning):
assert cli._cmd_replay(args) == 1
assert observed == {"served": True, "stopped": True, "bundle": bundle}
assert "John Smith" in (run_dir / "REPORT.md").read_text(encoding="utf-8")