diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b50843e..b969ca2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,8 @@ jobs: run: python3 scripts/check_orro_fallback_policy.py - name: Check ORRO command migration run: python3 scripts/check_orro_command_migration.py + - name: Check removed command documentation + run: python3 scripts/check_orro_removed_command_docs.py - name: Check ORRO command migration dry-run run: python3 scripts/check_orro_command_migration_dry_run.py --json --allow-network - name: Check ORRO wrapper @@ -49,7 +51,7 @@ jobs: - name: Check ORRO wrapper distribution run: python3 scripts/check_orro_wrapper_distribution.py --json --allow-network - name: Compile scripts - run: python3 -m py_compile scripts/check_no_bidi_controls.py scripts/check_orro_repo_contract.py scripts/check_orro_language_boundaries.py scripts/check_orro_assurance_contract_fixtures.py scripts/check_orro_release_manifest.py scripts/check_compatibility_matrix.py scripts/check_orro_release_state.py scripts/check_orro_packaging_decision.py scripts/check_orro_fallback_policy.py scripts/check_orro_command_migration.py scripts/check_orro_command_migration_dry_run.py scripts/check_orro_wrapper.py scripts/check_orro_wrapper_install.py scripts/check_orro_wrapper_distribution.py scripts/orro_build_backend.py scripts/orro_e2e_smoke.py scripts/update_orro_engine_lock.py scripts/bootstrap_orro.py src/orro_wrapper/__init__.py src/orro_wrapper/__main__.py src/orro_wrapper/cli.py + run: python3 -m py_compile scripts/check_no_bidi_controls.py scripts/check_orro_repo_contract.py scripts/check_orro_language_boundaries.py scripts/check_orro_assurance_contract_fixtures.py scripts/check_orro_release_manifest.py scripts/check_compatibility_matrix.py scripts/check_orro_release_state.py scripts/check_orro_packaging_decision.py scripts/check_orro_fallback_policy.py scripts/check_orro_command_migration.py scripts/check_orro_removed_command_docs.py scripts/check_orro_command_migration_dry_run.py scripts/check_orro_wrapper.py scripts/check_orro_wrapper_install.py scripts/check_orro_wrapper_distribution.py scripts/orro_build_backend.py scripts/orro_e2e_smoke.py scripts/update_orro_engine_lock.py scripts/bootstrap_orro.py src/orro_wrapper/__init__.py src/orro_wrapper/__main__.py src/orro_wrapper/cli.py - name: Bidi control scanner self-test run: python3 scripts/check_no_bidi_controls.py --self-test - name: Install ORRO wrapper package (editable) @@ -70,5 +72,7 @@ jobs: run: python3 scripts/update_orro_engine_lock.py --self-test - name: Bootstrap helper self-test run: python3 scripts/bootstrap_orro.py --self-test + - name: Removed command documentation self-test + run: python3 scripts/check_orro_removed_command_docs.py --self-test - name: Check whitespace run: git diff --check diff --git a/README.md b/README.md index 39b53a2..91da106 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Current split: ORRO turns a goal into an evidence-governed workflow: ```text -advise -> init/doctor/engine-lock -> scout -> flowplan -> proofrun -> proofcheck -> handoff -> report +advise -> init/doctor/engine-lock -> scout -> flowplan -> proofrun -> proofcheck -> handoff -> status ``` This repository is the product, documentation, examples, distribution, and @@ -208,6 +208,11 @@ The executable `orro` command is ORRO-owned, thin, and delegates to witnessd. The command migration is documented in [`docs/orro-command-migration.md`](docs/orro-command-migration.md). +Canonical invocation rule: use `orro` for operator-facing commands. Wrapper-owned +commands (`boundary`, `self-test`, and explicit `delegate`) are handled by ORRO; +workflow commands are delegated to witnessd, with `orro delegate -- ` +as the explicit delegated form. + The migration does not publish a package, does not move engine code, and does not change verifier or runtime semantics. `orro-wrapper` remains a compatibility command for the same thin wrapper module. @@ -266,7 +271,7 @@ orro flowplan "fix parser bug" --root . --profile code-change --out .witnessd/wo orro proofrun "fix parser bug" --repo . --home .witnessd --workflow-plan .witnessd/workflow-plan.json orro proofcheck .witnessd/runs/ --home .witnessd --out .witnessd/runs//proofcheck-verdict.json orro handoff .witnessd/runs/ --out .witnessd/runs//orro-handoff.json -orro report .witnessd/runs/ --home .witnessd +orro status .witnessd/runs/ --home .witnessd ``` ## Trust Boundaries diff --git a/docs/architecture.md b/docs/architecture.md index c720d2b..fa4cbad 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -7,7 +7,7 @@ user -> evidence artifacts -> Depone verifier -> verdict artifacts - -> ORRO report/handoff + -> ORRO status/handoff ``` ```text diff --git a/docs/bootstrap.md b/docs/bootstrap.md index db57245..0a05079 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -31,8 +31,10 @@ not run proofrun, proofcheck, handoff, auto, live models, or MCP calls. ## Check Existing -`--check-existing` inspects local engine roots and compares their `HEAD` commits -to the pinned engine lock. +`--check-existing` inspects local engine roots, compares their `HEAD` commits to +the pinned engine lock, and reports Depone's runtime identity: imported module +path, installed distribution version, source-declared version, and locked +commit. All four must agree. ```bash python3 scripts/bootstrap_orro.py \ @@ -66,8 +68,8 @@ python3 scripts/bootstrap_orro.py \ ``` `--execute` may clone witnessd and Depone and check out the pinned commits. It -does not run proofrun, proofcheck, handoff, or auto. Editable witnessd install is -separate and requires the explicit `--install-witnessd` flag: +does not run proofrun, proofcheck, handoff, or auto. Editable Depone and witnessd +installs require the explicit `--install-witnessd` flag: ```bash /usr/bin/python3 -m venv ~/.local/share/orro/venv @@ -84,7 +86,7 @@ Bootstrap rejects `--install-witnessd` outside a virtual environment so it cannot rewrite a system interpreter's command directory. Both ORRO and witnessd publish an `orro` console script. After installing the -pinned editable witnessd, bootstrap installs the ORRO wrapper last and +pinned editable Depone and witnessd, bootstrap installs the ORRO wrapper last and explicitly links the invoking environment's `bin/orro` plus `~/.local/bin/orro` to `bin/orro-wrapper`. This makes the PATH-facing owner deterministic instead of depending on pip install order. Bootstrap then checks diff --git a/docs/e2e-runner.md b/docs/e2e-runner.md index 9d40c32..337ec04 100644 --- a/docs/e2e-runner.md +++ b/docs/e2e-runner.md @@ -64,7 +64,7 @@ fetch, update, or mutate engine repositories. The happy path runs: ```text -advise -> init -> doctor -> engine-lock -> flowplan -> proofrun -> next -> auto --until-complete -> report +advise -> init -> doctor -> engine-lock -> flowplan -> proofrun -> auto --dry-run -> auto --until-complete -> status ``` The negative path checks that scout-only artifacts do not proofcheck-pass or diff --git a/docs/e2e-smoke-contract.md b/docs/e2e-smoke-contract.md index a6bad29..6913ae4 100644 --- a/docs/e2e-smoke-contract.md +++ b/docs/e2e-smoke-contract.md @@ -27,9 +27,9 @@ The current runner checks: - `orro engine-lock --check` - `orro flowplan` - `orro proofrun` -- `orro next` +- `orro auto --dry-run` - `orro auto --until-complete` -- `orro report` +- `orro status --latest` - scout-only artifacts do not proofcheck-pass The e2e result is test metadata, not proof. @@ -46,7 +46,7 @@ lock, and asserts: - `orro proofrun` emits execution evidence - `orro proofcheck` delegates verifier semantics to Depone - `orro handoff` requires a passing bound proofcheck verdict -- `orro report` does not overclaim +- `orro status` does not overclaim - scout-only artifacts do not pass proofcheck - auto v0 does not run proofrun diff --git a/docs/install.md b/docs/install.md index c5dbfac..32fa6d6 100644 --- a/docs/install.md +++ b/docs/install.md @@ -31,13 +31,17 @@ virtual environment's Python. Bootstrap prepares the pinned The ORRO-owned `orro` command delegates to witnessd. This repository is the canonical source of the published product/distribution wrapper package. -Both distributions install an `orro` console script, so bootstrap installs the -ORRO wrapper after the pinned editable witnessd install and then explicitly +Both engine distributions install their runtime packages from the pinned +checkouts (Depone is not published on PyPI), so bootstrap installs Depone and +witnessd editable before installing the ORRO wrapper last and then explicitly links both the shared environment's `bin/orro` and `~/.local/bin/orro` to `bin/orro-wrapper`. Bootstrap verifies the `orro_wrapper.cli:main` entry-point metadata, the non-engine wrapper boundary, and real `flowplan --help` -delegation before it succeeds. `orro-wrapper` remains available, and the pinned -witnessd compatibility shim remains reachable with `python -m orro`. +delegation before it succeeds. It also records Depone's imported module path, +installed distribution version, source-declared version, and locked commit; +these must agree or bootstrap fails closed with a repair command. `--check-existing` +reports the same runtime identity. `orro-wrapper` remains available, and the +pinned witnessd compatibility shim remains reachable with `python -m orro`. ## Bootstrap Planner diff --git a/docs/workflow-reference.md b/docs/workflow-reference.md index 112e8a8..072c056 100644 --- a/docs/workflow-reference.md +++ b/docs/workflow-reference.md @@ -32,7 +32,7 @@ orro flowplan "fix parser bug" --root . --profile code-change --out .witnessd/wo orro proofrun "fix parser bug" --repo . --home .witnessd --workflow-plan .witnessd/workflow-plan.json orro proofcheck .witnessd/runs/ --home .witnessd --out .witnessd/runs//proofcheck-verdict.json orro handoff .witnessd/runs/ --out .witnessd/runs//orro-handoff.json -orro report .witnessd/runs/ --home .witnessd +orro status .witnessd/runs/ --home .witnessd ``` Support surfaces: @@ -45,11 +45,15 @@ Support surfaces: Gemini read-only review lane through witnessd - `orro flowplan --profile review-only --lane-adapter agy`: route a Google Antigravity read-only review lane through witnessd -- `orro next`: non-executing continuation gate - `orro auto --dry-run`: recommendation only - `orro auto --once`: one safe post-run step only - `orro auto --until-complete`: bounded proofcheck/handoff loop only -- `orro report`: human-facing summary +- `orro status | --latest`: human-facing summary + +Canonical invocation rule: use the ORRO-owned `orro` command for operator-facing +commands. Wrapper-owned commands (`boundary`, `self-test`, and explicit +`delegate`) are handled by ORRO; workflow commands are delegated to witnessd, +with `orro delegate -- ` available when that delegation must be explicit. `orro auto` v0 does not run proofrun or workers. diff --git a/examples/code-change.md b/examples/code-change.md index 886cac4..86c3c89 100644 --- a/examples/code-change.md +++ b/examples/code-change.md @@ -17,7 +17,7 @@ orro proofrun "fix parser bug" \ orro proofcheck .witnessd/runs/ \ --home .witnessd \ --out .witnessd/runs//proofcheck-verdict.json -orro report .witnessd/runs/ --home .witnessd +orro status .witnessd/runs/ --home .witnessd ``` The workflow plan is intent, not proof. Actual proof starts with witnessd evidence and Depone proofcheck. diff --git a/examples/full-flow.md b/examples/full-flow.md index a2a659f..158a807 100644 --- a/examples/full-flow.md +++ b/examples/full-flow.md @@ -26,13 +26,13 @@ orro proofrun "fix parser bug" \ --workflow-plan .witnessd/workflow-plan.json \ --role-lane-plan .witnessd/role-lane-plan.json -orro next .witnessd/runs/ --home .witnessd --json +orro auto --dry-run .witnessd/runs/ --home .witnessd --json orro proofcheck .witnessd/runs/ \ --home .witnessd \ --out .witnessd/runs//proofcheck-verdict.json orro handoff .witnessd/runs/ \ --out .witnessd/runs//orro-handoff.json -orro report .witnessd/runs/ --home .witnessd +orro status .witnessd/runs/ --home .witnessd ``` `proofcheck` must pass before formal handoff. Handoff is review packaging, not approval. Report is summary, not proof. diff --git a/examples/non-developer-safe-flow.md b/examples/non-developer-safe-flow.md index 6f28bcb..4620e0f 100644 --- a/examples/non-developer-safe-flow.md +++ b/examples/non-developer-safe-flow.md @@ -10,8 +10,8 @@ orro flowplan "make the documentation clearer" --root . --profile docs-change -- After any observed run exists: ```bash -orro next .witnessd/runs/ --home .witnessd --json -orro report .witnessd/runs/ --home .witnessd +orro auto --dry-run .witnessd/runs/ --home .witnessd --json +orro status .witnessd/runs/ --home .witnessd ``` Safe interpretation: diff --git a/examples/release-readiness.md b/examples/release-readiness.md index 40aecb3..cf546a3 100644 --- a/examples/release-readiness.md +++ b/examples/release-readiness.md @@ -6,7 +6,7 @@ Use release-readiness flows to check setup, pinned engines, and post-run state. orro doctor --home .witnessd --json orro engine-lock --home .witnessd --out .witnessd/orro-engine-lock.json orro engine-lock --home .witnessd --check .witnessd/orro-engine-lock.json --json -orro report .witnessd/runs/ --home .witnessd +orro status .witnessd/runs/ --home .witnessd ``` Doctor and engine-lock are readiness and distribution checks only. They are not proof, not approval, and not assurance. diff --git a/examples/verification-only.md b/examples/verification-only.md index 9227fcb..18ed5ec 100644 --- a/examples/verification-only.md +++ b/examples/verification-only.md @@ -7,7 +7,7 @@ orro advise "verify existing evidence" --repo . --home .witnessd --json orro proofcheck .witnessd/runs/ \ --home .witnessd \ --out .witnessd/runs//proofcheck-verdict.json -orro report .witnessd/runs/ --home .witnessd +orro status .witnessd/runs/ --home .witnessd ``` Verification-only should recommend proofcheck over proofrun. Depone verifies persisted evidence bytes; ORRO does not become the verifier. diff --git a/packaging/command-migration-plan.v0.json b/packaging/command-migration-plan.v0.json index e15c1b3..283e6fb 100644 --- a/packaging/command-migration-plan.v0.json +++ b/packaging/command-migration-plan.v0.json @@ -6,6 +6,12 @@ "current_wrapper_command": "orro-wrapper", "target_command": "orro", "migration_phase": "owned-thin-wrapper", + "removed_commands": { + "sketch": "advise --mode sketch", + "trace": "advise --mode trace", + "next": "auto --dry-run", + "report": "status | --latest" + }, "owns_orro_command_now": true, "adds_orro_console_script": true, "requires_separate_migration_wave": false, diff --git a/scripts/bootstrap_orro.py b/scripts/bootstrap_orro.py index 5f9832b..ed3030b 100644 --- a/scripts/bootstrap_orro.py +++ b/scripts/bootstrap_orro.py @@ -27,6 +27,7 @@ COMMIT_RE = re.compile(r"^[0-9a-f]{40}$") ZERO_COMMIT = "0" * 40 FALSE_BOUNDARY_KEYS = ("approves_merge", "raises_assurance", "executes_commands", "verifies_evidence") +DEPONE_RUNTIME_IDENTITY_ERROR = "ERR_ORRO_BOOTSTRAP_DEPONE_RUNTIME_IDENTITY_MISMATCH" class BootstrapError(RuntimeError): @@ -179,6 +180,13 @@ def bootstrap_plan(mode: str, workspace: Path, lock_path: Path, lock: dict[str, "executes_engine": False, "verifies_evidence": False, }, + { + "name": "install Depone editable", + "action": "python3 -m pip install -e ", + "requires_explicit_flag": "--install-witnessd", + "executes_engine": False, + "verifies_evidence": False, + }, { "name": "install ORRO wrapper last", "action": "python3 -m pip install --no-deps -e ", @@ -222,7 +230,115 @@ def git_head(root: Path, label: str) -> str: return commit -def check_existing(witnessd_root: Path, depone_root: Path, lock_path: Path, lock: dict[str, Any]) -> dict[str, Any]: +def depone_source_version(depone_root: Path) -> str: + pyproject = depone_root / "pyproject.toml" + try: + text = pyproject.read_text(encoding="utf-8") + except OSError as exc: + raise BootstrapError( + DEPONE_RUNTIME_IDENTITY_ERROR, + f"could not read the Depone source version from {pyproject}", + {"path": str(pyproject), "error": str(exc)}, + ) from exc + in_project = False + for line in text.splitlines(): + stripped = line.strip() + if stripped.startswith("["): + in_project = stripped == "[project]" + continue + if in_project and stripped.startswith("version") and "=" in stripped: + version = stripped.split("=", 1)[1].strip().strip('"').strip("'") + if version: + return version + raise BootstrapError( + DEPONE_RUNTIME_IDENTITY_ERROR, + f"Depone source does not declare a project version in {pyproject}", + {"path": str(pyproject)}, + ) + + +def depone_runtime_identity(python: Path, depone_root: Path, locked_commit: str, checkout_commit: str) -> dict[str, Any]: + source_version = depone_source_version(depone_root) + module_probe = subprocess.run( + [ + str(python), + "-c", + ( + "import json, depone; " + "print(json.dumps({" + "'depone_file': depone.__file__, " + "'module_version': getattr(depone, '__version__', None)" + "}))" + ), + ], + env={**os.environ, "PYTHONPATH": os.pathsep.join([str(depone_root), os.environ.get("PYTHONPATH", "")])}, + text=True, + capture_output=True, + check=False, + ) + metadata_env = os.environ.copy() + metadata_env.pop("PYTHONPATH", None) + metadata_probe = subprocess.run( + [str(python), "-c", "import importlib.metadata as metadata; print(metadata.version('depone'))"], + env=metadata_env, + text=True, + capture_output=True, + check=False, + ) + if module_probe.returncode != 0 or metadata_probe.returncode != 0: + raise BootstrapError( + DEPONE_RUNTIME_IDENTITY_ERROR, + "could not inspect the Depone runtime; repair with " + f"git -C {depone_root} checkout {locked_commit} && {python} -m pip install -e {depone_root}", + { + "module_stdout": module_probe.stdout, + "module_stderr": module_probe.stderr, + "metadata_stdout": metadata_probe.stdout, + "metadata_stderr": metadata_probe.stderr, + }, + ) + try: + imported = json.loads(module_probe.stdout) + except json.JSONDecodeError as exc: + raise BootstrapError( + DEPONE_RUNTIME_IDENTITY_ERROR, + "Depone runtime identity probe did not emit JSON; repair with " + f"git -C {depone_root} checkout {locked_commit} && {python} -m pip install -e {depone_root}", + {"stdout": module_probe.stdout, "stderr": module_probe.stderr}, + ) from exc + + identity = { + "depone_file": imported.get("depone_file"), + "installed_version": metadata_probe.stdout.strip(), + "module_version": imported.get("module_version"), + "source_version": source_version, + "checkout_commit": checkout_commit, + "locked_commit": locked_commit, + } + mismatches: list[str] = [] + try: + imported_path = Path(str(identity["depone_file"])).resolve() + imported_path.relative_to(depone_root.resolve()) + except (TypeError, ValueError): + mismatches.append("depone.__file__ is outside the locked checkout") + if identity["installed_version"] != source_version: + mismatches.append("installed distribution version differs from source version") + if identity["module_version"] != source_version: + mismatches.append("depone.__version__ differs from source version") + if checkout_commit != locked_commit: + mismatches.append("checkout commit differs from locked commit") + if mismatches: + repair = f"git -C {depone_root} checkout {locked_commit} && {python} -m pip install -e {depone_root}" + raise BootstrapError( + DEPONE_RUNTIME_IDENTITY_ERROR, + "Depone runtime identity disagreement (" + "; ".join(mismatches) + "); " + f"repair with: {repair}", + {"identity": identity, "mismatches": mismatches, "repair_command": repair}, + ) + return identity + + +def check_existing(witnessd_root: Path, depone_root: Path, lock_path: Path, lock: dict[str, Any], *, python: Path | None = None) -> dict[str, Any]: if not witnessd_root.is_dir(): raise BootstrapError("ERR_ORRO_BOOTSTRAP_ENGINE_ROOT_MISSING", "witnessd root is missing", {"path": str(witnessd_root)}) if not depone_root.is_dir(): @@ -246,6 +362,23 @@ def check_existing(witnessd_root: Path, depone_root: Path, lock_path: Path, lock }, ] matched = all(step["status"] == "pass" for step in steps) + if not matched: + receipt = { + "kind": "orro-bootstrap-receipt", + "schema_version": SCHEMA_VERSION, + "mode": "check-existing", + "engine_lock": lock_summary(lock_path, lock), + "engine_lock_matched": False, + "witnessd_root": str(witnessd_root), + "depone_root": str(depone_root), + "steps": steps, + "not_proof": True, + "not_verifier_truth": True, + "boundary": boundary(), + } + raise BootstrapError("ERR_ORRO_BOOTSTRAP_ENGINE_LOCK_MISMATCH", "local engine roots do not match engine lock", {"receipt": receipt}) + runtime = depone_runtime_identity(python or Path(sys.executable), depone_root, depone_expected, depone_actual) + steps.append({"name": "check Depone runtime identity", "status": "pass", "identity": runtime}) receipt = { "kind": "orro-bootstrap-receipt", "schema_version": SCHEMA_VERSION, @@ -259,8 +392,6 @@ def check_existing(witnessd_root: Path, depone_root: Path, lock_path: Path, lock "not_verifier_truth": True, "boundary": boundary(), } - if not matched: - raise BootstrapError("ERR_ORRO_BOOTSTRAP_ENGINE_LOCK_MISMATCH", "local engine roots do not match engine lock", {"receipt": receipt}) return receipt @@ -477,6 +608,10 @@ def execute_bootstrap(workspace: Path, lock_path: Path, lock: dict[str, Any], *, ] if install_witnessd: require_virtual_environment(Path(sys.prefix), Path(sys.base_prefix)) + depone_install = run_command([sys.executable, "-m", "pip", "install", "-e", str(depone_root)]) + steps.append({"name": "install Depone editable", "status": "pass", "command": depone_install, "executes_engine": False, "verifies_evidence": False}) + runtime = depone_runtime_identity(Path(sys.executable), depone_root, lock["depone"]["commit"], git_head(depone_root, "Depone")) + steps.append({"name": "verify Depone runtime identity", "status": "pass", "identity": runtime, "executes_engine": False, "verifies_evidence": False}) install_step = run_command([sys.executable, "-m", "pip", "install", "-e", str(witnessd_root)]) steps.append({"name": "install witnessd editable", "status": "pass", "command": install_step, "executes_engine": False, "verifies_evidence": False}) wrapper_install = run_command([sys.executable, "-m", "pip", "install", "--no-deps", "-e", str(ROOT)]) @@ -593,6 +728,81 @@ def self_test() -> int: lambda: check_existing(witnessd_repo, depone_repo, mismatch, mismatch_lock), checks, ) + + runtime_root = tmp / "runtime-Depone" + runtime_root.mkdir() + write_text(runtime_root / "pyproject.toml", '[project]\nname = "depone"\nversion = "1.2.3"\n') + probe_config = tmp / "runtime-probe.json" + probe = write_text( + tmp / "runtime-probe", + "#!/usr/bin/python3\n" + "import json, sys\n" + f"config = json.loads(open({str(probe_config)!r}, encoding='utf-8').read())\n" + "if config.get('unrunnable'):\n" + " raise SystemExit(1)\n" + "if 'import json, depone;' in sys.argv[-1]:\n" + " print(config.get('module_stdout', ''))\n" + "else:\n" + " print(config.get('installed_version', ''))\n", + ) + probe.chmod(0o755) + locked_runtime_commit = "a" * 40 + + def write_runtime_probe(**overrides: Any) -> None: + payload = { + "module_stdout": json.dumps( + {"depone_file": str(runtime_root / "depone" / "__init__.py"), "module_version": "1.2.3"} + ), + "installed_version": "1.2.3", + } + payload.update(overrides) + probe_config.write_text(json.dumps(payload), encoding="utf-8") + + write_runtime_probe() + identity = depone_runtime_identity(probe, runtime_root, locked_runtime_commit, locked_runtime_commit) + assert identity["depone_file"] == str(runtime_root / "depone" / "__init__.py") + assert identity["installed_version"] == identity["module_version"] == identity["source_version"] == "1.2.3" + assert identity["checkout_commit"] == identity["locked_commit"] == locked_runtime_commit + checks.append({"name": "runtime_identity_all_facts_agree", "status": "pass"}) + + write_runtime_probe(installed_version="9.9.9") + try: + depone_runtime_identity(probe, runtime_root, locked_runtime_commit, locked_runtime_commit) + except BootstrapError as exc: + assert exc.code == DEPONE_RUNTIME_IDENTITY_ERROR + assert exc.details["mismatches"] == ["installed distribution version differs from source version"] + checks.append({"name": "runtime_identity_installed_version_mismatch", "status": "pass", "code": exc.code}) + else: + raise AssertionError("runtime identity installed-version mismatch did not fail") + + write_runtime_probe( + module_stdout=json.dumps( + {"depone_file": str(tmp / "outside" / "depone" / "__init__.py"), "module_version": "1.2.3"} + ) + ) + try: + depone_runtime_identity(probe, runtime_root, locked_runtime_commit, locked_runtime_commit) + except BootstrapError as exc: + assert exc.code == DEPONE_RUNTIME_IDENTITY_ERROR + assert exc.details["mismatches"] == ["depone.__file__ is outside the locked checkout"] + checks.append({"name": "runtime_identity_module_path_mismatch", "status": "pass", "code": exc.code}) + else: + raise AssertionError("runtime identity module-path mismatch did not fail") + + write_runtime_probe(module_stdout="not-json") + expect_error( + "runtime_identity_probe_no_json", + DEPONE_RUNTIME_IDENTITY_ERROR, + lambda: depone_runtime_identity(probe, runtime_root, locked_runtime_commit, locked_runtime_commit), + checks, + ) + write_runtime_probe(unrunnable=True) + expect_error( + "runtime_identity_probe_unrunnable", + DEPONE_RUNTIME_IDENTITY_ERROR, + lambda: depone_runtime_identity(probe, runtime_root, locked_runtime_commit, locked_runtime_commit), + checks, + ) result = { "kind": "orro-bootstrap-self-test-result", "schema_version": SCHEMA_VERSION, diff --git a/scripts/check_orro_removed_command_docs.py b/scripts/check_orro_removed_command_docs.py new file mode 100644 index 0000000..15dc55a --- /dev/null +++ b/scripts/check_orro_removed_command_docs.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Reject removed ORRO command invocations in active documentation.""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +PLAN_PATH = ROOT / "packaging/command-migration-plan.v0.json" +DOC_SUFFIXES = {".md", ".rst", ".txt"} +ALLOWED_PATH_PARTS = {"fixtures", "migration", "compatibility"} + + +def fail(message: str) -> None: + print(f"ORRO removed-command documentation violation: {message}", file=sys.stderr) + raise SystemExit(1) + + +def removed_commands() -> dict[str, str]: + try: + payload = json.loads(PLAN_PATH.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + fail(f"could not read command source {PLAN_PATH}: {exc}") + commands = payload.get("removed_commands") + if not isinstance(commands, dict) or not commands or not all(isinstance(k, str) and isinstance(v, str) for k, v in commands.items()): + fail("command source must contain a non-empty removed_commands string map") + return commands + + +def violation_pattern(commands: dict[str, str]) -> re.Pattern[str]: + names = "|".join(re.escape(name) for name in commands) + return re.compile(rf"\borro\s+(?:{names})(?=\s|`|$)", re.IGNORECASE) + + +def is_allowed(path: Path) -> bool: + relative = path.relative_to(ROOT) + return bool({part.lower() for part in relative.parts} & ALLOWED_PATH_PARTS) + + +def scan(pattern: re.Pattern[str]) -> list[str]: + findings: list[str] = [] + for path in ROOT.rglob("*"): + if not path.is_file() or path.suffix.lower() not in DOC_SUFFIXES or is_allowed(path): + continue + try: + lines = path.read_text(encoding="utf-8").splitlines() + except UnicodeDecodeError: + continue + for line_number, line in enumerate(lines, 1): + if pattern.search(line): + findings.append(f"{path.relative_to(ROOT)}:{line_number}: {line.strip()}") + return findings + + +def self_test() -> None: + pattern = violation_pattern({"next": "auto --dry-run", "report": "status --latest"}) + assert pattern.search("orro next") + assert pattern.search("orro report run") + assert not pattern.search("orro status --latest") + assert is_allowed(ROOT / "tests/fixtures/example.md") + print("ORRO removed-command documentation self-test: pass") + + +def main() -> int: + if "--self-test" in sys.argv[1:]: + self_test() + return 0 + findings = scan(violation_pattern(removed_commands())) + if findings: + fail("\n" + "\n".join(findings)) + print("ORRO removed-command documentation: pass") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/check_orro_repo_contract.py b/scripts/check_orro_repo_contract.py index f86813a..8ba7d23 100644 --- a/scripts/check_orro_repo_contract.py +++ b/scripts/check_orro_repo_contract.py @@ -620,6 +620,7 @@ def check_packaging_decision() -> None: required_paths = [ "scripts/check_orro_packaging_decision.py", "scripts/check_orro_command_migration.py", + "scripts/check_orro_removed_command_docs.py", "scripts/check_orro_command_migration_dry_run.py", "docs/packaging-decision.md", "docs/orro-command-migration.md", @@ -728,6 +729,7 @@ def check_wrapper() -> None: "scripts/orro_build_backend.py", "scripts/check_orro_version_coherence.py", "scripts/check_orro_command_migration.py", + "scripts/check_orro_removed_command_docs.py", "docs/thin-wrapper.md", "docs/wrapper-distribution.md", "docs/orro-command-migration.md", @@ -832,6 +834,7 @@ def check_no_engine_code() -> None: "check_orro_assurance_contract_fixtures.py", "check_orro_fallback_policy.py", "check_orro_command_migration.py", + "check_orro_removed_command_docs.py", "check_orro_command_migration_dry_run.py", "check_orro_packaging_decision.py", "check_orro_repo_contract.py", diff --git a/tests/e2e/README.md b/tests/e2e/README.md index abce820..458a156 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -29,9 +29,9 @@ Pinned-engine smoke checks should: 5. Run `orro proofrun`. 6. Run `orro proofcheck`. 7. Run `orro handoff`. -8. Run `orro report`. +8. Run `orro status --latest`. 9. Assert scout-only artifacts do not pass proofcheck. -10. Assert report does not overclaim. +10. Assert status does not overclaim. 11. Assert handoff requires proofcheck. 12. Assert auto v0 does not run proofrun.