162 lines
5.2 KiB
Python
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())
|