CI and images / lint (push) Successful in 3s
CI and images / extension-version (push) Successful in 2s
CI and images / frontend-build (push) Successful in 19s
extension / lint (push) Successful in 21s
CI and images / backend-lint-and-test (push) Successful in 33s
CI and images / integration (push) Successful in 2m25s
CI and images / sign-extension (push) Successful in 4s
CI and images / build-web (push) Successful in 2m1s
CI and images / smoke-web (push) Successful in 1m2s
CI and images / build-agent (push) Successful in 10m7s
CI and images / promote (push) Skipped
Operator: the agent's build string could not identify the agent. VERSION was a
literal in app.py an author was meant to bump, and nobody did — the September
image printed the same "2026-07-17.1" as the July one, so the one surface
meant to answer "did my pull work?" answered the same either way.
Nothing new was needed. scripts/artifacts.sh has derived a version per
artifact since milestone 313, and build-agent has been computing the agent's
on every run and printing it to the log. The image just never carried it.
Three values, never folded together (rule 149):
FC_VERSION YYYY.MM.DD.HHMM from the COMMIT its shipped files last changed
in — identical on dev and main for the same source, which is
what makes "am I running production's code?" answerable.
FC_CHANNEL a sibling field, never a suffix inside the name.
FC_REVISION the 12-char sha; the same string as the fc.revision LABEL, so
the image and the registry cannot disagree about which commit
this is.
The page SHOWS the version and COMPARES the revision. Those were one value
before, which is how a version acquires a second job and then cannot be
changed without breaking the reload banner. An unstamped local build reads
`unknown` and compares `local` — absent rather than empty, one spelling of
"cannot say".
scripts/artifacts.sh joins the AGENT path set in the same commit, and it had
to: a version has no backstop. A revision that is computed differently stops
matching the published label and forces a rebuild, so it self-corrects; a
version is compared against nothing, so a change to cmd_version alone would
leave the agent publishing the old format with nothing to contradict it. That
is #3202's finding, and the agent was rightly exempt only while it had no
version of its own. tests/test_artifact_paths.py pins it.
Also corrects two build.yml comments claiming agent/ had not changed since
2026-07-17. Both were already false — it changed 2026-09-23 — and one of them
is the stated rationale for the force_build escape hatch. Rewritten without
dates: how long an artifact has been quiet is a `git log` question, and its
answer in a comment is wrong the next time anyone commits (lesson #4383).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVjrnpQjRgHdvq95rASoiR
121 lines
4.6 KiB
Python
121 lines
4.6 KiB
Python
"""The agent's self-report: a derived version, and a build id kept apart from it.
|
|
|
|
The agent's version used to be a literal in `app.py` that an author was asked
|
|
to bump on every change. Nobody did, so the September image printed the same
|
|
`2026-07-17.1` as the July one — the one surface meant to answer *"did my pull
|
|
work?"* gave the same answer either way.
|
|
|
|
These pin the two properties that make the replacement worth having: the
|
|
displayed string degrades honestly when a build carries no stamp, and the value
|
|
the page COMPARES is not the value it SHOWS (rule 149).
|
|
|
|
`build_info` is stdlib-only on purpose, so the suite can import it — `app` is
|
|
unimportable here (torch, transformers, ultralytics are not in the CI image,
|
|
which is why build.yml lints the agent and then only `compileall`s it).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from agent.fc_agent import build_info
|
|
|
|
APP = Path(__file__).resolve().parents[1] / "agent" / "fc_agent" / "app.py"
|
|
|
|
|
|
def _stamp(monkeypatch, *, version="", channel="", revision=""):
|
|
monkeypatch.setattr(build_info, "FC_VERSION", version)
|
|
monkeypatch.setattr(build_info, "FC_CHANNEL", channel)
|
|
monkeypatch.setattr(build_info, "FC_REVISION", revision)
|
|
|
|
|
|
# --- what a person reads -----------------------------------------------------
|
|
|
|
|
|
def test_an_unstamped_build_says_unknown_rather_than_nothing(monkeypatch):
|
|
"""A blank in the meta line reads as "no version", which is a different and
|
|
false claim from "this build does not carry one". Same posture the web
|
|
image's build_info takes, for the same reason."""
|
|
_stamp(monkeypatch)
|
|
|
|
assert build_info.display_version() == "unknown"
|
|
|
|
|
|
def test_the_channel_rides_beside_the_version_not_inside_it(monkeypatch):
|
|
"""Rule 149. The channel is a sibling field; a `-dev` folded into the name
|
|
is issue #2993's exact failure. Rendering it in parentheses is display, not
|
|
encoding — `FC_VERSION` itself never carries it, which the next test is
|
|
what actually holds."""
|
|
_stamp(monkeypatch, version="2026.09.24.1052", channel="dev")
|
|
|
|
assert build_info.display_version() == "2026.09.24.1052 (dev)"
|
|
|
|
|
|
def test_a_version_with_no_channel_is_the_bare_version(monkeypatch):
|
|
_stamp(monkeypatch, version="2026.09.24.1052")
|
|
|
|
assert build_info.display_version() == "2026.09.24.1052"
|
|
|
|
|
|
# --- what the page decides on ------------------------------------------------
|
|
|
|
|
|
def test_the_build_id_prefers_the_revision(monkeypatch):
|
|
"""Two builds of the same commit ARE the same agent and must not prompt a
|
|
reload; two different commits always differ here, even when they land in
|
|
the same minute and derive one version name. The revision is the only one
|
|
of the three that is true of the content rather than of the clock."""
|
|
_stamp(monkeypatch, version="2026.09.24.1052", revision="a3071a754907")
|
|
|
|
assert build_info.build_id() == "a3071a754907"
|
|
|
|
|
|
def test_the_build_id_falls_back_to_the_version_then_to_a_constant(monkeypatch):
|
|
_stamp(monkeypatch, version="2026.09.24.1052")
|
|
assert build_info.build_id() == "2026.09.24.1052"
|
|
|
|
_stamp(monkeypatch)
|
|
# A constant, deliberately — see build_info.build_id. A per-process nonce
|
|
# would make every ordinary container RESTART claim a new version had
|
|
# arrived, a false positive on the surface the banner exists to keep
|
|
# trustworthy.
|
|
assert build_info.build_id() == "local"
|
|
|
|
|
|
def test_the_displayed_string_is_never_the_compared_one(monkeypatch):
|
|
"""The property both of the above serve, stated once on its own. Folding
|
|
these together is how the version acquires a second job and then cannot be
|
|
changed without breaking the reload banner."""
|
|
_stamp(monkeypatch, version="2026.09.24.1052", channel="dev",
|
|
revision="a3071a754907")
|
|
|
|
assert build_info.display_version() != build_info.build_id()
|
|
|
|
|
|
# --- the page wires them to the right places ---------------------------------
|
|
#
|
|
# Read as SOURCE, not imported: `app` pulls the agent's GPU stack. Asserting on
|
|
# what is PRESENT rather than on what is absent, deliberately — a guard that
|
|
# greps for the absence of a pattern trips over the comment explaining why the
|
|
# pattern is gone (lesson #4365), and app.py's new comment names the old
|
|
# literal it replaced.
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"marker, why",
|
|
[
|
|
(
|
|
'id=build>__VERSION__',
|
|
"the meta line must show the human-readable version",
|
|
),
|
|
(
|
|
'const PAGE_BUILD="__BUILD_ID__"',
|
|
"the staleness check must compare the build id, not the version",
|
|
),
|
|
],
|
|
)
|
|
def test_the_control_page_shows_one_value_and_compares_the_other(marker, why):
|
|
assert marker in APP.read_text(), why
|