m2-market/ledger
m2 (AI Agent) 83cda680d2 wedge: T026 integration tests (real ledger via uvicorn, real bundle, temp HOME)
Written inline after two headless workers died on API errors. Tests caught a
real cross-slice bug: ledger grants stored the full install ref as listing_id,
breaking GET /license/{op}/{listing} — fixed by parsing the listing prefix per
the ref convention in contracts/ledger-api.md. Also pinned the indexer test's
schema path (venv-installed package resolved outside the repo) — its earlier
pass was vacuous without jsonschema installed. 27 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 03:34:21 +02:00
..
src/m2_ledger wedge: T026 integration tests (real ledger via uvicorn, real bundle, temp HOME) 2026-07-02 03:34:21 +02:00
tests wedge: T013 property tests + T014 Dockerfile merged; fix zero-amount install rows 2026-07-02 03:17:07 +02:00
Dockerfile T014: ledger Dockerfile + deploy notes 2026-07-02 03:14:18 +02:00
pyproject.toml wedge(T001-T003): monorepo skeleton, uv workspaces, ruff+pytest config 2026-07-02 02:19:47 +02:00
README.md T014: ledger Dockerfile + deploy notes 2026-07-02 03:14:18 +02:00

m2-ledger

Append-only credits ledger — transactions {ts, from, to, amount, reason, ref}, balances always derived by folding the tx log (never stored authoritatively). See specs/001-market-first-wedge/contracts/ledger-api.md (frozen v1 contract) and docs/config.md (env var / key reference) for the full spec.

Run locally

cd ledger
uv sync
LEDGER_DB_PATH=./ledger.db \
LEDGER_SERVICE_KEYS=svc_devkey \
LEDGER_ADMIN_KEYS=admin_devkey \
  uv run uvicorn m2_ledger.api:app --reload --port 8000

GET http://localhost:8000/health should return {"status":"healthy"}. All other endpoints require an X-API-Key header (service or admin class — see docs/config.md §4).

Deploy as a Coolify app

m2-ledger is the one sanctioned new service in the first wedge (constitution I); it deploys as a plain Coolify app on the coolify Docker network, not baked into any image.

  1. Build: point the Coolify app at this repo/subpath (ledger/) with ledger/Dockerfile — no build args, no secrets at build time (constitution VI: no secrets in repos or images).
  2. Network + alias: attach the app to the coolify network with alias m2-ledger, so callers use http://m2-ledger:8000 per the frozen contract's base URL.
  3. Env vars (inject at runtime via Coolify's environment panel — never commit real values, see docs/config.md §5):
    • LEDGER_DB_PATH=/data/ledger.db (already set as the image default; override only if the persistent volume is mounted elsewhere)
    • LEDGER_SERVICE_KEYS — comma-separated service-class keys (CLI/Store/indexer)
    • LEDGER_ADMIN_KEYS — comma-separated admin-class keys (grants, snapshots); keep this set small
    • LEDGER_PLATFORM_PCT (default 0.10, UNCONFIRMED) and LEDGER_STARTER_GRANT (default 100, UNCONFIRMED) — optional, override the code defaults
    • REGISTRY_REPO_URL + FORGEJO_TOKEN — required once /snapshot actually commits to m2/market-registry (see TODO below)
  4. Persistent volume: mount a Coolify persistent volume at /data (the image declares VOLUME /data) so ledger.db survives redeploys. This is the single source of truth for the tx log — never point it at ephemeral container storage.
  5. Health check: the image's HEALTHCHECK hits GET /health (no auth). Coolify should also be configured to poll the same endpoint for its own readiness gate.
  6. Canary-first (constitution VI): roll out to one environment before any fleet-wide dependency on m2-ledger — there is no fleet-wide blast for this service since it's a single shared instance, but verify /health and one /balance/{operator_id} call with a real service key before pointing the CLI's ledger_url at it in ~/.m2-market/config.toml.

Snapshot cron (TODO T034)

POST /snapshot (admin) currently computes audit/YYYY-MM-DD.json balances and returns the JSON body but does not commit it anywhere — see the TODO(T034) in src/m2_ledger/api.py. Once T034 lands (Forgejo commit via REGISTRY_REPO_URL + FORGEJO_TOKEN), add a daily Coolify scheduled task (or cron container) that calls POST /snapshot with an admin key once every 24h, per constitution V ("Daily balance snapshots MUST be committed to Forgejo for audit") and the contract's "admin, also daily cron" note. Not wired up yet.