trading-system-docs/tolaria/c5-sync-service/rq_c5a_cli.py

162 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""
Red Queen — C5A: CONTRACT & STATE MACHINE CLI.
Deterministische, inaktive CLI zur Inspektion und Steuerung der C5A State Machine.
Führt KEINE externen Writes aus (kein Polling, kein Tolaria-Write, kein Search-Rebuild).
Nutzung:
python3 rq_c5a_cli.py --db <path> health
python3 rq_c5a_cli.py --db <path> bootstrap-state
python3 rq_c5a_cli.py --db <path> bootstrap-transition --to RECONCILING
python3 rq_c5a_cli.py --db <path> set-baseline --commit <sha>
python3 rq_c5a_cli.py --db <path> ingest-commit --json <file>
python3 rq_c5a_cli.py --db <path> process-commit --json <file>
python3 rq_c5a_cli.py --db <path> list-commits [--status <s>]
python3 rq_c5a_cli.py --db <path> commit-status --sha <sha>
python3 rq_c5a_cli.py --db <path> no-write-check
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from rq_c5a import (
C5AStore,
SyncStateMachine,
assert_no_write_guarantee,
C5AError,
InvalidTransitionError,
InvalidReasonCodeError,
InvalidOperationError,
)
def _load_json(path: str) -> dict:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def cmd_health(store: C5AStore, args) -> int:
print(json.dumps(store.health(), indent=2, ensure_ascii=False))
return 0
def cmd_bootstrap_state(store: C5AStore, args) -> int:
print(store.bootstrap_state())
return 0
def cmd_bootstrap_transition(store: C5AStore, args) -> int:
try:
new_state = store.bootstrap_transition(args.to)
except InvalidTransitionError as e:
print(json.dumps(e.to_dict(), ensure_ascii=False))
return 2
print(new_state)
return 0
def cmd_set_baseline(store: C5AStore, args) -> int:
try:
store.set_baseline(args.commit)
except C5AError as e:
print(json.dumps(e.to_dict(), ensure_ascii=False))
return 2
print(f"baseline={args.commit}")
return 0
def cmd_ingest_commit(store: C5AStore, args) -> int:
commit = _load_json(args.json)
store.upsert_commit(commit)
for obj in commit.get("changed_objects", []):
obj["commit_sha"] = commit["commit_sha"]
store.add_object_change(obj)
print(json.dumps({"commit_sha": commit["commit_sha"], "status": "ingested"}, ensure_ascii=False))
return 0
def cmd_process_commit(store: C5AStore, args) -> int:
commit = _load_json(args.json)
sm = SyncStateMachine(store)
try:
result = sm.process_commit(commit)
except C5AError as e:
print(json.dumps(e.to_dict(), ensure_ascii=False))
return 2
print(json.dumps(result, ensure_ascii=False))
return 0
def cmd_list_commits(store: C5AStore, args) -> int:
commits = store.list_commits(status=args.status)
print(json.dumps(commits, indent=2, ensure_ascii=False))
return 0
def cmd_commit_status(store: C5AStore, args) -> int:
status = store.commit_status(args.sha)
if status is None:
print(json.dumps({"error": f"Commit nicht gefunden: {args.sha}"}, ensure_ascii=False))
return 2
print(status)
return 0
def cmd_no_write_check(store: C5AStore, args) -> int:
result = assert_no_write_guarantee()
print(json.dumps(result, indent=2, ensure_ascii=False))
return 0 if result["no_write_guarantee"] else 2
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description="C5A Contract & State Machine CLI")
parser.add_argument("--db", default=os_env_db(), help="Pfad zur c5a.db (default: $C5A_DB oder ./c5a.db)")
sub = parser.add_subparsers(dest="cmd", required=True)
sub.add_parser("health", help="Health Contract anzeigen")
sub.add_parser("bootstrap-state", help="Bootstrap-Zustand anzeigen")
p = sub.add_parser("bootstrap-transition", help="Bootstrap-Übergang")
p.add_argument("--to", required=True)
p = sub.add_parser("set-baseline", help="Baseline setzen")
p.add_argument("--commit", required=True)
p = sub.add_parser("ingest-commit", help="Commit + Object-Changes aufnehmen")
p.add_argument("--json", required=True)
p = sub.add_parser("process-commit", help="Commit durch State Machine verarbeiten")
p.add_argument("--json", required=True)
p = sub.add_parser("list-commits", help="Commits auflisten")
p.add_argument("--status")
p = sub.add_parser("commit-status", help="Status eines Commits")
p.add_argument("--sha", required=True)
sub.add_parser("no-write-check", help="No-Write-Guarantee prüfen")
args = parser.parse_args(argv)
store = C5AStore(args.db)
try:
handlers = {
"health": cmd_health,
"bootstrap-state": cmd_bootstrap_state,
"bootstrap-transition": cmd_bootstrap_transition,
"set-baseline": cmd_set_baseline,
"ingest-commit": cmd_ingest_commit,
"process-commit": cmd_process_commit,
"list-commits": cmd_list_commits,
"commit-status": cmd_commit_status,
"no-write-check": cmd_no_write_check,
}
return handlers[args.cmd](store, args)
finally:
store.close()
def os_env_db() -> str:
import os
return os.environ.get("C5A_DB", "c5a.db")
if __name__ == "__main__":
sys.exit(main())