"""What this agent build IS — stamped at image build time, not configurable. The mirror of `backend/app/build_info.py`, for the same reasons and with the same posture. Kept as its own module rather than as constants in `app.py` because it is stdlib-only and therefore importable by the test suite, which cannot import `app` (torch, transformers and ultralytics are not in the CI image — see build.yml's "Agent syntax check"). ## Why this replaced a hand-written string `app.VERSION` used to be a literal an author was asked to bump, carrying a version AND a changelog in one string: VERSION = "2026-07-17.1 · idle model-unload: after ~5 min idle ..." Nobody bumped it. The September image printed the identical string to the July one, so the one surface that was supposed to answer *"did my pull work?"* answered *"2026-07-17.1"* either way. An artifact that cannot identify itself is worse than one that says nothing, because the stale value reads as an answer. The values are now derived by `scripts/artifacts.sh` from the commit its shipped files last changed in — the same derivation the web image has used since milestone 313, and the same one the reuse check already ran for the agent and discarded. ## Three values, never folded together (rule 149) * `FC_VERSION` — the NAME, `YYYY.MM.DD.HHMM` UTC, derived from COMMIT time. For people to read and quote. Identical on `dev` and `main` for the same source, which is the property that makes "am I running the same code as production?" answerable at a glance. * `FC_CHANNEL` — a SIBLING field, never a suffix inside the name. * `FC_REVISION` — the 12-char commit sha, the artifact's IDENTITY. This is what the reuse check already keys on as the `fc.revision` image label. **Absent rather than empty when unknown.** A locally built image has no stamp, and neither does any image predating this module. One spelling of "cannot say" instead of two. """ import os FC_VERSION = os.environ.get("FC_VERSION", "").strip() FC_CHANNEL = os.environ.get("FC_CHANNEL", "").strip() FC_REVISION = os.environ.get("FC_REVISION", "").strip() def display_version() -> str: """The build, as a line for a human: `2026.09.24.1052 (dev)`. `unknown` rather than a blank when unstamped — an empty slot in the meta line reads as "no version", which is a different and false claim from "this build does not carry one". """ if not FC_VERSION: return "unknown" return f"{FC_VERSION} ({FC_CHANNEL})" if FC_CHANNEL else FC_VERSION def build_id() -> str: """The token the control page compares against `/status` to notice it is showing a CACHED page from a previous build. Deliberately NOT `display_version()`. That is the value for reading; this is the value for deciding, and folding the two is what rule 149 is about. The revision is the better discriminator of the two — two builds of the same commit ARE the same agent and should not prompt a reload, and two different commits always differ here even when they land in the same minute and derive one version name. A locally built image falls through to a constant, so the reload banner cannot fire for it. That is honest rather than a gap: nothing in an unstamped image knows what source it was built from, and a per-process nonce would make every ordinary container RESTART claim a new version had arrived — a false positive on the exact surface the banner exists to keep trustworthy. """ return FC_REVISION or FC_VERSION or "local"