Files
AutopilotMonitor/docs/versioning.md
T
Oliver Kieselbach bbb55034e3 feat(versioning): one version contract for agent, backend and MCP
All three components now report <major>.<minor>.<build> plus a 7-char commit
and a build timestamp. The backend had been pinned at a hand-written 1.0.0
since inception and starts at 1.5.1000; the MCP server's package.json version
was bumped by hand and starts at 1.5.300, and it now reports its own commit on
/health instead of only the documentation bundle's.

The build number keeps coming from a counter blob reserved before the build
with ETag-CAS, but there is now one implementation instead of two copies:
.github/scripts/Request-BuildNumber.ps1, called by all three workflows and by
the local build.ps1. It lives under .github/ because scripts/Deployment/ is
gitignored (those scripts carry SAS tokens) and would therefore be absent from
a CI checkout; scripts/Build/ and scripts/Release/ are shadowed by the
[Bb]uild/ and [Rr]elease/ artifact ignores.

The counter cannot be replaced by the published manifest, which is the obvious
simplification to reach for: version.json is written only on stable publish, so
two consecutive dev builds would derive the same number and the second would
overwrite the first's artifact. git rev-list --count fails the same way, two
builds of one commit collide.

Both deploy workflows now poll their health endpoint until it reports exactly
the version and commit just deployed, and publish versions/<component>.json
only then. A green deploy step alone means the package was accepted, not that
it is being served -- so a manifest that disagrees with the live endpoint now
means the running instance drifted (rollback, stale revision) rather than a
pipeline that stopped halfway. The agent manifest gains the same commit and
buildUtc fields additively; the bootstrap reads it by field name, so older
installers ignore them, and sha256/bootstrapVersion stay untouched.

Requires the versions container plus AZURE_BLOB_VERSIONS_SAS_TOKEN and the two
seeded counters (backend 999, mcp 299) before the first deploy run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:41:03 +02:00

5.0 KiB

type, title, description, resource, tags, timestamp
type title description resource tags timestamp
Concept Version Contract — Agent, Backend, MCP One version shape for all three shipping components, the ETag-CAS counter blob that mints build numbers, and the published manifests that make "what is deployed" checkable from outside. /.github/scripts/Request-BuildNumber.ps1
build
ci
release
versioning
2026-07-22T00:00:00+02:00

Version Contract

Three components ship from this repository — the agent, the Functions backend and the MCP server. They share one version shape, one way of minting build numbers, and one manifest format, so that a version string means the same thing whoever emits it.

version   = <major>.<minor>.<build>    major.minor curated by hand, build minted per build
commit    = 7-char short SHA of this repo
buildUtc  = ISO-8601 build moment
Component major.minor from Counter blob Reported by
Agent V2 VersionPrefix in AutopilotMonitor.Agent.V2.csproj agent/buildcounter-v2.txt agent/version.json
Backend VersionPrefix in AutopilotMonitor.Functions.csproj versions/backend.txt GET /api/health
MCP version in package.json versions/mcp.txt GET /health, MCP handshake

Schema

Build numbers — reserve before build, ETag-CAS

.github/scripts/Request-BuildNumber.ps1 is the only implementation; the CI workflows and the local build.ps1 all call it.

  1. Anonymous GET on the counter blob → value N and ETag. The blob holds the last used number as plain text and is public-read; only the write needs the container SAS.
  2. PUT N+1 with If-Match: <etag>. The quoted ETag form is required — PowerShell strips the quotes on read, and an unquoted If-Match is rejected client-side.
  3. 412 Precondition Failed → a concurrent build reserved first → re-read and retry (5 attempts, linear backoff).
  4. Build with the reserved number.

Reserving before the build is what makes concurrent minting impossible. A failed build burns a number — deliberate: uniqueness matters, density does not.

Why a counter and not the published manifest: manifests are written on publish, not on build. The agent writes version.json only for stable releases, so deriving the next number from it would hand two consecutive dev builds the same number and let the second overwrite the first's artifact. A state-free scheme such as git rev-list --count HEAD fails for the same reason — two builds of one commit collide.

Seeding (once per component): PUT <last used number> with If-None-Match: *, so an existing counter can never be clobbered. Agent V2 was seeded with 1356 on 2026-07-17; versions/backend.txt with 999 and versions/mcp.txt with 299 on 2026-07-22.

Local builds

No counter is reached, the build number stays 0, and the commit is empty or unknown. A 1.5.0 with no commit hash on /api/health is therefore a developer machine, not a release — the fallback is the signal, not a defect. The agent is the exception: build.ps1 produces uploadable artifacts and reserves a real number unless -SkipUpload is passed.

Manifests

versions/backend.json and versions/mcp.json (public-read) record what is deployed:

{ "component": "backend", "version": "1.5.1000", "commit": "abc1234",
  "buildUtc": "…", "deployedUtc": "…", "runId": "…" }

The MCP manifest adds docsCommit, because for that component "which build" and "which documentation" are separate questions.

Each is written last and only after the deploy workflow has polled the component's health endpoint until it reports that exact version and commit. A green deploy step alone means the package was accepted, not that it is being served. Consequently a manifest that disagrees with the live endpoint means the running instance drifted after the fact (rollback, stale revision) rather than a half-finished pipeline.

The agent keeps its own manifest at agent/version.json — the bootstrap derives that URL from the download URL and verifies the ZIP against its sha256, so it cannot move. It carries the same commit and buildUtc fields; sha256 and bootstrapVersion remain, being the customer-facing integrity contract.

Consequences

  • Bumping major.minor is a human decision, made in the component's own file (VersionPrefix, or version in package.json). Nothing bumps it automatically.
  • A new component joins by seeding a counter blob and calling the same script; no new mechanism.
  • The counter blobs and manifests live in the versions container on autopilotmonitoreu (writes via AZURE_BLOB_VERSIONS_SAS_TOKEN). The agent's counter stays in the agent container next to the artifacts it numbers.

Citations

  • .github/scripts/Request-BuildNumber.ps1 — the CAS protocol
  • .github/workflows/deploy-backend.yml, deploy-mcp.yml — reserve, verify, publish
  • .github/workflows/build-agent.yml, scripts/Deployment/build.ps1 — agent line
  • Build Counter Blob — agent-specific consequences