fix(free-tier): review follow-ups — read the classifier's context, never replace a locked identity, re-inventory on retry

Correctness
- The welcome-tier recovery hooks (model_not_free move, wrong-host heal) and
  the long-wait rate-limit check read the turn's extract_api_error_context()
  dict, which never carries welcome_refusal / welcome_route. They now read
  classified.error_context, where _nous_welcome_tier parks them; the guard
  records the classifier's reset_at. Tests drive the real classifier and the
  real extractor so the two-context boundary is exercised.
- The connector path caught every AnonCredentialDead and re-minted; a locked
  account (anon_account_locked) is now retired without replacement, matching
  the inference resolver.
- A background bootstrap retry reused the boot-time provider inventory; it
  re-inventories, so a provider connected during the cooldown keeps
  inference.
- The desktop's setup.ready listener only refreshes an untouched picker
  (oauth mode, no local endpoint, idle flow) and re-checks after the
  readiness round, so an API-key form opened meanwhile is never dismissed.
- /__log on the rehearsal server sent its response while holding the state
  lock that _send re-acquires; the log is copied out first.

Reductions
- One shared FakePortal / install_portal (tests/hermes_cli/anon_portal.py)
  behind both free-tier fixtures, with a single httpx.Client transport seam.
- The rehearsal server's static inference answers are a table; dead
  scaffolding (REAL_PAID_URL, claim_codes, the no-op dead_once branch,
  extra_headers) removed.
- Setup-notice copy is a code-to-key map; its test uses real codes (the old
  loop built nonexistent ones and only exercised the fallback).
- The ineffective FreeTierErrorCode union is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Robin Fernandes
2026-09-14 15:50:28 +10:00
committed by kshitij
parent 51e39af967
commit 59fad62a40
16 changed files with 351 additions and 314 deletions
+3 -1
View File
@@ -190,7 +190,9 @@ def retry_bootstrap_mint(*, force: bool = False, announce: bool = True) -> Setup
return run_bootstrap(announce=announce)
if current.has_identity:
return current
record = _build_record(other=current.other_providers, force=force)
# Re-inventory: a provider the user connected during the cooldown must keep inference; the
# boot-time answer is stale by now.
record = _build_record(other=_inventory_other_providers(), force=force)
with _lock:
_record = record
if announce: