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:
m2 (AI Agent) 2026-07-02 04:12:46 +02:00
parent 5d651705ff
commit 2b84874515
2 changed files with 50 additions and 3 deletions

View file

@ -143,6 +143,11 @@ def snapshot(conn=Depends(get_conn), _key: str = Depends(require_admin)):
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
path = f"audit/{date}.json"
content = json.dumps(balances, sort_keys=True, indent=2)
# TODO(T034): commit `content` to `path` in m2/market-registry via Forgejo API
# (REGISTRY_REPO_URL + FORGEJO_TOKEN); this endpoint only produces the JSON.
return {"path": path, "content": content}
from m2_ledger.snapshot import SnapshotError, commit_snapshot
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"]}

View 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")}