Skip to content
Closed
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
17 changes: 16 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ installed `openadapt-flow` engine.
## First run

```bash
openadapt quickstart [--headed] [--break-it] [--out NEW_DIRECTORY]
openadapt quickstart [--headed] [--out NEW_DIRECTORY]
```

`quickstart` runs the bundled synthetic workflow from recording through an
Expand Down Expand Up @@ -100,6 +100,21 @@ openadapt flow push APPROVED_DERIVATIVE --kind recording
Do not upload a raw recording. The push path accepts an approved sanitized
derivative and validates its exact bytes.

## Advanced effect-verification simulation

After the healthy quickstart succeeds, you can run an optional synthetic case
in which the application reports success while its independent source rejects
the write:

```bash
openadapt quickstart --simulate-rejected-write \
--out openadapt-quickstart-rejected-write
```

OpenAdapt runs the healthy tutorial first. It then uses the same certified
bundle for the simulated rejection. The independent effect check detects the
missing write and returns `HALTED`. This doesn't qualify your own workflow.

## Capture and research commands

The optional `capture` group exposes the supported low-level Capture component
Expand Down
9 changes: 0 additions & 9 deletions docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,6 @@ openadapt flow visualize openadapt-quickstart/bundle --out graph.html
openadapt flow lint openadapt-quickstart/bundle
```

Run the same certified bundle against a fault-injecting backend:

```bash
openadapt quickstart --break-it --out openadapt-quickstart-broken
```

The application displays success, but the backend does not save the record.
The independent effect verifier detects the mismatch and returns `HALTED`.

## Run the manual demo lifecycle

The engine command is `openadapt-flow`. The launcher provides the equivalent
Expand Down
48 changes: 29 additions & 19 deletions openadapt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,27 @@ def _is_externally_managed_error(error: BaseException) -> bool:
"--headed", is_flag=True, help="Show the browser while the tutorial runs."
)
@click.option(
"--break-it",
"break_it",
"--simulate-rejected-write",
"simulate_rejected_write",
is_flag=True,
help=(
"After the verified run, rerun the same certified bundle against a "
"fault-injecting backend and watch the engine halt instead of "
"trusting the screen."
"After the verified run, simulate an application that reports success "
"while its independent source rejects the write."
),
)
@click.option(
"--break-it",
"deprecated_break_it",
is_flag=True,
hidden=True,
)
@click.pass_context
def quickstart(
command_ctx: click.Context, out: Optional[Path], headed: bool, break_it: bool
command_ctx: click.Context,
out: Optional[Path],
headed: bool,
simulate_rejected_write: bool,
deprecated_break_it: bool,
) -> None:
"""Run a verified local tutorial against the bundled synthetic app.

Expand All @@ -185,6 +194,13 @@ def quickstart(

_require_supported_python()

if deprecated_break_it:
click.echo(
"Warning: --break-it is deprecated; use --simulate-rejected-write.",
err=True,
)
simulate_rejected_write = simulate_rejected_write or deprecated_break_it

if out is None:
root = Path(_DEFAULT_QUICKSTART_DIR).resolve()
suffix = 2
Expand All @@ -208,8 +224,8 @@ def quickstart(
]
if headed:
argv.append("--headed")
if break_it:
argv.append("--break-it")
if simulate_rejected_write:
argv.append("--simulate-rejected-write")
# Engine-owned flags (--guided, --interactive-record, and future engine
# additions) forward verbatim instead of being whitelisted here.
argv.extend(command_ctx.args)
Expand Down Expand Up @@ -247,21 +263,15 @@ def quickstart(
"The synthetic write was confirmed through a read-only system-of-record API."
)
click.echo("No model or Cloud call was enabled.")
if break_it:
if simulate_rejected_write:
click.echo(
"The rerun against the fault-injecting backend HALTED as designed: "
"the screen claimed success and the system of record disagreed."
"The simulated rejected write HALTED as designed. The application "
"reported success, but the independent source rejected the write."
)
click.echo(f"Caught-fault evidence: {root / 'run-broken' / 'REPORT.md'}")
click.echo(f"Simulation evidence: {root / 'run-rejected-write' / 'REPORT.md'}")
click.echo(f"Inspect qualification gaps: openadapt flow lint {root / 'bundle'}")
if not break_it:
click.echo(
"Watch it catch a lie: openadapt quickstart --break-it --out "
f"{root}-break-it"
)
click.echo(
"See a fail-safe halt: openadapt flow replay "
f"{root / 'bundle'} --drift modal --run-dir {root}-halt"
"Record your first workflow: https://docs.openadapt.ai/guides/record-your-app/"
)
click.echo(
"Connect this computer when you want Cloud history and collaboration: "
Expand Down
50 changes: 46 additions & 4 deletions tests/test_cli_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_quickstart_forwards_the_headed_tutorial_option(monkeypatch):
assert calls[0][-1] == "--headed"


def test_quickstart_forwards_the_break_it_option_and_names_the_evidence(
def test_quickstart_forwards_the_rejected_write_simulation_and_names_the_evidence(
monkeypatch,
):
calls = []
Expand All @@ -127,17 +127,59 @@ def test_quickstart_forwards_the_break_it_option_and_names_the_evidence(
with runner.isolated_filesystem():
result = runner.invoke(
cli_main,
["quickstart", "--break-it", "--out", "break-run"],
[
"quickstart",
"--simulate-rejected-write",
"--out",
"rejected-write-run",
],
)

assert result.exit_code == 0, result.output
assert len(calls) == 1
assert calls[0][0] == "tutorial"
assert calls[0][-1] == "--break-it"
assert "run-broken" in result.output
assert calls[0][-1] == "--simulate-rejected-write"
assert "run-rejected-write" in result.output
assert "HALTED" in result.output


def test_quickstart_hides_and_warns_for_the_deprecated_break_it_alias(monkeypatch):
calls = []
monkeypatch.setattr(
"openadapt.cli._invoke_flow",
lambda argv: calls.append(list(argv)) or 0,
)

runner = CliRunner()
help_result = runner.invoke(cli_main, ["quickstart", "--help"])
assert help_result.exit_code == 0, help_result.output
assert "--simulate-rejected-write" in help_result.output
assert "--break-it" not in help_result.output

with runner.isolated_filesystem():
result = runner.invoke(
cli_main,
["quickstart", "--break-it", "--out", "deprecated-alias-run"],
)

assert result.exit_code == 0, result.output
assert calls[0][-1] == "--simulate-rejected-write"
assert "Warning: --break-it is deprecated" in result.output


def test_quickstart_success_output_leads_to_a_real_workflow(monkeypatch):
monkeypatch.setattr("openadapt.cli._invoke_flow", lambda _argv: 0)

runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli_main, ["quickstart", "--out", "first-run"])

assert result.exit_code == 0, result.output
assert "--simulate-rejected-write" not in result.output
assert "--break-it" not in result.output
assert "https://docs.openadapt.ai/guides/record-your-app/" in result.output


def test_quickstart_restores_the_operator_scrub_setting(monkeypatch):
monkeypatch.setenv("OPENADAPT_FLOW_SCRUB", "auto")
monkeypatch.setattr("openadapt.cli._invoke_flow", lambda _argv: 0)
Expand Down