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.