From 0d70c09f6f6dd7f132197afecd4ffdab9c217f67 Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Fri, 10 Jul 2026 03:41:29 +0200 Subject: [PATCH] Support VPS local preflight collection --- ...-leo-authorized-apply-runbook-current.json | 6 ++-- ...ng-leo-authorized-apply-runbook-current.md | 6 ++-- ...ld_working_leo_authorized_apply_runbook.py | 5 ++-- ...ollect_working_leo_production_preflight.py | 27 ++++++++++++------ ...ollect_working_leo_production_preflight.py | 28 ++++++++++++++++++- 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.json b/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.json index f76ee60..6cc35a4 100644 --- a/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.json +++ b/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.json @@ -27,7 +27,7 @@ "rio_strategy_nodes": 2, "sources": 28 }, - "generated_at_utc": "2026-07-10T01:39:08.583122+00:00", + "generated_at_utc": "2026-07-10T01:41:16.353240+00:00", "mode": "operator_runbook_generation_only", "operator_environment": { "operator_commands_run_sql_if_executed": true, @@ -337,8 +337,8 @@ "pre_apply_validation_commands": [ { "applies_production_packet": false, - "command_template": ".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md", - "description": "Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase.", + "command_template": ".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --transport ssh --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md", + "description": "Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase. Use --transport local-docker when running this collector directly on the VPS.", "expected_artifact": "docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md", "id": "readonly_production_preflight_collector", "mutates_production_db": false diff --git a/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.md b/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.md index 141f6a5..61c07d5 100644 --- a/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.md +++ b/docs/reports/leo-working-state-20260709/working-leo-authorized-apply-runbook-current.md @@ -1,6 +1,6 @@ # Working Leo Authorized Apply Runbook -Generated UTC: `2026-07-10T01:39:08.583122+00:00` +Generated UTC: `2026-07-10T01:41:16.353240+00:00` Mode: `operator_runbook_generation_only` Status: `ready_not_executed` Ready for authorized operator: `True` @@ -94,8 +94,8 @@ Do not run apply commands unless the operator explicitly authorizes production D ## Pre-Apply Validation Commands -- `readonly_production_preflight_collector`: Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase. - - command: `.venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md` +- `readonly_production_preflight_collector`: Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase. Use --transport local-docker when running this collector directly on the VPS. + - command: `.venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --transport ssh --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md` - artifact: `docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md` - mutates production DB: `False` - applies production packet: `False` diff --git a/scripts/build_working_leo_authorized_apply_runbook.py b/scripts/build_working_leo_authorized_apply_runbook.py index 5b844d5..b5c9f2a 100644 --- a/scripts/build_working_leo_authorized_apply_runbook.py +++ b/scripts/build_working_leo_authorized_apply_runbook.py @@ -138,10 +138,11 @@ PRE_APPLY_VALIDATION_COMMANDS: list[dict[str, Any]] = [ "id": "readonly_production_preflight_collector", "description": ( "Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. " - "During an authorized apply window, still run each phase preflight again immediately before its phase." + "During an authorized apply window, still run each phase preflight again immediately before its phase. " + "Use --transport local-docker when running this collector directly on the VPS." ), "command_template": ( - ".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute " + ".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --transport ssh " "--out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json " "--markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md" ), diff --git a/scripts/collect_working_leo_production_preflight.py b/scripts/collect_working_leo_production_preflight.py index 9ca59fc..1becb12 100644 --- a/scripts/collect_working_leo_production_preflight.py +++ b/scripts/collect_working_leo_production_preflight.py @@ -86,8 +86,11 @@ def preflight_entries(report_dir: Path = REPO_REPORT_DIR) -> list[dict[str, Any] return generated["phases"]["preflight_order"] -def build_ssh_command(args: argparse.Namespace) -> list[str]: - command = [ +def build_command(args: argparse.Namespace) -> list[str]: + docker_psql = f"docker exec -i {args.container} psql -U {args.db_user} -d {args.db} -v ON_ERROR_STOP=1 -P pager=off" + if args.transport == "local-docker": + return docker_psql.split() + return [ "ssh", "-i", str(args.ssh_key), @@ -98,15 +101,16 @@ def build_ssh_command(args: argparse.Namespace) -> list[str]: "-o", f"ConnectTimeout={args.connect_timeout}", args.ssh_target, - ( - f"docker exec -i {args.container} " - f"psql -U {args.db_user} -d {args.db} -v ON_ERROR_STOP=1 -P pager=off" - ), + docker_psql, ] - return command def command_for_report(args: argparse.Namespace) -> str: + if args.transport == "local-docker": + return ( + f"docker exec -i {args.container} psql -U {args.db_user} -d {args.db} " + "-v ON_ERROR_STOP=1 -P pager=off" + ) return ( "ssh -i [SSH_KEY] -o BatchMode=yes -o StrictHostKeyChecking=accept-new " f"-o ConnectTimeout={args.connect_timeout} {args.ssh_target} " @@ -119,7 +123,7 @@ def collect_preflight(args: argparse.Namespace) -> dict[str, Any]: entries = preflight_entries(args.report_dir) results: list[dict[str, Any]] = [] mode = "live_vps_readonly_preflight" if args.execute else "validated_not_executed" - command = build_ssh_command(args) + command = build_command(args) for index, entry in enumerate(entries, start=1): path = args.report_dir / entry["file"] @@ -176,6 +180,7 @@ def collect_preflight(args: argparse.Namespace) -> dict[str, Any]: "production_apply_executed": False, "readonly_transaction_wrapper": True, "ssh_target": args.ssh_target if args.execute else None, + "transport": args.transport, "command_redacted": command_for_report(args) if args.execute else None, "preflight_count": len(results), "passes": sum(1 for item in results if item["ok"]), @@ -234,6 +239,12 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser.add_argument("--out", type=Path, default=DEFAULT_OUT) parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT) parser.add_argument("--execute", action="store_true", help="run the preflight SQL against the VPS") + parser.add_argument( + "--transport", + choices=["ssh", "local-docker"], + default="ssh", + help="Use ssh from an operator machine, or local-docker when already running on the VPS.", + ) parser.add_argument("--ssh-target", default=DEFAULT_SSH_TARGET) parser.add_argument("--ssh-key", type=Path, default=DEFAULT_SSH_KEY) parser.add_argument("--connect-timeout", type=int, default=15) diff --git a/tests/test_collect_working_leo_production_preflight.py b/tests/test_collect_working_leo_production_preflight.py index 9ba7028..1e32ac8 100644 --- a/tests/test_collect_working_leo_production_preflight.py +++ b/tests/test_collect_working_leo_production_preflight.py @@ -12,12 +12,13 @@ sys.path.insert(0, str(REPO_ROOT / "scripts")) import collect_working_leo_production_preflight as preflight # noqa: E402 -def _args(tmp_path: Path, *, execute: bool = False) -> argparse.Namespace: +def _args(tmp_path: Path, *, execute: bool = False, transport: str = "ssh") -> argparse.Namespace: return argparse.Namespace( report_dir=REPO_ROOT / preflight.REPO_REPORT_DIR, out=tmp_path / "preflight.json", markdown_out=tmp_path / "preflight.md", execute=execute, + transport=transport, ssh_target="root@example.invalid", ssh_key=tmp_path / "ssh-key", connect_timeout=3, @@ -89,6 +90,31 @@ def test_execute_mode_wraps_sql_readonly_and_uses_ssh(monkeypatch, tmp_path: Pat assert call["input"].rstrip().endswith("commit;") +def test_execute_mode_can_run_local_docker_on_vps(monkeypatch, tmp_path: Path): + calls = [] + + class Completed: + returncode = 0 + stdout = "ok\n" + stderr = "" + + def fake_run(command, *, input, text, capture_output, timeout, check): + calls.append({"command": command, "input": input}) + return Completed() + + monkeypatch.setattr(preflight.subprocess, "run", fake_run) + + report = preflight.collect_preflight(_args(tmp_path, execute=True, transport="local-docker")) + + assert report["status"] == "pass" + assert report["transport"] == "local-docker" + assert len(calls) == 5 + for call in calls: + assert call["command"][:3] == ["docker", "exec", "-i"] + assert "ssh" not in call["command"] + assert call["input"].startswith("\\set ON_ERROR_STOP on\nbegin transaction read only;\n") + + def test_markdown_preserves_claim_ceiling(tmp_path: Path): report = preflight.collect_preflight(_args(tmp_path)) out = tmp_path / "preflight.md"