Files
hermes-relay/scripts/certify-upstream-isolation.py

118 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Run the bounded upstream half of the HRUI-070/071/081/161 matrix.
The harness deliberately executes upstream's own fixtures instead of copying
its registries, cron stores, or session database logic into Hermes-Relay.
Run it from an exact detached hermes-agent checkout; live delivery and physical
client checks remain a separate certification step.
"""
from __future__ import annotations
import argparse
import subprocess
import sys
from pathlib import Path
MINIMUM_COMMITS = {
"HRUI-070": "bb597e1c02",
"HRUI-071": "a8ec50f68",
"HRUI-081": "48e12a06f",
"HRUI-161": "8be9c76f8",
}
MATRIX: dict[str, tuple[tuple[tuple[str, ...], str | None], ...]] = {
"HRUI-070": (
(("tests/cron/test_scheduler_provider.py",), "multiplex_ticker_ticks_each_profile_once"),
(("tests/cron/test_run_one_job.py",), "installs_secret_scope_under_multiplex"),
(("tests/cron/test_scheduler.py",), "parallel_jobs_isolated_contextvars or live_adapter_timeout_assumes_delivered_no_duplicate"),
(("tests/cron/test_relay_fronted_delivery.py",), None),
),
"HRUI-071": (
(("tests/tui_gateway/test_session_resume_db_ownership.py",), None),
(("tests/tui_gateway/test_session_db_ownership_teardown.py",), None),
),
"HRUI-081": (
(("tests/tools/test_tool_search.py",), "empty_search_keeps_connected_sources_discoverable or search_catalog_is_scoped_to_session_toolsets or default_listing_cap_bounds_fixed_catalog_overhead"),
),
"HRUI-161": (
(("tests/hermes_cli/test_plugins.py",), "plugin_manager_scoped_by_hermes_home_override or force_reload_re_registers_shell_hooks or unload_all_sweeps_preledger_tool_names"),
(("tests/hermes_cli/test_plugin_prompt_sections.py",), None),
),
}
def _run(repo: Path, *args: str, capture: bool = False) -> subprocess.CompletedProcess[str]:
return subprocess.run(
args,
cwd=repo,
check=False,
text=True,
capture_output=capture,
)
def _git_text(repo: Path, *args: str) -> str:
result = _run(repo, "git", *args, capture=True)
if result.returncode:
raise SystemExit(result.stderr.strip() or "git command failed")
return result.stdout.strip()
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--hermes-source", required=True, type=Path)
parser.add_argument("--revision", required=True)
parser.add_argument(
"--lane",
action="append",
choices=tuple(MATRIX),
help="Run one lane (repeatable); defaults to all four",
)
args = parser.parse_args()
repo = args.hermes_source.resolve()
expected = _git_text(repo, "rev-parse", f"{args.revision}^{{commit}}")
head = _git_text(repo, "rev-parse", "HEAD")
if head != expected:
raise SystemExit(
f"refusing source drift: checkout HEAD is {head}, expected {expected}"
)
if _run(repo, "git", "symbolic-ref", "-q", "HEAD").returncode == 0:
raise SystemExit("refusing mutable branch checkout: detach HEAD at the revision")
status = _git_text(repo, "status", "--porcelain", "--untracked-files=all")
if status:
raise SystemExit("refusing dirty source: tracked or untracked files would affect pytest")
lanes = args.lane or list(MATRIX)
for lane in lanes:
minimum = MINIMUM_COMMITS[lane]
ancestry = _run(repo, "git", "merge-base", "--is-ancestor", minimum, "HEAD")
if ancestry.returncode:
raise SystemExit(f"{lane}: HEAD does not contain required {minimum}")
failures = 0
for lane in lanes:
print(f"\n== {lane} ==", flush=True)
for paths, expression in MATRIX[lane]:
command = [
sys.executable,
"-m",
"pytest",
"-q",
"-p",
"no:cacheprovider",
*paths,
]
if expression:
command.extend(("-k", expression))
result = _run(repo, *command)
failures += int(result.returncode != 0)
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())