Release and versioning¶
The root README defines Brama's product contract. This document defines how that contract is versioned, built, published, upgraded, and recovered.
Current release state¶
Brama is pre-1.0 and does publish immutable binary releases. main remains
mutable development source and is never a production coordinate.
Read the release records rather than treating a version copied into prose as the newest release:
released-surface.jsonnames the newest release this repository has recorded;brama version baseline --writeregenerates it from that release rather than from the working tree, andbrama version baselinechecks it against the channel that published it;- the canonical Stado release receipt names the immutable source snapshot, staged archive, digest, qualification, and promoted channel.
A tag alone is not a release. Treat a coordinate as installable only after Stado has stored the signed release receipt and reconciled its channel.
Canonical product version¶
The version field in Cargo.toml is the only product version source. CLI and
MCP version output, health output, release manifests, tags, artifact paths, and
release notes derive from it.
Product version and source revision are separate:
product_version: SemVer fromCargo.toml;source_revision: the immutable source snapshot selected by Stado;platform: target OS and architecture;artifact_digest: SHA-256 of the immutable runtime archive;built_at: UTC build timestamp;provenance: the canonical Stado release receipt.
A commit SHA must not be presented as the product version. An artifact without all five identities is not releasable.
Version policy and public contract¶
Brama uses Semantic Versioning. Before 1.0.0:
- an incompatible change advances
MINORand resetsPATCH; - an additive or corrective compatible change advances
PATCH; 1.0.0is an explicit stability declaration.
The public contract includes:
- CLI commands, flags, machine-readable output, and exit behavior;
- MCP protocol version, tool names, schemas, and read-only boundary;
- HTTP methods, paths, request and response fields, status codes, error codes, selectors, aliases, and authentication headers;
- environment and generated configuration fields;
- journal record kinds and durable state interpretation;
- capability resource naming and ownership;
- release archive layout and launcher behavior;
- documented limits, retry, cost, compatibility, and recovery behavior.
brama version surface prints the mechanically visible part of this contract: the
command list a caller invokes by name, read from the binary's own declaration and refused when
src/main.rs, parsed as Rust (the struct deriving Parser, its
#[command(subcommand)] enum and each variant's #[command(name, hide, skip)]),
disagrees; that parse is what reads a published revision's surface through
git show, and a revision whose declaration does not parse is refused with the
parser's reason. released-surface.json is the immutable
published baseline, never a candidate regenerated from the same working tree.
brama version contract asks the AutoVersion rule, through the AutoVersion crate Brama builds with,
what the candidate surface requires and compares that with the version Cargo.toml
declares; brama version conformance --fixtures FIXTURES.md proves that crate
against AutoVersion's shared fixtures before the workflow trusts it. The
declared version may run ahead of the required one within the same series only when every
coordinate it skips was declared by an earlier commit on the branch: the Stado release plane
never reuses a coordinate, so a build that failed at one patch makes the following patch the
next honest one, and the gate says which coordinates were consumed. A jump nothing accounts
for is refused, naming the first coordinate that was never declared. An earlier commit whose
Cargo.toml cannot be shown or names no version refuses the comparison with that
commit and the cause, rather than being left out of the history, so a coordinate it declared
is never reported as undeclared. With
--self-check the gate proves it can still refuse before it trusts its own verdict.
Two entries in the list above — the release archive layout and the launcher's
behavior — are contract that no extractor can see, so a release owner declares
that breakage instead of hoping a command list reveals it. The declaration lives
in declared-breakage.json, naming the version it belongs to and why, and
brama version contract passes it to the rule as a declared break, which escalates the
class and can never lower it. It applies only while Cargo.toml still declares
that version, so it expires on its own rather than becoming a switch left on that
makes every later release look breaking.
Release channels¶
- Development:
main; maintainers only; mutable; no production guarantee. - Preview: immutable
vX.Y.Z-rc.N; staging and explicit canaries; retained with qualification evidence. - Stable: immutable
vX.Y.Z; production callers; promoted from the exact preview bytes whenever a preview exists.
There is no latest production contract. Discovery labels may point to an
immutable release, but installation and rollback always resolve SemVer,
revision, platform, and digest. /download/<platform>/<file> on this
site is such a label: it reads the release of the version these pages document.
Release artifacts¶
A release stores one immutable archive and signed receipt per platform:
stado://releases/brama/X.Y.Z/linux-amd64/release.tar.gz
stado://releases/brama/X.Y.Z/darwin-arm64/release.tar.gz
Anyone can download them, with their signed manifest and signature, at
https://brama.wisent.com/download/<version>/<platform>/<file>;
Download shows how to verify them.
Each archive expands to:
bin/brama
bin/skarbiec-entitlements-router
bin/start-with-skarbiec
bin/provision-skarbiec-trust
bin/launcher/*.sh
bin/launcher/policy/*.sh
libexec/generate-skarbiec-config.mjs
libexec/skarbiec-config/keys.mjs
libexec/skarbiec-config/requirement.mjs
LICENSE
The archive carries no installation's choices: no subscriptions and no donation
recipient keys. The recipients an installation encrypts donated subscriptions to are
declared on the host it runs on
(BRAMA_DONATION_RECIPIENT_KEYS).
bin/skarbiec-entitlements-router is a second product's binary. The
.wisent-release.json input pins its immutable Skarbiec source URI and SHA-256,
and Stado verifies and mounts that snapshot before invoking the build. Brama
therefore ships the compatible router without cloning another provider or
asking an operator to match two versions by hand. Stado records source inputs,
builder identity, stage paths, and the final archive digest in the release
receipt.
An archive never contains the Skarbiec vault, provider credentials, host-specific
runtime configuration, Brama journal state, or any signing key.
SKARBIEC_VAULT_FILE must always be supplied by the operator.
Trust material is provisioned per installation¶
The signed policy, workload registry, trust root, WORM receipt command and
workload proof key are not in the archive. bin/provision-skarbiec-trust
generates them on the host, once, before the first start, and
bin/start-with-skarbiec refuses to start while any of them is missing.
Two reasons, and the first is the one that decides it. The registry pins the absolute path and the SHA-256 of the binary permitted to redeem a capability, and a build machine cannot know where the artifact will be installed. The second is that a copy generated at build time would ship one Ed25519 proof seed inside every download of that archive, so the workload identity of every installation would be a value any downloader already holds.
Re-provisioning replaces the installation's identity, so
provision-skarbiec-trust refuses to overwrite existing material unless
--force is given, and the capabilities bound to the previous key must be
re-granted afterwards.
When an installation runs against a registry that does not describe it, the
symptom is not a startup failure. Brama comes up, /health answers 200, and
every request needing a provider credential returns 503
dependency_unavailable with capability redemption denied: peer mismatch on
stderr. The message names none of the four fields it compared, and three of
them can be right while the fourth is wrong.
The registry pins executable_path, executable_sha256,
uid, gid and, on macOS, the designated code-signing requirement.
bin/provision-skarbiec-trust uses the provisioning account's uid and gid;
BRAMA_WORKLOAD_UID and BRAMA_WORKLOAD_GID override them.
The launcher now does this itself. When the registry in the installation it is
about to run does not describe that installation - wrong path, wrong digest,
wrong account - it provisions before starting rather than refusing, because the
bundle carries everything the generator needs and the account that runs the
service is the account the registry must name. Copying an installation to a new
directory, which is what the fleet does every time it materialises an artifact
under a digest-named path, therefore heals on the next start instead of
producing a gateway that answers /health and serves nothing.
brama diagnose prints that comparison for every installed
generation, beside the units that start Brama, the capabilities actually issued
against the providers the policy grants, each alias route against those
capabilities, where the gateway is reachable and by which scheme, and the
current boot attempt. Run it before concluding anything about a redemption
refusal.
Qualification gate¶
Before promoting a stable Stado release, the release owner records:
- README, onboarding, core, integration, example, and test contracts agree;
- the public-surface decision and required version agree;
- the selected clean revision and builder inputs are immutable;
- the built binary reports the expected product version and source revision;
- archive layout, provenance, and digest are complete;
- approved local, integration, credentialed, recovery, and security evidence is recorded with omitted layers called out;
- upgrade and rollback are actionable for the candidate;
CHANGELOG.mdcontains user-impact release notes.
No narrow check qualifies unexecuted layers.
Release procedure¶
- Select one clean source revision from
main. - Review the full public contract, not only CLI command names.
- Select and commit the required SemVer change in
Cargo.tomland lockfile. - Update
CHANGELOG.md, compatibility, migration, and operator actions. - Submit the immutable source snapshot to Stado.
- Let Stado resolve
.wisent-release.json, verify pinned inputs, run the platform recipe, run its post-build journeys against what the build staged, stage the archive, and store the signed release receipt. The binary platforms runtests/release/journey.sh launcher-verbs: the stagedbrama launcher verbsagainst the staged router (tests/launcher/verbs.rs), compiled from the source, inputs and dependenciessrc/release/build.shrecords in.build/cargo.sh, its commands and answers kept underlauncher-verbs/in the qualification output Stado retains with the release (WISENT_TEST_EVIDENCE_DIR). The web platform runsnode vercel-ingress/tools/docs-navigation.mjs checkover the pages it publishes. A platform without a passing journey qualifies nothing it carries. - Promote the exact version to
candidate. - Reconcile the candidate on the target service and record qualification.
- Promote the same immutable receipts to
stable. - Update
released-surface.jsonfrom the complete canonical release.
Steps that execute validation or contact environments require the explicit
approval described in TESTING.md.
The maintained runner entry point, .github/runner/reconcile-release-runner.sh, uses its service account's exported HOME and requires BRAMA_RUNNER_NAME from the service definition. Neither defaults to a personal account or machine name. A missing value refuses before download or registration. BRAMA_RUNNER_ROOT, labels, repository URL and registration-token file keep their declared overrides. Inspect the resulting registration in GitHub Settings → Actions → Runners; an accepted configuration alone does not prove the runner is serving jobs.
Raw run records stay in private evidence storage or ignored local output. The legacy .evidence/ directory is ignored and is not an audit exception for source files. Removing those records from Git's index does not rewrite their observations or delete the retained local files. New release evidence must follow the normal release receipt and artifact flow, not a source commit.
Release control workflow¶
The release-control Actions form and gh workflow run release-control.yml
invoke the same Stado operations. Every invocation requires runner_label
for a Stado-selected macOS ARM64 control runner. In the selected GitHub environment,
set STADO_CONFIG_PATH to its readable absolute Stado configuration file
and STADO_BIN_PATH to its executable absolute Stado binary. Neither has
a built-in account path. Missing or unusable paths fail before release mutation.
submit-candidateandsubmit-stablerequireversion,source_directoryandsource_revision. The directory is an existing checkout on that runner; the revision is a full lowercase 40-character commit. Its committed manifest must name Brama. Stado reads that exact tree without changing the checkout and requires its version declaration to match the requested version.promote-candidateandpromote-stablerequire onlyversionbeside the runner label and consume qualified published bytes.statusandrollbackaccept no version or source inputs. Rollback restores the previous desired release; it does not select an arbitrary version.
gh workflow run release-control.yml -f action=submit-candidate \
-f runner_label=<label> -f version=<semver> \
-f source_directory=<absolute-checkout> -f source_revision=<full-sha>
Stable submission, stable promotion and rollback use release-production;
candidate submission uses release-qualification; other actions use
release-operations. Configure approval and identity grants on those environments.
The workflow prints Stado's JSON and preserves its refusal. Successful submission
means a run was accepted, not that its qualification or rollout passed. Read
stado release status brama --json for the actual persisted result.
The workflow neither restarts credential services nor creates qualification receipts.
The former prepare action and rollback_compatible_with input
are removed; use submit-candidate and Stado's recorded release policy.
Release notes¶
Each release section in CHANGELOG.md states:
- added, changed, corrected, removed, and deprecated behavior;
- security-relevant changes;
- HTTP, CLI, MCP, configuration, state, and capability changes;
- compatibility and migration requirements;
- operator actions before and after upgrade;
- known limitations;
- exact source revision, platforms, artifact digests, and provenance objects.
Commit titles alone are not release notes.
The documentation pages under vercel-ingress/docs are the source; no
markdown copy is rendered into them. Their navigation has one source too: the
All topics directory on the docs home page. node
vercel-ingress/tools/docs-navigation.mjs check names every page whose mobile menu or
sidebar differs from that directory, every page that wears no navigation, every
Documentation topic note naming a file the repository does not have, and every
directory link no vercel.json rewrite serves, exiting 1 when it names any and
2 when its inputs cannot be read. write renders the directory into every
routed page, marking the page itself, removes the stale notes, and names only what a
person must place: a page without navigation lines and a link to nothing. A topic is
renamed or added in the directory, then write carries it to every page.
Compatibility and migrations¶
Every release declares compatibility for:
- callers versus server;
- launcher, packaged router, and Brama binary;
- current and previous configuration documents;
- journal record versions and donated-subscription overlay;
- Skarbiec wire protocol and capability resource naming;
- provider protocols and catalog schema;
- rolling or mixed-version service operation.
Brama currently has no database migration. Any future durable-state change must name preconditions, backup, duration, service impact, retry/resume behavior, forward path, and rollback. A release must not silently reinterpret journal records.
Upgrade¶
- Resolve the target SemVer, platform, archive URI, and digest from Stado.
- Read every intervening release note and required operator action.
- Preserve the append-only journal and any operator-required vault backup.
- Let Stado install the digest-pinned archive under a new versioned directory.
- Reconcile the selected channel to the host service manager.
- Confirm the approved health/version outcome and one authorized catalog path.
- Retain the previous immutable runtime until the rollback window closes.
Rollback and recovery¶
Rollback identifies the previous SemVer, source revision, platform, digest, and staged launcher path. The operator must:
- stop promotion and prevent concurrent launchers from mutating the same overlay;
- preserve journal and capability evidence;
- confirm the previous release can interpret current non-secret state;
- point the host service manager at the previous verified installation;
- confirm build identity and the approved health/version outcome;
- confirm one authorized non-billable discovery path;
- revoke or rotate credentials only when compromise, not code regression, requires it.
If a release changes durable state incompatibly and no reverse migration is qualified, rollback is unsupported and the release must say so before upgrade.
Security and ownership¶
Build, signing, GitHub publication, bearer verification, request-sign, and provider runtime identities are separate least-privilege credentials. Runtime credentials never enter artifacts, provenance, or GitHub. Release maintainers own source/version/provenance; deployment operators own host, network, runtime grants, state backup, and rollback execution.