Files
zeekay 34073201e6 one gateway: every model call goes to Hanzo, as whoever is asking
There were two. Ours — AI_GATEWAY_URL, addressed with the caller's IAM token, so
the org that pays is read off the bearer's claim. And upstream's, addressed with
LLM_GATEWAY_URL and a single shared personal key, with the product in the path.

Neither of the two was working. The upstream pair is unset in production, so the
27 call sites that reach for it — signals, conversations, growth, stamp, review,
warehouse enrichment, path cleaning, AI observability translation — raised
"LLM_GATEWAY_URL and an API key must be configured" and had never run. And the
three build_* functions that were supposed to prefer ours referenced names that
do not exist in their scope (`user`, `api_key`, `bearer`), so they raised
NameError before reaching either gateway. Every AI feature in this repo was
down, in one of two ways, and the fallback hid which.

So there is one now. The three get_* constructors keep their names, because 27
files call them, and resolve to the Hanzo gateway; the three build_* variants
differ only in tagging the generation with an explicit ai_product. Both go
through one resolver, and every label — ai_product, ai_stage, team_id, the trace
id — is built in one place rather than by each caller.

A static key is deleted rather than moved. A key in the environment is one
tenant's credential doing every tenant's work: it would bill every customer's
assistant to whoever owns the key. The bearer is an IAM token and nothing else —
a signed-in person's own token when someone is acting, this deployment's
identity when nobody is.

And there is no fallback. Unset, malformed, or IAM refusing a token now raises
GatewayUnavailable. Answering anyway would mean answering from outside the
estate, unbilled and unattributed, which is worse than not answering.

Verified against the live Django with the real SDKs: 20 assertions on identity,
base URLs, labels and refusal, and all 23 calling modules import.
2026-08-13 13:47:44 -07:00
..
2026-08-09 09:16:43 -07:00
2026-08-09 09:16:43 -07:00
2026-08-09 09:16:43 -07:00
2026-08-09 09:16:43 -07:00
2026-08-09 09:16:43 -07:00