93 lines
3.8 KiB
Python
Executable File
93 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""CI wrapper for Jarca Ledger DB restore/safety smoke.
|
|
|
|
The wrapper builds a synthetic fixture DB under a temporary directory, copies it
|
|
through the existing restore smoke script, and emits only status-level evidence.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO_ROOT))
|
|
|
|
from jarca_ledger_mcp.db import DEFAULT_DB_PATH
|
|
from scripts.ci_fixture_db import create_ci_fixture_db
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="Run DB safety smoke with synthetic temp DBs only.")
|
|
parser.add_argument("--timeout", type=int, default=45, help="Subprocess timeout in seconds")
|
|
return parser.parse_args()
|
|
|
|
|
|
def parse_json_output(output: str) -> dict[str, Any]:
|
|
try:
|
|
return json.loads(output)
|
|
except json.JSONDecodeError:
|
|
return {"success": False, "error_code": "invalid_json_output"}
|
|
|
|
|
|
def copy_sqlite_backup(source: Path, backup: Path) -> None:
|
|
"""Create a standalone SQLite backup, including data still held in WAL."""
|
|
backup.parent.mkdir(parents=True, exist_ok=True)
|
|
if backup.exists():
|
|
backup.unlink()
|
|
with sqlite3.connect(f"file:{source}?mode=ro", uri=True) as source_conn:
|
|
with sqlite3.connect(backup) as backup_conn:
|
|
source_conn.backup(backup_conn)
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
repo = Path(__file__).resolve().parents[1]
|
|
with tempfile.TemporaryDirectory(prefix="jarca-ledger-ci-db-") as tmp:
|
|
tmp_path = Path(tmp)
|
|
source = tmp_path / "fixture.sqlite3"
|
|
backup = tmp_path / "backup.sqlite3"
|
|
target = tmp_path / "restored.sqlite3"
|
|
try:
|
|
fixture = create_ci_fixture_db(source)
|
|
copy_sqlite_backup(source, backup)
|
|
if any(path.resolve() == DEFAULT_DB_PATH.resolve() for path in [source, backup, target]):
|
|
print(json.dumps({"success": False, "gate": "db-safety-smoke", "error_code": "operating_db_path_refused"}, ensure_ascii=False))
|
|
return 20
|
|
env = os.environ.copy()
|
|
env["JARCA_LEDGER_DB_PATH"] = str(source)
|
|
cmd = [sys.executable, "scripts/ledger_restore_smoke.py", "--backup", str(backup), "--target", str(target)]
|
|
run = subprocess.run(cmd, cwd=repo, env=env, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=args.timeout, check=False)
|
|
except subprocess.TimeoutExpired:
|
|
print(json.dumps({"success": False, "gate": "db-safety-smoke", "error_code": "timeout", "timeout_seconds": args.timeout}, ensure_ascii=False))
|
|
return 21
|
|
except Exception as exc: # noqa: BLE001 - deterministic wrapper failure
|
|
print(json.dumps({"success": False, "gate": "db-safety-smoke", "error_code": "wrapper_error", "error": exc.__class__.__name__}, ensure_ascii=False))
|
|
return 1
|
|
|
|
smoke = parse_json_output(run.stdout)
|
|
success = run.returncode == 0 and bool(smoke.get("success")) and target.exists()
|
|
result = {
|
|
"success": success,
|
|
"gate": "db-safety-smoke",
|
|
"fixture": "synthetic",
|
|
"fixture_integrity": fixture.get("integrity_check"),
|
|
"restore_smoke_exit_code": run.returncode,
|
|
"restored_exists": target.exists(),
|
|
"restored_integrity": smoke.get("target", {}).get("integrity_check"),
|
|
"health_success": smoke.get("health_check", {}).get("success"),
|
|
"operating_db_used": False,
|
|
}
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
return 0 if success else 22
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|