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
BillingLedger.grant_caller_credit(api_key, account_id, amount) -> boolAppends a credit event with the deterministic idcaller-credit-{account_id}and notecaller-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.AccountStore.is_active_key(api_key) -> bool. The chat billing gate (_handle_proxy_chat) rejects keys that are not active account keys with401 invalid_api_key— but only when an accounts store is configured, so embedded/test trackers without accounts keep today's any-key behavior.--starting-credit N(USDT) onmeshnet-tracker start. Default is the single constantDEFAULT_CALLER_CREDIT_USDT = 1.0inserver.py(devnet-friendly alpha, revised 2026-07-06; set 0 on mainnet). Threaded throughTrackerServer→_TrackerHTTPServer; registration and_handle_account_key_createcallgrant_caller_creditwhen billing is on and the amount is positive. The ledger's own_starting_creditstays 0 — implicit per-key grants inensure_client/charge_requestremain dead code paths.- Stack variable
STARTING_CREDITindeploy/portainer/meshnet-tracker-nobuild-stack.yml(default 1). - 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-creditbalance; 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 0disables the grant entirely (mainnet posture)python -m pytestpasses from repo root