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

3.2 KiB

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