D50 = A (R-1290): the published gate asserts the newest 5 released versions plus the pinned MinAgent (0.158.1) and day-0 agent (0.165.0); a pin with no tag fails; decoys red-proved. No binary change.
gates / gates (push) Successful in 1m41s
gates / gates (push) Successful in 1m41s
This commit is contained in:
1 parent
6b6ba0c17f
commit
feaaa0e8ff
4 files changed
+93
-35
No files matched your search
@@ -1,3 +1,14 @@
|
|||||||
|
## 2026-10-25 (evening, attended) — the `published` gate follows D50 = A: the newest 5 plus the pinned MinAgent and day-0 agent (R-1290; no binary change)
|
||||||
|
|
||||||
|
- **D50 = A** (operator, 2026-10-25): the registry keeps the newest 5 released agent versions, the hub's MinAgent floor and
|
||||||
|
the day-0 manifest's agent. `scripts/retention-policy.json`: `generic_versions_kept` 10 → 5, new `pinned`
|
||||||
|
(`hub_min_agent` 0.158.1, `day0_agent` 0.165.0 — copies of hub state, because CI holds no hub secret; the session that
|
||||||
|
records a vouch or a new MinAgent updates them in the same commit). `check-published-versions.py` asserts the window
|
||||||
|
plus every pin; a pin with no tag FAILS. CI was red since the operator's hand prune of 0.159.0–0.160.0 (job 2334).
|
||||||
|
- **Decoys** (`test_gate_decoys.py`, via `FELHOM_RETENTION_POLICY`, test only): a pinned version older than the window is
|
||||||
|
asserted; its 404 convicts; a pin with no tag convicts. Seen failing on the old gate (two LIVE HOLES): felhom.eu
|
||||||
|
`documentation/audits/night-2026-10-25/redproof-d50.txt`.
|
||||||
|
|
||||||
## v0.165.0 (2026-10-25) — D43 = A: BYO hosts keep their own SSH settings (R-1257); the line test for a box that seeds all day (R-1250) — released in the attended release run; **VOUCHED by the operator 2026-10-25 18:38:49 CET** (binary `e9d04a47…`, bundle `8dea1f34…`)
|
## v0.165.0 (2026-10-25) — D43 = A: BYO hosts keep their own SSH settings (R-1257); the line test for a box that seeds all day (R-1250) — released in the attended release run; **VOUCHED by the operator 2026-10-25 18:38:49 CET** (binary `e9d04a47…`, bundle `8dea1f34…`)
|
||||||
|
|
||||||
**Found at the release (felhom.eu R-1285, P4):** on a BYO host upgraded from 0.164.0 the drop-in is removed only on the bundle run AFTER the one that installs this wrapper — the first run is planned by the old wrapper. Proven on a BYO test host (VM 9371 on demo-hp): the second `agent_config_update` logged `BUNDLE REMOVED … (ours, on a BYO host — D43)`.
|
**Found at the release (felhom.eu R-1285, P4):** on a BYO host upgraded from 0.164.0 the drop-in is removed only on the bundle run AFTER the one that installs this wrapper — the first run is planned by the old wrapper. Proven on a BYO test host (VM 9371 on demo-hp): the second `agent_config_update` logged `BUNDLE REMOVED … (ours, on a BYO host — D43)`.
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ TAG_RE = re.compile(r"^v(\d+\.\d+\.\d+)$")
|
|||||||
# THE retention number, read from the one file that owns it. A check and the policy it enforces
|
# THE retention number, read from the one file that owns it. A check and the policy it enforces
|
||||||
# must read the same number from the same place, or they drift and the drift looks like a defect
|
# must read the same number from the same place, or they drift and the drift looks like a defect
|
||||||
# in something else — which is exactly what happened on 2026-08-08/09 (R-287).
|
# in something else — which is exactly what happened on 2026-08-08/09 (R-287).
|
||||||
RETENTION_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "retention-policy.json")
|
RETENTION_FILE = os.environ.get("FELHOM_RETENTION_POLICY") or \
|
||||||
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "retention-policy.json") # the env: decoy tests only
|
||||||
|
|
||||||
|
|
||||||
def retention_kept():
|
def retention_kept():
|
||||||
@@ -114,6 +115,21 @@ def retention_kept():
|
|||||||
raise ValueError("generic_versions_kept must be a positive int, got %r" % (n,))
|
raise ValueError("generic_versions_kept must be a positive int, got %r" % (n,))
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
def retention_pinned():
|
||||||
|
"""D50 = A: the versions kept besides the newest N — the hub's MinAgent floor and the day-0 agent. Copies of hub
|
||||||
|
state the vouching session writes into the policy file (CI holds no hub secret). Missing key = no pins."""
|
||||||
|
with open(RETENTION_FILE, encoding="utf-8") as fh:
|
||||||
|
pins = json.load(fh).get("pinned") or {}
|
||||||
|
if not isinstance(pins, dict):
|
||||||
|
raise ValueError("pinned must be an object, got %r" % (pins,))
|
||||||
|
out = {}
|
||||||
|
for name, v in pins.items():
|
||||||
|
if not isinstance(v, str) or not re.match(r"^\d+\.\d+\.\d+$", v):
|
||||||
|
raise ValueError("pinned %s must be X.Y.Z, got %r" % (name, v))
|
||||||
|
out[name] = v
|
||||||
|
return out
|
||||||
|
|
||||||
tried = []
|
tried = []
|
||||||
|
|
||||||
|
|
||||||
@@ -211,6 +227,7 @@ def main():
|
|||||||
all_versions = versions
|
all_versions = versions
|
||||||
try:
|
try:
|
||||||
keep = retention_kept()
|
keep = retention_kept()
|
||||||
|
pins = retention_pinned()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
inconclusive("cannot read the retention policy (%s): %s" % (RETENTION_FILE, e))
|
inconclusive("cannot read the retention policy (%s): %s" % (RETENTION_FILE, e))
|
||||||
|
|
||||||
@@ -218,10 +235,15 @@ def main():
|
|||||||
# lexically: "0.9.0" > "0.10.0" as strings, and that would silently drop the wrong end.
|
# lexically: "0.9.0" > "0.10.0" as strings, and that would silently drop the wrong end.
|
||||||
def _key(v):
|
def _key(v):
|
||||||
return tuple(int(x) for x in v.split("."))
|
return tuple(int(x) for x in v.split("."))
|
||||||
versions = sorted(all_versions, key=_key)[-keep:]
|
window = sorted(all_versions, key=_key)[-keep:]
|
||||||
|
# D50 = A: the pinned versions are asserted wherever they sit. A pin with no tag cannot be installed at all —
|
||||||
|
# a failure, never a skip.
|
||||||
|
unpinned_tags = sorted(("%s=%s" % (k, v)) for k, v in pins.items() if v not in all_versions)
|
||||||
|
versions = sorted(set(window) | set(v for v in pins.values() if v in all_versions), key=_key)
|
||||||
dropped = [v for v in all_versions if v not in versions]
|
dropped = [v for v in all_versions if v not in versions]
|
||||||
|
|
||||||
print(" %d released version(s); retention policy keeps the newest %d" % (len(all_versions), keep))
|
print(" %d released version(s); retention policy keeps the newest %d plus the pinned %s" % (
|
||||||
|
len(all_versions), keep, ", ".join("%s=%s" % kv for kv in sorted(pins.items())) or "(none)"))
|
||||||
print(" verifying: %s" % ", ".join(versions))
|
print(" verifying: %s" % ", ".join(versions))
|
||||||
if dropped:
|
if dropped:
|
||||||
# NEVER silent. A bounded check that does not say what it stopped covering is how a
|
# NEVER silent. A bounded check that does not say what it stopped covering is how a
|
||||||
@@ -232,6 +254,9 @@ def main():
|
|||||||
print(" their configs resolve; what is no longer asserted is the BINARY's presence.")
|
print(" their configs resolve; what is no longer asserted is the BINARY's presence.")
|
||||||
|
|
||||||
bad = []
|
bad = []
|
||||||
|
for pin in unpinned_tags:
|
||||||
|
bad.append((pin, ["the pinned version has NO git tag — nothing can install it (D50 = A)"]))
|
||||||
|
print(" FAIL pinned %s: no git tag v%s" % (pin, pin.split("=")[1]))
|
||||||
for v in versions:
|
for v in versions:
|
||||||
pkg_url = "%s/api/packages/%s/generic/%s/%s/%s" % (GITEA_BASE, OWNER, PKG, v, PKG)
|
pkg_url = "%s/api/packages/%s/generic/%s/%s/%s" % (GITEA_BASE, OWNER, PKG, v, PKG)
|
||||||
raw_url = "%s/%s/%s/raw/tag/v%s/%s" % (GITEA_BASE, OWNER, REPO, v, PROBE_CONFIG)
|
raw_url = "%s/%s/%s/raw/tag/v%s/%s" % (GITEA_BASE, OWNER, REPO, v, PROBE_CONFIG)
|
||||||
|
|||||||
@@ -1,41 +1,33 @@
|
|||||||
{
|
{
|
||||||
"_comment": [
|
"_comment": [
|
||||||
"THE retention number for published agent artifacts. One file, read by everything that",
|
"THE retention rule for published agent artifacts. One file, read by everything that depends on it, because a check",
|
||||||
"depends on it, because a check and the policy it enforces must read the same number from the",
|
"and the policy it enforces must read the same rule from the same place or they drift — and the drift looks like a",
|
||||||
"same place or they drift — and the drift looks like a defect in something else.",
|
"defect in something else (R-287, 2026-08-08/09; again R-1290, 2026-10-25).",
|
||||||
"",
|
"",
|
||||||
"WHAT WENT WRONG WITHOUT IT (2026-08-08/09). The registry stopped serving felhom-agent",
|
"D50 = A (operator ruling 2026-10-25, felhom.eu `09` §3 decision 262): the registry keeps the NEWEST 5 released agent",
|
||||||
"0.120.0 and older, while scripts/check-published-versions.py demanded that EVERY git tag",
|
"versions, PLUS the hub's MinAgent floor, PLUS the version the hub's day-0 artifact manifest names. The operator prunes",
|
||||||
"still be downloadable. Both rules are individually sensible; together they are impossible.",
|
"by hand to that rule (on 2026-10-25 he removed 0.159.0–0.160.0 and kept 0.158.1, the floor). CI asserts exactly these",
|
||||||
"CI went red at a commit whose own run had been green the day before, on a true finding that",
|
"and never more. Before D50 the number was 10 (the R-267 newest-10 prune, R-287/R-291).",
|
||||||
"no one could act on. The red will return at the next publish unless the two read one number.",
|
|
||||||
"",
|
"",
|
||||||
"HOW THE NUMBER WAS ARRIVED AT. generic_versions_kept is 10 because that is what the registry",
|
"THE PINS ARE COPIES OF HUB STATE, because CI cannot read the hub without a secret it must not hold. The session that",
|
||||||
"keeps: the deleter was ESTABLISHED on 2026-08-10 (R-287) — the newest-10 prune of generic packages",
|
"records an agent vouch (day0_agent) or declares a new MinAgent with a floor (hub_min_agent) updates `pinned` in the",
|
||||||
"run under R-267 (felhom-agent and felhom-golden generic held exactly 10 afterwards). Until then this",
|
"same commit (felhom-build-deploy skill, agent release). A pin that names a version with no tag is a FAILURE, never a",
|
||||||
"file called the 10 an observed state with no located ruling; that is superseded (corrected",
|
"skip — a pin nobody can install is the defect the pin exists to catch.",
|
||||||
"2026-10-05, R-291). Container packages are not pruned by that rule.",
|
|
||||||
"",
|
"",
|
||||||
"SO THIS FILE IS A FLOOR, NOT A LICENCE. It says: CI may assume nothing older than the newest",
|
"SO THIS FILE IS A FLOOR, NOT A LICENCE: it says what CI may assume is still downloadable. It does not authorise",
|
||||||
"N generic versions is still downloadable. It does NOT authorise deleting anything, and the",
|
"deleting anything else.",
|
||||||
"operator should confirm or replace the number — at which point this file changes and both",
|
|
||||||
"readers follow it in the same commit.",
|
|
||||||
"",
|
"",
|
||||||
"THE DEEPER BOUND, recorded so a future session does not have to re-derive it: the principled",
|
"NEVER retire a git TAG to satisfy this. felhom-host-install.sh fetches an agent's config files from",
|
||||||
"limit is the hub's vouched min_agent floor (0.127.0 on 2026-08-09). Nothing can install an",
|
"raw/tag/v<version>/configs/, so deleting a tag retires the ability to install that version at all."
|
||||||
"agent below it — the hub refuses to vouch one and boxes update to the floor — so a released",
|
|
||||||
"version below the floor being un-downloadable costs nothing real. Bounding on the floor would",
|
|
||||||
"be better than bounding on a count, and it needs the gate to read the hub, which is network",
|
|
||||||
"the gate does not have today. Filed as the follow-up in R-287.",
|
|
||||||
"",
|
|
||||||
"NEVER retire a git TAG to satisfy this. felhom-host-install.sh fetches an agent's config",
|
|
||||||
"files from raw/tag/v<version>/configs/, so deleting a tag retires the ability to install that",
|
|
||||||
"version at all — a strictly worse act than an un-downloadable binary."
|
|
||||||
],
|
],
|
||||||
"generic_versions_kept": 10,
|
"generic_versions_kept": 5,
|
||||||
"readers": [
|
"readers": [
|
||||||
"scripts/check-published-versions.py — bounds its assertion to the newest N versions"
|
"scripts/check-published-versions.py — asserts the newest N versions plus every pinned version"
|
||||||
],
|
],
|
||||||
"recorded": "2026-08-09",
|
"recorded": "2026-10-25",
|
||||||
"recorded_by": "CC 2026-08-09; the number's source (the R-267 newest-10 prune, established 2026-08-10 by R-287) recorded 2026-10-05 (R-291)"
|
"recorded_by": "CC 2026-10-25, D50 = A (operator); earlier history: R-287 (2026-08-09), R-291 (2026-10-05)",
|
||||||
|
"pinned": {
|
||||||
|
"hub_min_agent": "0.158.1",
|
||||||
|
"day0_agent": "0.165.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -179,13 +179,24 @@ def published_cases(base):
|
|||||||
TAGS = ["v0.150.%d" % i for i in range(3)] # inside any retention window >= 3
|
TAGS = ["v0.150.%d" % i for i in range(3)] # inside any retention window >= 3
|
||||||
VERS = [t[1:] for t in TAGS]
|
VERS = [t[1:] for t in TAGS]
|
||||||
|
|
||||||
|
# D50 = A: the gate also asserts the PINNED versions. The decoys run against their own policy file (the real pins
|
||||||
|
# name versions the fake Gitea does not hold); PINS is what each case writes there.
|
||||||
|
pol_dir = tempfile.mkdtemp(prefix="retention-decoy-")
|
||||||
|
pol = os.path.join(pol_dir, "retention-policy.json")
|
||||||
|
PINS = {}
|
||||||
|
|
||||||
def case(name, setup, expect_rc, must=()):
|
def case(name, setup, expect_rc, must=()):
|
||||||
FAKE.reset()
|
FAKE.reset()
|
||||||
FAKE.tags = list(TAGS)
|
FAKE.tags = list(TAGS)
|
||||||
FAKE.packages = set(VERS)
|
FAKE.packages = set(VERS)
|
||||||
FAKE.raw = set(VERS)
|
FAKE.raw = set(VERS)
|
||||||
|
PINS.clear()
|
||||||
setup()
|
setup()
|
||||||
rc, out = run([sys.executable, gate], ROOT, child_env(base))
|
with io.open(pol, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump({"generic_versions_kept": keep, "pinned": dict(PINS)}, fh)
|
||||||
|
env = child_env(base)
|
||||||
|
env["FELHOM_RETENTION_POLICY"] = pol
|
||||||
|
rc, out = run([sys.executable, gate], ROOT, env)
|
||||||
if not FAKE.hits:
|
if not FAKE.hits:
|
||||||
fails.append("published/%s: the gate never asked the fake Gitea - the seam is not wired" % name)
|
fails.append("published/%s: the gate never asked the fake Gitea - the seam is not wired" % name)
|
||||||
report("published: " + name, rc, out, expect_rc, must)
|
report("published: " + name, rc, out, expect_rc, must)
|
||||||
@@ -220,6 +231,25 @@ def published_cases(base):
|
|||||||
case("BY DESIGN: a version older than the retention window is not asserted", retired, 0,
|
case("BY DESIGN: a version older than the retention window is not asserted", retired, 0,
|
||||||
("NOT ASSERTED", "0.9.0"))
|
("NOT ASSERTED", "0.9.0"))
|
||||||
|
|
||||||
|
def pinned_old_ok():
|
||||||
|
FAKE.tags = ["v0.8.0"] + ["v0.10.%d" % i for i in range(keep)]
|
||||||
|
FAKE.packages = set(t[1:] for t in FAKE.tags)
|
||||||
|
FAKE.raw = set(t[1:] for t in FAKE.tags)
|
||||||
|
PINS["hub_min_agent"] = "0.8.0"
|
||||||
|
case("GENUINE (D50): a pinned version older than the window is asserted and downloads", pinned_old_ok, 0,
|
||||||
|
("verifying: 0.8.0", "ALL RELEASED VERSIONS INSTALLABLE"))
|
||||||
|
|
||||||
|
def pinned_old_gone():
|
||||||
|
pinned_old_ok()
|
||||||
|
FAKE.packages.discard("0.8.0")
|
||||||
|
case("FACT (D50): a pinned version older than the window whose package 404s", pinned_old_gone, 1,
|
||||||
|
("FAIL v0.8.0", "binary NOT downloadable"))
|
||||||
|
|
||||||
|
def pinned_no_tag():
|
||||||
|
PINS["day0_agent"] = "0.150.9"
|
||||||
|
case("FACT (D50): a pin that names a version with no tag", pinned_no_tag, 1,
|
||||||
|
("FAIL pinned day0_agent=0.150.9", "no git tag"))
|
||||||
|
|
||||||
def five_hundred():
|
def five_hundred():
|
||||||
FAKE.tags_status = 500
|
FAKE.tags_status = 500
|
||||||
case("INCONCLUSIVE: the tags api answers 500", five_hundred, 2, ("INCONCLUSIVE",))
|
case("INCONCLUSIVE: the tags api answers 500", five_hundred, 2, ("INCONCLUSIVE",))
|
||||||
|
|||||||
Reference in new issue
Block a user