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, "rio_strategy_nodes": 2,
"sources": 28 "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", "mode": "operator_runbook_generation_only",
"operator_environment": { "operator_environment": {
"operator_commands_run_sql_if_executed": true, "operator_commands_run_sql_if_executed": true,
@ -337,8 +337,8 @@
"pre_apply_validation_commands": [ "pre_apply_validation_commands": [
{ {
"applies_production_packet": false, "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", "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.", "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", "expected_artifact": "docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md",
"id": "readonly_production_preflight_collector", "id": "readonly_production_preflight_collector",
"mutates_production_db": false "mutates_production_db": false

View file

@ -1,6 +1,6 @@
# Working Leo Authorized Apply Runbook # 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` Mode: `operator_runbook_generation_only`
Status: `ready_not_executed` Status: `ready_not_executed`
Ready for authorized operator: `True` 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 ## 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. - `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 --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` - 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` - artifact: `docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md`
- mutates production DB: `False` - mutates production DB: `False`
- applies production packet: `False` - applies production packet: `False`

View file

@ -139,9 +139,10 @@ PRE_APPLY_VALIDATION_COMMANDS: list[dict[str, Any]] = [
"description": ( "description": (
"Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. " "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": ( "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 " "--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" "--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"] return generated["phases"]["preflight_order"]
def build_ssh_command(args: argparse.Namespace) -> list[str]: def build_command(args: argparse.Namespace) -> list[str]:
command = [ 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", "ssh",
"-i", "-i",
str(args.ssh_key), str(args.ssh_key),
@ -98,15 +101,16 @@ def build_ssh_command(args: argparse.Namespace) -> list[str]:
"-o", "-o",
f"ConnectTimeout={args.connect_timeout}", f"ConnectTimeout={args.connect_timeout}",
args.ssh_target, args.ssh_target,
( docker_psql,
f"docker exec -i {args.container} "
f"psql -U {args.db_user} -d {args.db} -v ON_ERROR_STOP=1 -P pager=off"
),
] ]
return command
def command_for_report(args: argparse.Namespace) -> str: 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 ( return (
"ssh -i [SSH_KEY] -o BatchMode=yes -o StrictHostKeyChecking=accept-new " "ssh -i [SSH_KEY] -o BatchMode=yes -o StrictHostKeyChecking=accept-new "
f"-o ConnectTimeout={args.connect_timeout} {args.ssh_target} " 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) entries = preflight_entries(args.report_dir)
results: list[dict[str, Any]] = [] results: list[dict[str, Any]] = []
mode = "live_vps_readonly_preflight" if args.execute else "validated_not_executed" 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): for index, entry in enumerate(entries, start=1):
path = args.report_dir / entry["file"] path = args.report_dir / entry["file"]
@ -176,6 +180,7 @@ def collect_preflight(args: argparse.Namespace) -> dict[str, Any]:
"production_apply_executed": False, "production_apply_executed": False,
"readonly_transaction_wrapper": True, "readonly_transaction_wrapper": True,
"ssh_target": args.ssh_target if args.execute else None, "ssh_target": args.ssh_target if args.execute else None,
"transport": args.transport,
"command_redacted": command_for_report(args) if args.execute else None, "command_redacted": command_for_report(args) if args.execute else None,
"preflight_count": len(results), "preflight_count": len(results),
"passes": sum(1 for item in results if item["ok"]), "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("--out", type=Path, default=DEFAULT_OUT)
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_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("--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-target", default=DEFAULT_SSH_TARGET)
parser.add_argument("--ssh-key", type=Path, default=DEFAULT_SSH_KEY) parser.add_argument("--ssh-key", type=Path, default=DEFAULT_SSH_KEY)
parser.add_argument("--connect-timeout", type=int, default=15) 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 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( return argparse.Namespace(
report_dir=REPO_ROOT / preflight.REPO_REPORT_DIR, report_dir=REPO_ROOT / preflight.REPO_REPORT_DIR,
out=tmp_path / "preflight.json", out=tmp_path / "preflight.json",
markdown_out=tmp_path / "preflight.md", markdown_out=tmp_path / "preflight.md",
execute=execute, execute=execute,
transport=transport,
ssh_target="root@example.invalid", ssh_target="root@example.invalid",
ssh_key=tmp_path / "ssh-key", ssh_key=tmp_path / "ssh-key",
connect_timeout=3, 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;") 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): def test_markdown_preserves_claim_ceiling(tmp_path: Path):
report = preflight.collect_preflight(_args(tmp_path)) report = preflight.collect_preflight(_args(tmp_path))
out = tmp_path / "preflight.md" out = tmp_path / "preflight.md"