fix(free-tier): review round 2 — route-gate the allowance verdict, keep policy/billing 403s, pool the provision RPC, guard the retry race

Should-fix
- _is_genuine_nous_rate_limit: the structured rate_limited verdict counts only
  on the welcome host; a paid-host 429 keeps main's exhausted-bucket rule.
- _nous_welcome_tier: the route-keyed dark-tier 403 applies only to a 403 that
  matches neither the content-policy nor the billing patterns, so a safety
  refusal or billing wall on the welcome host keeps its own recovery.
- free_tier.provision joins _LONG_HANDLERS (a forced mint + lock waits +
  re-inventory no longer block the RPC reader).
- retry_bootstrap_mint: under the lock, a build that found no identity never
  overwrites a record that has one (the loop racing the user's click).

Simplifications from the review
- _raise_for_anon_status is a (status, error) table; retryable derives from
  ANON_TERMINAL_CODES once (a bare 401 on sign-up now rides the ladder
  instead of dying for the process).
- classify_mint_exception is public and pure; the hand-built failure dict in
  free_tier.provision is gone (the memo is the one source).
- SetupRecord carries the memo payload as one `failure` dict instead of three
  unpacked fields.
- _welcome_surface_kind is a closed table with a "refused" default;
  _welcome_outage_copy excludes the classifier's `unknown` catch-all.
- FREE_TIER_RATE_LIMIT_CHAT is CARD + the sign-in tail, not a slice.
- Copy tests assert the contract (model named, tail present/absent) instead
  of freezing whole sentences.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Robin Fernandes
2026-09-15 23:44:42 +10:00
committed by kshitij
parent a241f42fc8
commit 2a94ca80e7
11 changed files with 129 additions and 82 deletions
+6 -1
View File
@@ -93,6 +93,7 @@ def test_provision_sets_the_free_tier_up_through_the_lifecycle_primitive(tmp_pat
monkeypatch.setenv("HERMES_SHARED_AUTH_DIR", str(tmp_path / "shared-store"))
monkeypatch.setenv("HERMES_GUEST_ONBOARDING", "1")
calls = []
real_ensure = anon_auth.ensure_portal_identity
def fake_provision(**kw):
calls.append(kw)
@@ -114,9 +115,13 @@ def test_provision_sets_the_free_tier_up_through_the_lifecycle_primitive(tmp_pat
with _auth_store_lock():
store = _load_auth_store(); store["providers"].pop("nous"); _save_auth_store(store)
monkeypatch.setattr(anon_auth, "ensure_portal_identity", refused)
# The real primitive memoises the refusal; the RPC reports that memo.
monkeypatch.setattr(anon_auth, "ensure_portal_identity", real_ensure)
monkeypatch.setattr(anon_auth, "_reconcile_and_provision", refused)
anon_auth.reset_mint_memo_for_tests()
result = _call("free_tier.provision")
assert result["has_guest"] is False and "not open" in result["error"]
assert result["error_code"] == "anon_gate_closed" and result["retryable"] is False
_set_guest_off(monkeypatch)
monkeypatch.setattr(anon_auth, "ensure_portal_identity", lambda **kw: (_ for _ in ()).throw(AssertionError("must not run")))