Skip to content

Blocking incompatibility when the CLI version doesn't match the tenant's #101

Description

@cubeorgdev

What's missing

A tenant and a blue CLI can be on different versions and nothing stops the developer.

  • The tenant's expected client version travels as minimum_client_version, and the schema is explicit that it is decoration: "Operator-visible rollout floor. Capability negotiation, not this field, enforces whether a client may consume the document." (crates/gh-service/src/schema.rs:26-30). ensure_client_compatible (schema.rs:84-105) never reads it — there's a test asserting a 999.0.0 floor still passes (schema.rs:562-569).
  • What does fail is contract_version / required_capabilities, and it fails badly for a human: governance requires unsupported client capabilities: <slug>, <slug> names capability strings, not a version, and offers no way out.
  • Nothing covers a client newer than the tenant. Discovery carries only a document version: 1, no deployment version (crates/gh-service/src/discovery.rs:6-18; server side services/control-api/src/lib.rs:1970-1988).
  • The server already knows: every client posts client_version, platform and architecture on status (crates/gh-cli/src/commands.rs:1892-1894) and the dashboard renders them (control-api/src/lib.rs:5644, :5786). It just never acts on the mismatch.

So a developer on the wrong CLI either hits a cryptic capability error or silently drifts against the tenant's config.

Desired behaviour

  1. The tenant publishes the CLI version it supports (exact pin, or floor + ceiling).
  2. On config fetch — and ideally at first-run discovery — the client compares its own CARGO_PKG_VERSION and, on mismatch, hard-blocks. No governed launch, no passthrough, no falling back to a stale cache. This is a blocking incompatibility, not a warning.
  3. Blocking is symmetric: a client newer than the tenant is blocked the same as an older one. Downgrading is a legitimate resolution.
  4. The block message names both versions and offers exactly two exits:
    • Upgrade / downgrade to the required version — with a copy-paste command for the platform the client is actually running on.
    • blue reset to detach from the tenant (commands.rs:361-387, detach_tenant at :326). Detach must keep working while blocked.

Remediation text, per platform

Point at the release page for the required version, https://github.com/BlocksOrg/blue/releases/tag/v<required>, and print one pinned command — the one matching the running platform, not the whole matrix. Both installers already accept a pin via BLUE_VERSION (scripts/install.sh:6, scripts/install.ps1:5), so no new installer flags are needed.

macOS / Linux:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/BlocksOrg/blue/releases/download/v<required>/install.sh | BLUE_VERSION=v<required> sh

Windows PowerShell:

$env:BLUE_VERSION = "v<required>"
irm https://github.com/BlocksOrg/blue/releases/download/v<required>/install.ps1 | iex

For anyone who'd rather grab the archive directly, name the one asset for their target: blue-v<required>-<target>.tar.gz (.zip on Windows), where <target> is one of the six published in .github/workflows/release.yml:57-75aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-musl, aarch64-unknown-linux-musl, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc. The client can resolve that from std::env::consts::OS / ARCH using the same mapping the installers use (install.sh:18-33, install.ps1:8-12).

Where it hooks in

  • Wire field — either promote minimum_client_version to enforced and add a ceiling, or add a dedicated enforced constraint: crates/gh-service/src/schema.rs:14-60, contract deploy/contract/governance.openapi.yaml:1663, docs apps/docs/next/reference/governance-config.mdx:81 and apps/docs/next/deployment/blue-yaml.mdx:295. If minimum_client_version changes meaning, that's a contract change — the current wording promises the opposite.
  • The check — extend ensure_client_compatible (schema.rs:84-105). It's already called on every fetch path (gh-service/src/client.rs:222, source.rs:154, source.rs:182), so a hard error there fails closed for free.
  • The messageensure_client_compatible returns Result<(), String>; rendering a platform-specific remediation plus the blue reset hint wants something richer. HarnessCompatibilityError::with_install_hint (commands.rs:2200) already does exactly this job for harness versions — worth mirroring.
  • First run — catching it in discover_configuration (commands.rs:98-117) would block before OAuth instead of after, but that means putting a version in the unauthenticated discovery document. See open questions.
  • Dashboard — the client list already shows client_version; flagging incompatible clients there is a natural follow-up.

Open questions

  • Exact pin or floor + ceiling? A range is kinder to rolling upgrades; an exact pin is far easier to explain in the error message.
  • Advertise the version pre-auth (discovery) as well as post-auth (governance-config)? Pre-auth gives a better first-run experience but exposes the deployment version to unauthenticated callers.
  • Interaction with required: true and the offline/stale-cache path — a blocked client must not quietly serve the cached config.

Acceptance

  • A mismatched CLI, older or newer, blocks every governed launch with one message naming the installed version and the required version.
  • That message contains a runnable, version-pinned install command for the running platform, plus the release-page link.
  • blue reset is offered in the message and succeeds from the blocked state.
  • A matching CLI is unaffected; tests cover older / newer / in-range, and a golden message per published target.

Blue version: main @ 34abb7f (workspace 0.1.0)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions