BBramaDocs

Brama changelog: current changes

Current changes

Entries without a release coordinate are recorded here as unpublished. The unreleased changes to the subscription pool and its credentials have their own page. Released entries live in 0.4.46 to 0.4.13, 0.2.77 to 0.2.42 and 0.2.5 to 0.2.2. Brama follows Semantic Versioning and the pre-one compatibility policy in RELEASE.md.

Unreleased: the release ships no donation recipient key

Incompatible. The archive carried etc/brama-skarbiec/recipient-public-keys.asc, one installation's own keys, and every new installation directory was seeded from it, so every gateway built from the release encrypted donations to that installation. The archive and the container image now carry no recipient key. An installation declares its recipients on the host in ~/.config/brama/recipient-public-keys.asc, or the file BRAMA_DONATION_RECIPIENT_KEYS names; each new installation directory is seeded from it and one that already holds its keys keeps them. With neither, startup ends with no donation recipient keys: … naming both paths (Launcher inputs).

Unreleased: brama review --help names an example gateway

The --gateway help gave this documentation site's host as the gateway to call. That host serves documentation and the public download, not a gateway, so the help now names https://brama.example.com: pass the base URL of the Brama gateway your deployment runs.

Unreleased: a workload bearer issued call:brama may call every route

A bearer the Skarbiec authority resolves used to reach only the routes named one by one in its call:brama#<route> capabilities, so a product calling a route it was not issued answered 403 until someone minted that route too. A call capability on brama with no route now authorizes every model; the identity stays model-scoped for the paths it may reach, so it never stands for the console. A model refused to a workload bearer names the capability it lacks and the stado credentials token mint … --capabilities call:brama,… --replace-capabilities command that issues every route (errors).

Unreleased: anyone can download Brama from this site

/download/<platform>/<file> serves the release of the version these pages document and /download/<version>/<platform>/<file> one exact release: the archive, its signed manifest, the signature and the qualification receipt, read from the release channel the site's deployment declares in BRAMA_RELEASE_ORIGIN. /download/keys/<key_id>.pem serves the key that verifies the signature. The quick start and README download from here instead of a forge mirror that had stopped receiving releases. See Download.

Unreleased: a documentation site whose deployment declares no gateway refuses by name

The documentation site forwards every path outside /docs to the gateway origin its deployment declares in BRAMA_GATEWAY_ORIGIN, and publication is off by default. A deployment that declares no origin answered every such call 503 gateway_origin_undeclared, which callers read as Brama refusing them; it now answers 404 api_not_published, retryable false, naming the site, method and path, the address to call instead and the variable that publishes a gateway. A value that is not one credential-free HTTPS origin answers 500 gateway_origin_invalid. The publishing script that served the gateway through one network provider's tunnel is gone; an origin is declared through the deployment. See Startup and readiness.

Unreleased: the release runs the launcher check under its current name, and a real test exercises its grammar

The router-verbs check was renamed brama launcher verbs, but the release workflow still called brama launcher check-router-verbs, which no longer exists, so a release would have stopped at that step. It now calls brama launcher verbs, as the bundle build already did. tests/launcher/verbs.rs runs the real binary against the pinned router with a launcher family holding a router call in every command position and the router literal in every position that is no call, and checks the refusals for an unreadable stage, a non-text launcher and a path with no directory. See Launcher inputs.

Unreleased: every release platform runs a real journey against what it built

.wisent-release.json declared no post-build tests, so Stado refused every queued Brama change as unqualified and no release could carry it. The binary platforms now run tests/release/journey.sh launcher-verbs against the staged brama and router, compiled from the source, inputs and dependencies the build records in .build/cargo.sh; the web platform runs the documentation navigation check over the pages it publishes. See Release procedure.

Unreleased: Brama reports pooled subscriptions to Wisent's contribution ledger and gates aliases on its entitlements

