Status: Accepted (user decision 2026-08-15). CLI mechanics instantiated by w4/m34–m35.
The monorepo ships things with different consumers: platform services we run ourselves (continuously deployed via deploy.yml → GitOps digest pin → Argo), and user-installed artifacts (the bex CLI today; mobile later). Building the CLI's release channel forced the question of how the repo's components are versioned — per component, or together.
bex converges on one shared version — Kubernetes-style lockstep, including the CLI. Like kubectl and the control plane, the version number itself is the compatibility statement. The gh model (client and server versioned independently, forever) is rejected.
- The train:
bex/vX.Y.Zcovers operator + bex-api + ssh-gateway + dashboard + the self-host install artifact (Helm/kustomize). These already build and test as one unit (lego/is one image with multiple entrypoints); the shared version recognizes that. - Launch = self-host delivery (≈ 1.0). A shared version stands for upgrade obligations — cross-version upgrade paths, CRD schema and control-plane DB migrations. The train launches when we take those on, not before.
- Until launch: services carry no version tags — their identity is image digest + git SHA in the GitOps pin ledger. The CLI rides
bex-cli/v0.xindependently; 0.x explicitly means "pre-convergence". At launch it jumps to the shared version, accepts kubectl-style empty releases, and gets a documented skew policy (e.g. ±1 minor, enforceable by the m34 update notice). - After launch, both identities coexist: our own cluster keeps deploying continuously from
mainby digest; each platform release casts a tested digest set into a version for external operators. The train must not slow our own cadence. - Exemptions: mobile (store/OTA cadence cannot lockstep) and platform-pinned assets (e.g. the agent-sandbox image). They declare a compatibility range against the platform version instead of sharing the number.
- Internal dependencies are never versioned (
lego/typesetc.): everything in the workspace is consumed at the same commit — atomic cross-component commits are the point of the monorepo.
Established by the CLI and reused by the train: the tag is the only version source (build-time injection, no VERSION files); releases are tag-triggered (cli-release.yml template: test gates → build matrix → checksums.txt → keyless cosign bundle → GitHub release → channels) and immutable; the shared repo's /releases is partitioned by tag prefix, so consumers always filter by prefix (never /releases/latest) and asset URLs carry the encoded %2F. Versions are minted only by the /release skill (sync shipped source → validate checks → select version → tag → watch → verify channels; invocation authorizes publication without a separate version confirmation); /ship lands code and never tags. Staleness reminder + tag ruleset: w4/032.