Files
neuron-tai/docs/issues/39-caller-credit-account-keys.md
2026-07-06 14:17:36 +03:00

61 lines
3.2 KiB
Markdown

# US-039 — Caller Credit granted once per account; chat requires account keys
Status: planned
Priority: Critical (blocks friends-test inference — every request 402s)
Stage: Designed
## Context
`DEFAULT_STARTING_CREDIT = 0.0` and nothing can raise it: no CLI flag, no admin
credit endpoint. The only funding path is the on-chain USDT deposit watcher —
so on a fresh public tracker every inference request returns
`402 insufficient balance`, including keys created through the working
account flow (`/v1/auth/register``/v1/account/keys`, also in the dashboard).
Separately, the billing gate accepts **any** bearer string (only revoked keys
are rejected), so wiring starter credit into `BillingLedger.ensure_client`
would let anyone mint unlimited free credit by inventing keys.
Decision (grilled 2026-07-06): **Caller Credit is granted once per account**,
when the account creates its first API key; chat requests on an
accounts-enabled tracker must present a real active `sk-mesh-` key.
Rejected: credit-any-bearer (free-inference farming against volunteers' GPUs
on the open internet) and manual-admin-credit-only (operator toil, no
self-serve testing).
## Design
1. **`BillingLedger.grant_caller_credit(api_key, account_id, amount) -> bool`**
Appends a credit event with the deterministic id
`caller-credit-{account_id}` and note `caller-credit`. Already-seen id →
no-op returning False. The deterministic id makes the once-per-account rule
idempotent locally *and* across hive gossip (event dedupe is by id), so a
second key, a retried request, or a replicated event can never double-grant.
2. **`AccountStore.is_active_key(api_key) -> bool`.** The chat billing gate
(`_handle_proxy_chat`) rejects keys that are not active account keys with
`401 invalid_api_key` — but only when an accounts store is configured, so
embedded/test trackers without accounts keep today's any-key behavior.
3. **`--starting-credit N`** (USDT) on `meshnet-tracker start`. Default is the
single constant `DEFAULT_CALLER_CREDIT_USDT = 1.0` in `server.py`
(devnet-friendly alpha, revised 2026-07-06; set 0 on mainnet). Threaded
through `TrackerServer``_TrackerHTTPServer`; registration and
`_handle_account_key_create` call `grant_caller_credit` when billing is on
and the amount is positive. The ledger's own `_starting_credit` stays 0 —
implicit per-key grants in `ensure_client`/`charge_request` remain dead
code paths.
4. **Stack variable `STARTING_CREDIT`** in
`deploy/portainer/meshnet-tracker-nobuild-stack.yml` (default 1).
5. **Docs.** QUICKSTART gains "Get an API key" (register/login/create key via
dashboard or curl, what 402 means); DEPLOY_PORTAINER's stale "any key is
accepted and starts with the configured starter credit" paragraph replaced.
## Acceptance criteria
- Fresh account → first key → key has `--starting-credit` balance; chat succeeds
- Second key on the same account → no additional credit
- Revoke-and-recreate keys → still no additional credit (deterministic event id)
- Random bearer string on an accounts-enabled tracker → 401, never 402/free work
- Tracker without accounts store: gate behavior unchanged
- `--starting-credit 0` disables the grant entirely (mainnet posture)
- `python -m pytest` passes from repo root