281 lines
10 KiB
Python
281 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Collect live read-only production preflight output for the Working Leo packet.
|
|
|
|
This tool runs only the preflight SQL files from the authorized apply runbook.
|
|
It does not run apply, postflight, or rollback SQL. Before any SQL is sent to a
|
|
database, each file is statically checked for mutating SQL and then wrapped in a
|
|
Postgres read-only transaction. The intended use is to attach fresh row-level
|
|
preflight evidence to an operator receipt without crossing the production apply
|
|
authorization boundary.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import build_working_leo_authorized_apply_runbook as runbook
|
|
|
|
REPO_REPORT_DIR = Path("docs/reports/leo-working-state-20260709")
|
|
DEFAULT_OUT = REPO_REPORT_DIR / "working-leo-production-preflight-current.json"
|
|
DEFAULT_MARKDOWN_OUT = REPO_REPORT_DIR / "working-leo-production-preflight-current.md"
|
|
DEFAULT_SSH_TARGET = "root@77.42.65.182"
|
|
DEFAULT_SSH_KEY = Path.home() / ".ssh/livingip_hetzner_20260604_ed25519"
|
|
|
|
FORBIDDEN_SQL_TOKENS = {
|
|
"alter",
|
|
"analyze",
|
|
"begin",
|
|
"call",
|
|
"cluster",
|
|
"commit",
|
|
"copy",
|
|
"create",
|
|
"delete",
|
|
"drop",
|
|
"execute",
|
|
"grant",
|
|
"insert",
|
|
"merge",
|
|
"refresh",
|
|
"reindex",
|
|
"revoke",
|
|
"rollback",
|
|
"truncate",
|
|
"update",
|
|
"vacuum",
|
|
}
|
|
|
|
|
|
def _sha256_text(text: str) -> str:
|
|
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def _strip_sql_for_token_scan(sql: str) -> str:
|
|
without_block_comments = re.sub(r"/\*.*?\*/", " ", sql, flags=re.S)
|
|
without_line_comments = re.sub(r"--.*?$", " ", without_block_comments, flags=re.M)
|
|
without_strings = re.sub(r"'(?:''|[^'])*'", "''", without_line_comments)
|
|
without_meta = "\n".join(
|
|
line for line in without_strings.splitlines() if not line.lstrip().startswith("\\")
|
|
)
|
|
return without_meta
|
|
|
|
|
|
def forbidden_tokens(sql: str) -> list[str]:
|
|
scan = _strip_sql_for_token_scan(sql)
|
|
tokens = {match.group(0).lower() for match in re.finditer(r"\b[a-z_]+\b", scan, flags=re.I)}
|
|
return sorted(tokens & FORBIDDEN_SQL_TOKENS)
|
|
|
|
|
|
def readonly_sql(sql: str) -> str:
|
|
body_lines = [
|
|
line for line in sql.splitlines() if line.strip().lower() != r"\set on_error_stop on"
|
|
]
|
|
body = "\n".join(body_lines).strip()
|
|
return f"\\set ON_ERROR_STOP on\nbegin transaction read only;\n{body}\ncommit;\n"
|
|
|
|
|
|
def preflight_entries(report_dir: Path = REPO_REPORT_DIR) -> list[dict[str, Any]]:
|
|
generated = runbook.build_runbook(report_dir)
|
|
return generated["phases"]["preflight_order"]
|
|
|
|
|
|
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),
|
|
"-o",
|
|
"BatchMode=yes",
|
|
"-o",
|
|
"StrictHostKeyChecking=accept-new",
|
|
"-o",
|
|
f"ConnectTimeout={args.connect_timeout}",
|
|
args.ssh_target,
|
|
docker_psql,
|
|
]
|
|
|
|
|
|
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} "
|
|
f"'docker exec -i {args.container} psql -U {args.db_user} -d {args.db} "
|
|
"-v ON_ERROR_STOP=1 -P pager=off'"
|
|
)
|
|
|
|
|
|
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_command(args)
|
|
|
|
for index, entry in enumerate(entries, start=1):
|
|
path = args.report_dir / entry["file"]
|
|
sql = path.read_text(encoding="utf-8")
|
|
forbidden = forbidden_tokens(sql)
|
|
wrapped = readonly_sql(sql)
|
|
result: dict[str, Any] = {
|
|
"index": index,
|
|
"packet_id": entry["packet_id"],
|
|
"preflight_file": entry["file"],
|
|
"path": str(path),
|
|
"sql_sha256": _sha256_text(sql),
|
|
"readonly_sql_sha256": _sha256_text(wrapped),
|
|
"forbidden_tokens": forbidden,
|
|
"static_readonly_ok": not forbidden,
|
|
"executed": False,
|
|
"ok": False,
|
|
}
|
|
if forbidden:
|
|
result["error"] = "preflight SQL contains forbidden mutating or transaction token(s)"
|
|
results.append(result)
|
|
continue
|
|
|
|
if args.execute:
|
|
proc = subprocess.run(
|
|
command,
|
|
input=wrapped,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=args.timeout,
|
|
check=False,
|
|
)
|
|
result.update(
|
|
{
|
|
"executed": True,
|
|
"returncode": proc.returncode,
|
|
"stdout": proc.stdout,
|
|
"stderr": proc.stderr,
|
|
"ok": proc.returncode == 0,
|
|
}
|
|
)
|
|
else:
|
|
result.update({"ok": True, "stdout": "", "stderr": ""})
|
|
results.append(result)
|
|
|
|
status = "pass" if all(item["ok"] for item in results) else "fail"
|
|
return {
|
|
"generated_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
"mode": mode,
|
|
"status": status,
|
|
"execute_requested": args.execute,
|
|
"mutates_production_db": False,
|
|
"applies_production_packet": False,
|
|
"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"]),
|
|
"failures": [item for item in results if not item["ok"]],
|
|
"results": results,
|
|
"claim_ceiling": (
|
|
"This is read-only production preflight evidence. It can prove current row-level readiness or drift before "
|
|
"an apply window. It does not authorize production apply and does not prove canonical rows were changed."
|
|
),
|
|
}
|
|
|
|
|
|
def write_report(path: Path, report: dict[str, Any]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
|
|
|
|
def write_markdown(path: Path, report: dict[str, Any]) -> None:
|
|
lines = [
|
|
"# Working Leo Production Preflight",
|
|
"",
|
|
f"Generated UTC: `{report['generated_at_utc']}`",
|
|
f"Mode: `{report['mode']}`",
|
|
f"Status: `{report['status']}`",
|
|
f"Executed: `{report['execute_requested']}`",
|
|
f"Mutates production DB: `{report['mutates_production_db']}`",
|
|
f"Applies production packet: `{report['applies_production_packet']}`",
|
|
f"Production apply executed: `{report['production_apply_executed']}`",
|
|
f"Read-only transaction wrapper: `{report['readonly_transaction_wrapper']}`",
|
|
"",
|
|
"## Results",
|
|
"",
|
|
]
|
|
for item in report["results"]:
|
|
lines.append(
|
|
f"- `{item['packet_id']}` / `{item['preflight_file']}`: "
|
|
f"`ok={item['ok']}`, `executed={item['executed']}`, "
|
|
f"`forbidden_tokens={item['forbidden_tokens']}`"
|
|
)
|
|
if item.get("returncode") is not None:
|
|
lines.append(f" - returncode: `{item['returncode']}`")
|
|
stdout = (item.get("stdout") or "").strip()
|
|
stderr = (item.get("stderr") or "").strip()
|
|
if stdout:
|
|
lines.extend([" - stdout:", "", "```text", stdout, "```", ""])
|
|
if stderr:
|
|
lines.extend([" - stderr:", "", "```text", stderr, "```", ""])
|
|
lines.extend(["", "## Claim Ceiling", "", report["claim_ceiling"], ""])
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("\n".join(lines), encoding="utf-8")
|
|
|
|
|
|
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--report-dir", type=Path, default=REPO_REPORT_DIR)
|
|
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)
|
|
parser.add_argument("--timeout", type=int, default=120)
|
|
parser.add_argument("--container", default="teleo-pg")
|
|
parser.add_argument("--db-user", default="postgres")
|
|
parser.add_argument("--db", default="teleo")
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parse_args(argv)
|
|
report = collect_preflight(args)
|
|
write_report(args.out, report)
|
|
write_markdown(args.markdown_out, report)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"out": str(args.out),
|
|
"markdown_out": str(args.markdown_out),
|
|
"status": report["status"],
|
|
"execute_requested": report["execute_requested"],
|
|
"passes": report["passes"],
|
|
"preflight_count": report["preflight_count"],
|
|
},
|
|
indent=2,
|
|
sort_keys=True,
|
|
)
|
|
)
|
|
return 0 if report["status"] == "pass" else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|