#!/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 health python3 rq_c5a_cli.py --db bootstrap-state python3 rq_c5a_cli.py --db bootstrap-transition --to RECONCILING python3 rq_c5a_cli.py --db set-baseline --commit python3 rq_c5a_cli.py --db ingest-commit --json python3 rq_c5a_cli.py --db process-commit --json python3 rq_c5a_cli.py --db list-commits [--status ] python3 rq_c5a_cli.py --db commit-status --sha python3 rq_c5a_cli.py --db 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())