Rail-independence is the plan's spine: fedlearn rails verified NOT landed (no m2/m2-core, no capture/curator CLIs), so the wedge builds against frozen interfaces — m2/market-registry as registry of record, ApplyAdapter seam with local adapter now / m2core-sync stub, schemas frozen here with a fedlearn coordination note. Constitution check passed (1 justified deviation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.1 KiB
Contract: m2-ledger HTTP API (v1, frozen)
Base: http://m2-ledger:8000 (coolify network alias) / public via Traefik later if needed.
Auth: X-API-Key header. Two key classes: service (CLI/Store/indexer) and admin
(grants, snapshots). All bodies JSON. Amounts are integer credits.
Endpoints
GET /health
→ 200 {"status":"healthy"}
GET /balance/{operator_id} (service)
→ 200 {"operator_id","balance","as_of"} — balance derived from tx log.
POST /tx/install (service) — the FR-005 money move
Body: {"buyer","seller","amount","platform_pct"?,"ref"} (ref = listing_id + install id)
Semantics: single SQLite transaction — verify balance(buyer) ≥ amount, insert
buyer→seller net row + buyer→platform cut row, insert license grant, return grant.
→ 200 {"tx_ids":[..],"grant":{"grant_id","listing_id","operator_id",...}}
→ 402 {"error":"insufficient_funds","balance"} — nothing written.
→ 409 {"error":"duplicate_ref"} — same ref already settled (idempotency guard).
POST /tx/refund (service)
Body: {"ref"} → compensating refund rows for that install ref (edge case + FR-005).
→ 200 {"tx_ids":[..]}; 404 unknown ref; 409 already refunded.
POST /grant/starter (admin)
Body: {"operator_id","amount"?} (default from config, 100 cr UNCONFIRMED) → mint→operator
grant row. → 200 {"tx_id","balance"}
GET /tx?operator_id=&reason=&since= (service)
→ 200 {"transactions":[...]} append-only log view, paginated.
POST /snapshot (admin, also daily cron)
Dumps all balances → commits audit/YYYY-MM-DD.json to m2/market-registry (Forgejo API,
token from env). → 200 {"path","commit"}
GET /license/{operator_id}/{listing_id} (service)
→ 200 {"grant":...} or 404 — the apply path's pre-check.
Invariants (testable)
- No endpoint UPDATEs/DELETEs tx rows.
balance == fold(tx log)for every operator after any call sequence (SC-004).- Every grant has a non-null paying tx (SC-003).
/tx/installis all-or-nothing and idempotent byref.- Config (not code):
platform_pctdefault 0.10,starter_grantdefault 100.