A gateway that declares BRAMA_CONTRIBUTION_LEDGER_URL reports, once per UTC day, each donated subscription that answered a caller from the pool as a subscription.pooled contribution of its donor (the Wisent account id of a signed-in donor), and serves an alias named in BRAMA_ENTITLEMENT_GATED_ALIASES beyond a credential's model scope only to a caller the ledger says holds the model.access entitlement to it, refusing others with entitlement_required and the ledger's reason, and a ledger that did not answer with contribution_ledger_unanswered. Brama links no identity: a signed-in user links their account on the Rewards page of compute.wisent.com. An operator reads an agent's entitlement with brama rewards status. A gateway with no ledger serves as before.

Unreleased: the runtime grant lasts as long as the operator declares

brama workload register stated the largest integer JSON carries as the runtime grant's lifetime, a grant that never ends in practice, where Skarbiec requires one that does. It now passes BRAMA_RUNTIME_GRANT_SECONDS from the installation's service.env and refuses by name when it is missing or not a whole number of seconds above zero; the answer carries lifetime_seconds (register).

Unreleased: the provider's stated output limit wins over the build's table

A subscription request without max_tokens on the Anthropic wire was sized by the model table this build embeds whenever the table knew the model, over the limit the provider's own listing stated, and the listing's limit only filled models the table did not know; a model neither stated was sent unsized for the provider to refuse. The listing now wins and the table fills what it left unsaid, in discovery and in sizing; a request nothing sizes is refused before dispatch asking for max_tokens. The table is read strictly: one that does not parse, or a row missing a field, is refused naming the row instead of emptying itself (inference).

Unreleased: a pool no member of which can be served refuses at once

With every member of the subscription pool needing a new sign-in, every request stood for minutes while each member's credential read and refresh failed in turn, then answered subscription_reauthorization_required. A routed request is now refused before any of that, naming each member's state, refusal and repair; an expired or unchecked member still keeps the pool open, because redeeming it renews it. /readyz carries model and answers 503 while it says none or unknown, keeping any earlier cause in reason; clients read that answer and send nothing further (administration and operations).

Unreleased: every vault child stands in the register, and each waiter hears its own call

The account-discovery registration and routing-mark restoration ran their vault children outside the standing-call register, so a vault that never answered held them unlisted and unended. They now stand there like every other vault child. A request whose call is ended is refused with a sentence naming its own operation, target, process and age, not the oldest call's. A gateway whose host has no Stado resolver declaring a refresh interval read no measure and ended nothing; BRAMA_CREDENTIAL_CALL_STANDING_SECONDS now declares one there, and /readyz says in standing_measure what calls are measured by, or that nothing bounds them (administration and operations).

Unreleased: one home for every path Brama derives from it

The state directory, the usage ledger, the routes registry Brama adopts into, the Stado resolver state readiness reads, the installed vault executable, and the installation register reads each found the home directory their own way: some took HOME only, one refused a host whose HOME was unset although the user database names a home. All of them now take it from one lookup — HOME, else the user database's home — and with neither, the refusal names the declaration that would have stood in for it where one exists (BRAMA_SUBSCRIPTION_USAGE_FILE, BRAMA_INFERENCE_ROUTES_FILE, STADO_RESOLVER_STATE_FILE) and the cause.

Unreleased: the pages say the pool is shared, and wear one navigation

The subscription concept, the provider pages, discovery, the integration contracts, the runbook and the selector example described subscriptions as delegated to one agent. They now say what the code does: every subscription in the vault serves every caller, and the brama:agent: tag records who banked an account and who may retire it. Losing the brama:subscription mark, not the agent tag, is what makes an account unroutable. The navigation copied into every page had drifted into several versions and was missing from others; vercel-ingress/tools/docs-navigation.mjs renders it from the home page's directory and checks it (release).

Unreleased: no route stands for a model Brama chose

openai/embeddings and openai/moderation no longer resolve to compiled models. Embeddings and moderation name a concrete OpenAI model like chat does (openai/text-embedding-3-small, openai/omni-moderation-latest), usually through an alias the route registry declares; an alias that still names a retired pseudo-route is answered by OpenAI refusing that model (OpenAI).

