BBramaDocs

Brama runbook: credential authorization and the subscription pool

Credentials and subscriptions

Six symptoms that all end at one question: which credential was supposed to pay for this request, and what refused it? Read the refusal sentence first — each one names a different repair, and none of them is repaired by waiting. The error vocabulary is in errors; the rest of the runbook is at runbook.

A request answers 503 credential_unauthorized

The dispatch sentence behind it is usually direct '<provider>' credential is unavailable — no capability, read grant, or standalone credential could produce a secret for that provider at final use. It is retryable: false on purpose: waiting does not reach an authorization that does not match, and the refusal is what the caller receives. Check, in order: /readyz (denied[]), the launcher's provisioned trust material (bin/provision-skarbiec-trust was run once on this installation), and whether the alias's route names a provider this host was ever granted (alias_provider_capability_absent in the log at startup: "no provider capability was issued for this route; the alias will not serve").

An alias in that condition reports state capability_absent in brama aliases and in GET /v1/aliases, and a completion naming it answers 503 alias_unserviceable with the state and the route in details.

A request answers 429 subscription_unavailable

The dispatch sentences, from narrowest to widest (concepts/entitlement):

  • no active '<provider>' credential for agent — the signed agent owns no eligible credential for the provider the route names.
  • no active stateless provider models for signed agent — a selector found no candidate at all.
  • no working subscription model for signed agent — the selector walked its whole bounded candidate list; the body's attempts counts the provider calls actually paid for. After every provider it walked past (<provider> refused (…)) it names every pool member whose provider had no candidate route at all, as <member> (<provider>) not tried: <cause>, where the cause is its recorded status, its retirement, the ledger's standing refusal, the last model discovery's refusal, or a discovery that published no model for it — so an account of another provider is never silently absent from the answer.
  • all bounded '<provider>' credentials unavailable for agent — every bounded credential was tried or is inside a recorded block.

First command: brama subscription list. 0 of 0 subscription credentials are live is the whole diagnosis — sign an account in. Rows present but refused carry last_redeem_error in the words of whatever refused (credential cause first, then a block still in force, then the newest failed check; a lapsed block is deliberately not reported). A gateway that holds no usable credential signs itself in — through Weles on its own host with subscription login, or by hand. Taking the grant a coding harness on some machine already holds is not offered: a provider issues one OAuth pair per sign-in and revokes it when a second holder refreshes, so the borrowed copy signed that machine out, and a gateway repaired that way depended on a workstation staying in the fleet. A member left over from that arrangement is given back with subscription disown.

When Weles cannot drive the account, the sign-in is done by hand: the person logs in with their own browser and pastes the code back. Where Brama runs, on a terminal:

$ brama subscription login claude-code --by hand --subscription-id <id> --reason "<why>"

It prints the page to open, waits for the pasted code, stores the grant and proves it with one completion; exit 0 with result: signed_in is the proof (cli). From another machine, such as a workstation that holds no vault of its own, name the serving gateway so the grant lands in the vault it reads:

$ brama subscription login claude-code --by hand --subscription-id <id> --reason "<why>" \
    --gateway-consumer <consumer> --bearer-role <role>

Run there without a gateway, the command refuses before printing the page and names these options. Brama Desktop’s Sign in by hand on the selected account does the same against the gateway; result: signed_in on the screen is the proof.

A subscription reads burnt

burnt covers both recorded dead states — a grant the provider disowned and a subscription somebody retired — and last_redeem_error says which. A definitive provider refusal (invalid_grant, invalid_token, revoked, unauthorized_client, or an unexplained 401/403) is recorded as credential.state: needs_reauthorization with the provider's own sentence as cause, and the credential is left untouched until a sign-in replaces it — the refresh timer will not resurrect it, and a 30-minute block holds the dispatcher off it. Repair: on the host Weles runs on, one command does the whole sequence — the sign-in through Weles and the refresh that proves it:

$ brama subscription login <provider> --by weles --reason "<why>"

Exit 0 with result: signed_in is the proof (cli). Where Weles is not reachable, sign the subscription in by hand — for claude-code with brama subscription login --by hand, which runs the proving refresh itself; otherwise through Desktop or a donation, then:

$ brama subscription refresh <provider> --reason "<why>"

Exit 0 with result: refreshed is the proof; result: failed quotes each refusal in the provider's own words. A retirement outranks everything: a retired subscription is never refreshed.

subscription refresh reports it attempted nothing

The command exits with an attempt count of zero and this detail:

detail: no usable `<provider>` subscription is in this deployment's pool, so
no credential source is configured to refresh: one has to be signed in and
stored in the vault before this command has anything to act on

Not a fault in the refresher — the pool is empty for that provider. The same sentence answers a provider with no OAuth path. Sign an account in first; the walkthrough shows the full sequence (walkthrough-subscriptions).

An account vanished from every listing but keeps paying

The tag-loss case: discovery finds an account by its brama:subscription mark, so an item that loses it stops existing for every caller and every screen while its credential stays valid. Nothing that lists subscriptions can say so — the listing is the thing that lost it. The one reporter is /readyz: unroutable_accounts[] names the item id, provider (null when the item carries no brama:provider: tag), and coordinate (items stripped bare are recognised by the provider:<provider>:<id> coordinate alone). Repair in the vault: restore brama:subscription, brama:provider:<provider>, brama:id:<id>. No brama:agent: tag is needed for routing: every subscription serves every caller.

A provider refuses its own credential

A direct route whose provider answers 401 returns 502 provider_failure with the provider's sentence behind the kind prefix; the body's attempts field counts the single provider call that was made:

{"error":{"code":"provider_failure",
 "message":"provider_authentication: Incorrect API key provided: ...", ...}}

Distinguish it from 503 credential_unauthorized: the 502 means Skarbiec (or the standalone map) produced a credential and the provider rejected it — the repair is at the provider account, not the authorization chain. Because the alias names one route, that provider's answer is the answer the caller reads. A provider 429 surfaces as 429 provider_rate_limited with retryable: true; when the provider named the window's reset, a recorded block marks that account unusable until then, and requests that need it are refused in the meantime. A refusal without a reset records no block: the next request asks the account again.

A request is slow before the provider answers

Two log lines say where the time between routing_decision and routing_complete went. credential_obtained is written once per managed credential, with resource, path (configured, issued or read-grant — which step of the cascade answered) and elapsed_ms: the whole cascade, which is a child process to the entitlements router and a broker redemption. subscription_models_discovered is written once per request that ranks the shared subscription pool, with subscriptions, read (credentials read from the vault this time), awaiting_sign_in (left alone because the ledger says a sign-in is owed), failed and elapsed_ms. A request that spent nine seconds with elapsed_ms small on both lines spent them at the provider.

The request path trusts the ledger the way the refresh sweep does. A grant the provider disowned — credential.state: needs_reauthorization, with the provider's sentence as cause — is not read from the vault and not refreshed again until a sign-in replaces it; it appears in subscription_models_discovered as awaiting_sign_in. Before this, every request that ranked the account paid for that read and for the refusal — a document with no refresh token could be refused several times a minute for days while every caller waited on it. A credential the vault refuses outright is remembered for sixty seconds, the same window a failed model discovery is.

Concurrent cold catalogue requests for the same subscription share one discovery. The joiner reads the existing success and failure caches after that discovery finishes; unrelated accounts do not share it. This shares model metadata, never a credential for inference. In subscription_models_discovered, the request that performed the read reports read=1 and the joiners report read=0 for that account.

Why an account cannot be signed in

Brama resolves a subscription from Skarbiec and delegates authentication to Weles. Weles reads the matching login material from Skarbiec and performs OAuth on the Stado-selected host, without provider CLIs or copied local sessions. No second account catalogue or manually added login tag is required.

The pool's automatic_sign_in.last_attempt records the outcome. For a failure, inspect failure.code, failure.stage, failure.http_status, failure.run_id and source_revision. The detail is the service's actual response, not an explanation inferred from a missing configuration field. Readiness and Desktop show this same observation.

CodeObserved operation
subscription_identity_missingThe Skarbiec subscription has neither a principal nor an unambiguous reference to a login item.
skarbiec_login_missingNo Skarbiec login item matches the principal the subscription declares.
skarbiec_login_ambiguousSeveral login items match; the response lists their identifiers instead of selecting an unrelated account.
login_identity_changed, skarbiec_identity_changedThe account changed between resolution and credential use; no grant for the old identity is accepted.
weles_unreachableWeles did not answer: the request failed in transport, or the route in front of it (a Stado adapter) answered a server error with no JSON body, such as 502 upstream unavailable. The detail includes the operation, address, status and body or transport error. It is a dependency outage, not an operator action: the next maintenance pass asks again.
account_resolution_response_invalidWeles answered account resolution outside a server error with something that is not its JSON contract; the detail carries the status and the body as sent.
oauth_provider_refused, provider_challenge_refusedThe provider refused the OAuth or login operation. Its response and the Weles run are retained.
credential_persist_refused, persisted_credential_refresh_failedThe login did not yield a usable persisted subscription credential; it is not reported as repaired.
journal_unreadableThe journal holding the last sign-in could not be read, or its last failed record does not state its failure code and stage (an older record without structured operation evidence). The detail names which of the two is absent and when the attempt ran; it is not proof that a provider rejected the account, and no code is put in place of the one the record lacks.
not_placed_hostStado reported that this gateway runs on a different host than the declared placement.

The repeat check uses the resolved Skarbiec account-data revision and the actual attempt result. It retains the refusal for unchanged, non-retryable provider challenges rather than repeatedly submitting passwords or codes. An identity-resolution failure is not recorded as a completed browser login.

A gateway whose Weles is down says so in /readyz: each member whose sign-in found no Weles is listed under automatic_sign_in.blocked with weles_unreachable and the address it tried, whether that address came from BRAMA_WELES_URL or from Stado's service directory, and operator_action_required stays false for it. Qualify it with cargo test --test readiness-weles-down -- --ignored --nocapture against an isolated gateway whose pool the ledger refuses and whose BRAMA_WELES_URL names an address nothing listens on.

An account nobody signs in — an API key, or a retired subscription — reports applies: false and no word at all, so a healthy account is never shown as awaiting something. Weles signs in claude-code, codex and kimi.