wedge(T034): snapshot commits to registry audit/ via Forgejo contents API
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5d651705ff
commit
2b84874515
2 changed files with 50 additions and 3 deletions
|
|
@ -143,6 +143,11 @@ def snapshot(conn=Depends(get_conn), _key: str = Depends(require_admin)):
|
||||||
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
|
||||||
path = f"audit/{date}.json"
|
path = f"audit/{date}.json"
|
||||||
content = json.dumps(balances, sort_keys=True, indent=2)
|
content = json.dumps(balances, sort_keys=True, indent=2)
|
||||||
# TODO(T034): commit `content` to `path` in m2/market-registry via Forgejo API
|
from m2_ledger.snapshot import SnapshotError, commit_snapshot
|
||||||
# (REGISTRY_REPO_URL + FORGEJO_TOKEN); this endpoint only produces the JSON.
|
|
||||||
return {"path": path, "content": content}
|
try:
|
||||||
|
result = commit_snapshot(path, content)
|
||||||
|
except SnapshotError as exc:
|
||||||
|
# Snapshot content is still returned so an operator can commit by hand.
|
||||||
|
return {"path": path, "content": content, "committed": False, "error": str(exc)}
|
||||||
|
return {"path": path, "content": content, "committed": True, "commit": result["commit"]}
|
||||||
|
|
|
||||||
42
ledger/src/m2_ledger/snapshot.py
Normal file
42
ledger/src/m2_ledger/snapshot.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
"""Daily balance snapshot → committed to the registry's audit/ (constitution V, T034).
|
||||||
|
|
||||||
|
The commit uses the Forgejo contents API: create the file, or update it (same-day
|
||||||
|
re-runs) with the current blob sha. Config via env: FORGEJO_URL, FORGEJO_TOKEN,
|
||||||
|
REGISTRY_REPO (default m2/market-registry), FORGEJO_USER (default m2).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def commit_snapshot(path: str, content: str) -> dict:
|
||||||
|
forgejo_url = os.environ.get("FORGEJO_URL", "https://git.machinemachine.ai").rstrip("/")
|
||||||
|
token = os.environ.get("FORGEJO_TOKEN", "")
|
||||||
|
repo = os.environ.get("REGISTRY_REPO", "m2/market-registry")
|
||||||
|
user = os.environ.get("FORGEJO_USER", "m2")
|
||||||
|
if not token:
|
||||||
|
raise SnapshotError("FORGEJO_TOKEN not set — snapshot produced but not committed")
|
||||||
|
|
||||||
|
api = f"{forgejo_url}/api/v1/repos/{repo}/contents/{path}"
|
||||||
|
b64 = base64.b64encode(content.encode()).decode()
|
||||||
|
message = f"audit: ledger balance snapshot {path.rsplit('/', 1)[-1]}"
|
||||||
|
|
||||||
|
with httpx.Client(auth=(user, token), timeout=15.0) as client:
|
||||||
|
existing = client.get(api)
|
||||||
|
if existing.status_code == 200:
|
||||||
|
sha = existing.json().get("sha")
|
||||||
|
resp = client.put(api, json={"content": b64, "message": message, "sha": sha})
|
||||||
|
else:
|
||||||
|
resp = client.post(api, json={"content": b64, "message": message})
|
||||||
|
if resp.status_code not in (200, 201):
|
||||||
|
raise SnapshotError(f"registry commit failed: {resp.status_code} {resp.text[:200]}")
|
||||||
|
data = resp.json()
|
||||||
|
return {"path": path, "commit": (data.get("commit") or {}).get("sha")}
|
||||||
Loading…
Reference in a new issue