Support VPS local preflight collection
Some checks are pending
CI / lint-and-test (push) Waiting to run

This commit is contained in:
twentyOne2x 2026-07-10 03:41:29 +02:00
parent 637d285ecb
commit 0d70c09f6f
5 changed files with 55 additions and 17 deletions

View file

@ -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

View file

@ -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`

View file

@ -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"
),

View file

@ -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)

View file

@ -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"