Unreleased: best declared onto a local route is answered by it

An alias table that points best at a route no subscription pays for, such as a local inference route, now has that route answer the request directly, or refuse it with its own error. Before, the route was dropped as a preference and the request walked the subscription pool, waiting behind its discovery and then refusing subscription_reauthorization_required for a route that needed no subscription. The routing log names the mode best-declared-route (Aliases).

Unreleased: the gateway runs its own maintenance and signs members in without anyone

The serving process now runs its own maintenance passes: one when it starts, then one at the next moment its usage ledger states something becomes due (a grant's stated expiry, a plan window's stated reset, a rate block's stated end), and one right after a request is refused subscription_reauthorization_required. Each renews due grants and signs members in through Weles. No host schedule has to fire; brama maintain asks for one more. The refusal is now retryable: true, /readyz operator_action_required is true only for what that maintenance cannot repair (a missing direct-provider credential, a vault account the process cannot route, or a member in automatic_sign_in.blocked), and an unredeemable subscription alone asks nothing of anyone (brama maintain, Administration, Errors).

Unreleased: subscription decisions are held to their schema, and a breach names its answer

The answer schema of a decision, and of a chat request with response_format, now travels on the Responses wire as text.format and on the Anthropic Messages wire as one tool the model is made to call, whose input is read back as the answer; before, only the OpenAI chat wire carried it, so best-decision-model served from a Codex or Claude subscription could answer without probability mass. A decision_contract_violated refusal now names the route that answered — for best-decision-model, the subscription model it selected — and carries details.answered_by, details.reason and details.reply; brama decide prints the reply on a second line. A decision's usage latency_ms is the measured value, no longer rounded (Decisions, Errors).

Unreleased: the release check reads launcher scripts with the shell grammar

The release step that checks every broker verb the launcher calls against the pinned broker's inventory now reads the launcher and its stage files with the POSIX shell grammar: a call is a simple command whose command word is the router literal, wherever it stands (after reserved words, assignments, exec, inside command substitutions), and the literal as an operand, test operand, assignment value, redirection target or case pattern is no call. A script the grammar cannot read is refused naming the file and the construct it ends inside, and a launcher or router path that names no file inside a directory is refused by name. Installed generations are also read from their layout (brama diagnose).

Unreleased: token usage is stated or absent, never zero

A routed answer now holds its token usage only as the provider stated it. A buffered chat completion, Messages or Responses answer whose provider stated no usage has no usage member instead of zero counts; request totals, throughput and the subscription ledger skip it, and decisions report the counts as absent. A count the provider stated that is not a whole number is refused as a provider failure naming it. brama test prints an unstated count as null and no longer prints a cost_usd of zero, since a subscription call states no price (Inference, brama test).

Unreleased: a failed answer always carries its class and sentence

A routed answer is either the generation or one refusal holding both its class and its sentence; a failure without either can no longer be built, so the HTTP edge, the ranked walk, decisions, brama test, task-quality rows and the usage ledger read the refusal that was stated instead of each inventing a stand-in (failed, unclassified, an unattributed provider_failure). A provider request the transport did not carry is 503 dependency_unavailable with the transport's cause, the only class such a failure is answered with, since Brama sets no request deadline of its own. A chat answer with neither text nor a tool call, a text block without its text, and a history tool call whose arguments are not JSON are refused with the body or call named, instead of passing as an empty answer or a call with null input (Errors).

A refusal Brama makes before any dispatch (authentication, transport, a malformed request, an alias it cannot serve) asked no provider and its body no longer carries "attempts": 0; a refusal a dispatch made states the provider calls it counted. A typed or media refusal states one call when a provider answered it and none when Brama refused first, read from the refusal's class rather than from its contract code, and the process totals count it the same way (Errors).

Unreleased: streams state the provider's stop and meter, or nothing

A provider's stop reason travels as a typed value from each provider stream and is mapped once per dialect: a reason the dialect has no word for is passed on as the provider stated it instead of reported as a normal stop, a Responses provider that stopped at its output limit or content filter ends the Responses stream with response.incomplete, and a stop nobody stated is null. A stream whose provider stated no usage sends no usage frame or meter and adds nothing to the totals, instead of a meter of zeros. Argument fragments for a tool call that never started end the stream instead of landing in the open block, and an answer's creation time is refused, not written as zero, on a host whose clock reads before the epoch (Inference).

Unreleased: an unreadable declaration is refused, not unset

The Wisent Identity declaration, the vault child resident ceiling and the model catalog's address, file and copy path are read through Brama's one environment reader: a value that is not Unicode is refused by name instead of read as unset, a Wisent Identity declaration with only one of its two halves is refused as incomplete naming the missing one, and both Wisent Identity calls announce what they wait for (Gateway inputs).

brama detect announces its system_profiler and nvidia-smi runs on stderr like every other child Brama waits on, says why a run that exited unsuccessfully named no GPU, and reads the memory column by its position after the name instead of counting fields (detect).

Unreleased: brama tasks replaces collect-task-quality

Measuring a named task and reading back what task:<KEY> selection uses are one command family: brama tasks measure <KEY> takes the flags collect-task-quality took, with the task as its argument; brama tasks show <KEY> --agent-id <ID> prints the recorded checks for that task, and brama tasks list --agent-id <ID> every task the agent has checks for. collect-task-quality is removed (tasks).

Unreleased: no invented counts, lengths or windows

Numbers nobody decided are gone. brama detect and the MCP brama_detect tool report resources only: the model and backend they recommended came from memory thresholds written in source. brama diagnose shows the whole current boot attempt from the launcher's new first line, brama launcher: boot attempt of <installation>, instead of the log's last sixty lines, probes the loopback port only when service.env names one, and probes the origins tailscale serve status publishes instead of an assumed tailnet port. Refusals quote the whole undecodable answer or router error. tasks measure --max-models accepts any count of at least one. Model, provider and path-segment identifiers are checked for the characters that could break a route, without a byte ceiling, by one validator. The workload key file is bounded by the longest valid key. The in-memory model catalog is fetched again only when BRAMA_MODEL_CATALOG_TTL_SECONDS names an age, and an unparseable value is refused instead of read as fifteen minutes. The trust generator takes the workload account from provision-skarbiec-trust and assumes none (gateway inputs, detect).

Unreleased: no compiled-in default models

openai/default and qwen/default no longer resolve to a model chosen in source (gpt-5.4, qwen-max); they are ordinary model ids the provider is asked for, which it refuses. A caller that wants the gateway to choose names an alias (best, or one declared with brama aliases set) and the route registry says where it points. The first-use example request names best (provider).

Unreleased: Wisent Identity is configuration

The Wisent Identity origin and anon key are no longer compiled in. The gateway reads BRAMA_WISENT_AUTH_URL and BRAMA_WISENT_AUTH_ANON_KEY (Stado's catalog sets both for the service); with either unset no person is resolved, the log says wisent_identity_unconfigured, and organization calls answer identity unavailable (gateway inputs).

Unreleased: POST /v1/images/edits

Brama answered no image edit, so a client written against the OpenAI edit contract (Lem's figure edits among them) failed every edit of a picture it already had. POST /v1/images/edits now takes the OpenAI multipart/form-data form — the options as text fields, the input images as image or image[] files and an optional mask — and serves it as the image request it is, on the image-model alias or a canonical image route. A provider that declares an edit endpoint (OpenAI's /v1/images/edits) is sent the edit there, also when the input images arrive in the JSON generation request; Gemini and Seedream keep reading them in their generation request. brama image takes --mask beside --image for the same edit from a shell (media).

Unreleased: OAuth refresh failure causes

OAuth refresh now retains the client's underlying failure chain. A failed response read reports the endpoint, HTTP status and received byte count instead of passing an incomplete body to credential-rejection classification. Both successful and refused bodies use the same complete reader; decoding text after the read also preserves characters split across network chunks. The CLI and Desktop keep their existing verdict field, detail.

The native real-test runner uses an isolated Skarbiec vault and the real token endpoint. Before this source change, Brama 0.4.49 reproduced the missing transport cause; its complete authentication refusal correctly marked the deliberately invalid grant as needing a new sign-in. That baseline does not qualify the changed native binary, a partial-body interruption or the graphical surface.

Unreleased: request completion and concrete failures

The current request, streaming, integration and error pages now describe the shared transport's actual contract: completion or a concrete failure, not a gateway clock. Candidate and credential selection remain finite. Provider and OAuth responses are read whole, as the core resource contract states; these pages no longer promise obsolete byte caps either. Inference transport errors retain their cause; an incomplete provider stream is not a completed answer. Historical released entries remain unchanged. This documentation correction does not qualify a new binary or a provider journey; those require their own recorded execution.

Unreleased: brama subscription list and brama diagnose --json

The pool report moved from the top-level brama subscriptions into the brama subscription group as brama subscription list, with the same options and output, so one object has one name. brama subscriptions no longer parses. brama diagnose --json prints the same sections as one document, {"sections": [{"title", "lines"}]}.

Unreleased: brama review, a pull-request gate in the product

A repository that gated its pull requests on a model review carried its own copy of the loop: wisent-supabase-wisent-app's Python script sent the diff to the gateway, served the model two file tools, and read the verdict out of a Verdict: line in the prose. brama review is that loop once. The repository supplies its instructions, the request and the one directory the model may read; the model reads and searches files there (every path resolved and refused outside it) and ends by calling submit_verdict with approve or request_changes, which becomes the exit status 0 or 3. A missing token, an unreadable file, a gateway refusal, a verdict outside the schema or a review that runs out of --max-turns exits 1 and names the cause, so nothing that was not reviewed passes.

Image, video and voice, and a catalogue that says what a model is

Brama routed text. It now routes the three generation shapes that are not text: POST /v1/images/generations, POST /v1/videos with GET /v1/videos/{id} reading the job back, and POST /v1/audio/speech, whose answer is the provider's encoded audio rather than JSON. Three aliases reach them — image-model, video-model, voice-model — known by name and required of nobody, so a deployment that generates no media changes no policy to take this release. Media is paid by the deployment's own capability: no subscription in the pool carries an image, video or voice quota, so best cannot stand behind a media alias, and the registry refuses one that tries.

Which providers serve which shape is declared from their vendor documentation and nowhere else: openai for all three, xai and together for images, venice for images and voice, groq for voice. A provider that generates media behind a different contract is declared with empty media paths and refused by name.

The catalogue now says what a model is. Every model carries kind — text, image, video or audio, decided from the output modalities its source declares — and openWeights, which is the catalogue's own flag and is null where the source said nothing, a different answer from false. Until now outputModalities read ["text"] for every id including the image and video models the catalogue had always carried, so no console could tell a renderer from a chat model.

Categories are the operator's, not the gateway's. A category is declared in the route registry — by provider, by exact route, or by a term the publisher put in the model's name — and uncensored is the first one anybody asked for. Brama infers none: a gateway that guessed would be publishing an opinion as metadata. GET /v1/models narrows on kind, weights, category and provider and refuses any other query member by name; GET /v1/categories reports each declaration with how many models it currently holds, which is the check on the declaration. The shell surface is brama models, brama categories, brama image, brama video and brama speak; the console carries the same facets, the same declarations and the same three generation shapes.

Typed decisions: decision-model and best-decision-model

Brama served one shape until now: generation. A caller that needed a routing decision, a gate or a grade asked a chat model for prose and parsed it, and every caller parsed it differently. POST /v1/decisions takes a state and a map of typed questions — a noul, a choice over the options the caller declared, a score over the rubric it gave — and answers one typed answer each. Two aliases reach it and no other name does: decision-model for a route this deployment pays for, and best-decision-model, whose route may be best, so the caller's own signed identity selects the subscription that pays.

Two engines answer and the response says which. TypeSafe AI's System One model is declared as the typesafe provider and answers the decision wire natively; because it generates no text it carries no chat path at all, and a chat call naming one of its routes is refused rather than sent to a URL this build invented. Every other route is served by asking a chat model for the probability mass over each question's declared labels, and Brama computes the choice, the score and the confidence from that distribution itself — so the model cannot contradict its own numbers, and an answer outside the declared schema is refused decision_contract_violated rather than passed on.

The two aliases are known by name and required of nobody: unrouted, they report no_route with the command that repairs them, so no managed deployment has to change its policy to take this release. brama decide answers one from an operator shell, and brama routes set and brama routes rm declare and retire an alias through the same validated, atomic, owner-only registry write the admin route performs.

Two things found on the way and fixed with it. A model-scoped bearer could not reach /v1/decisions at all, because the path allowlist enumerates the inference paths. And brama aliases in an operator shell answered capability_absent for every provider whose credential the very next step would have read: the presence check parsed a file named by a variable only the launcher sets, while the credential path asks Skarbiec. It asks Skarbiec too now, once per provider. /v1/admin/snapshot additionally reports the endpoint each provider resolves, or why it resolves none.

Unreleased: an alias resolves to exactly one route

An alias now names one destination and nothing else. When that route refuses, the refusal is what the caller receives: no second route is attempted, so the answer an operator reads is the answer the route gave. Brama adds no elapsed-time cutoff to that attempt.

The route registry document carries exactly three known top-level fields: schema_version, whose value is the number one, deployments, and routes, which maps an alias to its single destination. Any other top-level field is refused by the deserializer, so a document that carries one is rejected whole rather than partly read.

PUT /v1/admin/routes takes exactly the two fields below and DELETE /v1/admin/routes takes {"alias"}; both answer {"ok":true,"routes":<document> } with the committed document.

{"alias": "wisent-backend", "primary": "codex/gpt-5.3-codex-spark"}

GET /v1/aliases returns rows of {"alias", "state", "route", "reason", "required", "authorized"}, and the four alias states are unchanged: serving, no_route, capability_absent and routes_file_invalid. A completion that names a declared alias which cannot serve is 503 alias_unserviceable carrying details.state and details.route; an unknown alias is 404 whose details also carry state and route. brama aliases prints alias, state and route, and brama adopt reviews one route per alias.

brama routes migrate is new and is how an existing registry file reaches this shape without hand editing. It reads the configured document, keeps only the known top-level fields, validates the result, and commits it with the same atomic owner-only replacement every other registry write uses. It is idempotent and prints the resulting document, so a second run reports the same document the first run committed.

Unreleased: subscription plan usage replaces four invocations

One sweep and one ledger were asked for four ways — POST /v1/subscription-usage/:agent_id, POST /v1/account/subscription-usage, POST /v1/admin/subscription-usage/:agent_id and POST /v1/admin/subscription-pool/usage — and every one of them read the same usage ledger and the same provider reports. One capability answers it now: POST /v1/plan-usage, scoped by the identity the caller proved through the same resolver the subscription pool uses. All four were deleted in the same change: their routes, their handlers, the Brama Desktop calls that reached them, and their paragraphs on this site.

The answer is the pool's document with every reading brought current, so a row means the same thing whichever capability produced it. A provider that answered sets limits and usage_source: "provider"; a provider that refused keeps its last reading, marks it stale, and records the refusal in errors in the provider's own words; a provider with no supported free report is recorded as exactly that. None of the three reads as a plan reported unused. A request body is refused with plan usage takes no request fields; the answer follows from the proven identity, and an inventory Brama could not read stops the provider reads rather than asking about accounts nobody confirmed.

A model-scoped bearer may now reach the capability, which every workload identity the authority resolves is: none of the four routes it replaced was reachable by one, so the single audience that signs its own requests could not read its own plan usage over any of them. brama subscriptions --refresh-usage and Brama Desktop's Refresh usage both read this capability. tests/usage drives it over real HTTP against an isolated vault, an isolated Wisent Identity authority and an isolated endpoint answering the providers' own usage reports.

Unreleased: the subscription pool replaces eleven invocations

Which subscriptions exist and who owns them was asked eleven ways — brama subscriptions list; GET, POST and DELETE /v1/subscriptions/:agent_id; GET and POST /v1/account/subscriptions; DELETE /v1/account/subscriptions/:subscription_id; GET and POST /v1/admin/subscriptions/:agent_id; DELETE /v1/admin/subscriptions/:agent_id/:subscription_id; and GET /v1/admin/subscription-pool — and every one of them answered it from the same vault declaration. One capability answers it now: GET and POST /v1/subscription-pool, scoped by the identity the caller proved, plus brama subscriptions as a leaf command. All eleven were deleted in the same change: their routes, their handlers, their tests, the Brama Desktop calls that reached them, and their paragraphs on this site. brama subscriptions list no longer parses.

The pool document is one shape for every audience — {"ok", "observed_at_ms", "scope", "errors", "subscriptions"} — so the per-audience providers key and the agentId key are gone, and the row key subscription_id is now id, which every row already carried. scope is deployment or the agent id the answer was narrowed to. A write names an owner only from the deployment scope; an agent or account holder that sends agent_id is refused with agent_id is derived from the proven identity and must not be sent, and a deployment-scoped write that omits it is refused too. Retiring a subscription the caller does not own is 404 subscription not found even when the deployment holds it.

BRAMA_WISENT_AUTH_URL now selects the Wisent Identity origin, and an unset variable means canonical Wisent Supabase. The anon key was already deployment-configurable while the origin it was presented to was compiled in, which left the human half of every identity decision unprovable anywhere except against the production project; the account holder's scope is now driven end to end in tests/pool.

The measured command surface is 42 leaf invocations in 6 groups at 15c27474b3a870e1a399697068e2b96f4e72d8dd, down from 50 in 7 (command surface).

Unreleased: the backend alias is the client's name

The Wisent App backend's chat alias is wisent-backend. It was wisent-backend/chat/primary beside a rank-suffixed twin, which spelled in the alias name the same rank that the registry's own per-alias column of spare destinations then carried; that column is removed by the current contract, under which an alias carries exactly one route. The name then became wisent-backend/chat, which spelled a shape POST /v1/chat/completions already names. The alias now carries only who is asking. wisent-backend/embeddings and wisent-backend/moderation keep their purpose segment for one stated reason: each needs a route of its own and the registry is keyed by alias name alone.

Unreleased: subscription usage and failure reporting

brama subscriptions list --refresh-usage and Brama Desktop's Refresh usage share native free provider reports, bounded credential access and OAuth renewal. Account rows retain their last readings, reset and observation times, independent usage_check results and complete stored failure causes. Discovery, storage, malformed replies and partial refresh failures are all reported as themselves rather than as a healthy empty pool. An integration whose provider publishes no free report is recorded as unmeasured, which is not the same row as a plan measured at zero.

Desktop exposes the same account-specific errors, retained stale windows and explicit Weles/Stado Sign in action. A missing measurement time is reported as unknown. Refresh operations may wait longer than ordinary reads so that a provider or credential refusal is reported by Brama itself rather than by the caller's own transport. No OMP integration is used.

Signing an account in is the gateway's own work. The whole escalation used to sit behind BRAMA_CREDENTIAL_AUTOMATIC_SIGN_IN=1, which no host set, so a credential a provider had disowned waited for someone to run a command; that variable is gone and the sweep asks Weles by itself. A stored document that is not a credential is no longer read as a healthy grant either: it has no expiry, the sweep called it NotDue forever, and it is now recorded as needing a sign-in and handed to Weles. Everything that can stop that loop is named per account — automatic_sign_in on every pool row, automatic_sign_in.blocked in the readiness answer, and one of eight blocked_by words listed in the credentials runbook — including a gateway running on a host the registry does not place one on.