61 lines
3.2 KiB
Markdown
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
|