gates: the newest release header must state its MinAgent; backfill v0.233.0-v0.236.0 (R-470, R-472)
gates / gates (push) Successful in 12s
gates / gates (push) Successful in 12s
From hub v0.112.0 a floor above the golden is served only with a declared
MinAgent, read from this CHANGELOG's newest `## vX.Y.Z` header. New fast,
blocking gate minagent_header_gate.py: that block must contain a line
starting `**MinAgent: X.Y.Z**`; prose, a code span or a non-bold mention
does not count (decoy test; red-proof F: relaxing the match to a body
mention fails test_decoy_body_mention_is_not_the_line).
Backfill: v0.233.0, v0.234.0, v0.235.0 and v0.236.0 carried no MinAgent
line. Each now reads `**MinAgent: 0.129.0** (unchanged)`, the value of the
nearest earlier header (v0.232.0). Proof it did not move: zero commits
under controller/internal/agentapi since 2026-09-01 (last: bb50e12,
2026-08-14), and the highest featureMinAgent entry is 0.129.0.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""minagent_header_gate.py — the newest release header must state its MinAgent (R-470, R-472).
|
||||
|
||||
Run from controller/: python3 scripts/minagent_header_gate.py
|
||||
Exit 0 clean · 1 the newest `## vX.Y.Z` block has no `**MinAgent: X.Y.Z**` line · 2 inconclusive.
|
||||
|
||||
WHY THIS EXISTS. From hub v0.112.0 a controller release can reach the fleet by the managed floor
|
||||
WITHOUT a golden, because the operator declares the release's agent requirement with the floor
|
||||
(operator ruling 2026-09-13, R-472). That declaration is READ from this file's newest header — the
|
||||
`**MinAgent: 0.129.0** (unchanged)` line `RUNBOOK-manual-build.md` §4.1 already told the vouch to
|
||||
read. Four headers (v0.233.0 … v0.236.0) did not carry it (R-470), so the value the hub is now told
|
||||
to trust would have had to be guessed. A rule with no gate is a wish; this is the gate.
|
||||
|
||||
WHAT COUNTS. The block from the newest `## vX.Y.Z` heading to the next `## ` heading must contain a
|
||||
line that STARTS with `**MinAgent: <X.Y.Z>**`. A mention of the word anywhere else — prose saying
|
||||
"MinAgent is unchanged", a code span, the heading itself — is the label without the fact (R-421)
|
||||
and does not count.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
SCRIPTS = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO = os.path.dirname(os.path.dirname(SCRIPTS))
|
||||
CHANGELOG = os.environ.get("MINAGENT_GATE_CHANGELOG", os.path.join(REPO, "CHANGELOG.md"))
|
||||
|
||||
HEADER_RE = re.compile(r"^##\s+v(\d+\.\d+\.\d+)\b")
|
||||
MINAGENT_LINE_RE = re.compile(r"^\*\*MinAgent:\s*(\d+\.\d+\.\d+)\*\*")
|
||||
|
||||
|
||||
def newest_block(lines):
|
||||
"""(version, block_lines) for the newest `## vX.Y.Z` heading, or (None, [])."""
|
||||
start, version = None, None
|
||||
for i, line in enumerate(lines):
|
||||
m = HEADER_RE.match(line)
|
||||
if m:
|
||||
start, version = i, m.group(1)
|
||||
break
|
||||
if start is None:
|
||||
return None, []
|
||||
block = []
|
||||
for line in lines[start + 1:]:
|
||||
if line.startswith("## "):
|
||||
break
|
||||
block.append(line)
|
||||
return version, block
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
lines = io.open(CHANGELOG, encoding="utf-8").read().splitlines()
|
||||
except OSError as e:
|
||||
print("minagent header gate INCONCLUSIVE: cannot read %s (%s)" % (CHANGELOG, e))
|
||||
return 2
|
||||
version, block = newest_block(lines)
|
||||
if version is None:
|
||||
print("minagent header gate INCONCLUSIVE: no '## vX.Y.Z' heading in %s" % CHANGELOG)
|
||||
return 2
|
||||
for line in block:
|
||||
m = MINAGENT_LINE_RE.match(line.strip())
|
||||
if m:
|
||||
print("minagent header gate OK — v%s declares MinAgent %s" % (version, m.group(1)))
|
||||
return 0
|
||||
print("minagent header gate FAILED: the newest release header v%s has no '**MinAgent: X.Y.Z**' line." % version)
|
||||
print("From hub v0.112.0 the floor carries a release past the golden only with a DECLARED MinAgent, and")
|
||||
print("that value is read from this line (R-472). Add it directly under the heading, e.g.:")
|
||||
print(" **MinAgent: 0.129.0** (unchanged)")
|
||||
print("Read the value from internal/agentapi/features.go (the highest featureMinAgent) — never guess it.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user