# 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 ```bash 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.