mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
bbb55034e3
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>
109 lines
5.0 KiB
Markdown
109 lines
5.0 KiB
Markdown
---
|
|
type: Concept
|
|
title: Version Contract — Agent, Backend, MCP
|
|
description: 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.
|
|
resource: /.github/scripts/Request-BuildNumber.ps1
|
|
tags:
|
|
- build
|
|
- ci
|
|
- release
|
|
- versioning
|
|
timestamp: 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:
|
|
|
|
```json
|
|
{ "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/build-counter-blob.md) — agent-specific consequences
|