steps/ per ladder step: gate rule 4, writer, backfill (8); R-653, R-656; decoy suite reads live pins (R-663)
gates / gates (push) Successful in 2s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-24 07:57:51 +02:00
parent 585a7cba22
commit 5ed599cd5f
16 changed files with 1150 additions and 22 deletions
+15
View File
@@ -19,6 +19,9 @@ WHAT IT CHECKS, per template that carries `update_ladder:` (format and field rul
3. the NEWEST entry's `to` is EXACTLY the compose's current image per service. This is the fact
that makes the rule hold without history: a compose moved without a new entry no longer
matches its ladder's head, whoever pushed it and however.
4. every entry but the newest carries its OWN definition at `steps/<step_key(to)>.yml`, whose
images per service are EXACTLY that entry's `to` (`09` §6.4 part 5: the box pins that file, one
step per press). The name alone is not the fact: the file's own `image:` lines are read.
A template WITHOUT a ladder passes here — it has never been moved since the gate existed, and its
first move is refused by the twin unless that move brings the first entry.
@@ -50,6 +53,18 @@ def check_app(app_dir):
for i in range(1, len(entries)):
if entries[i].get("from") != entries[i - 1].get("to"):
problems.append("entry %d's `from` is not entry %d's `to` — the ladder has a gap" % (i + 1, i))
for i, e in enumerate(entries[:-1]):
to = e.get("to")
if not isinstance(to, dict) or not to:
continue # already convicted by check_entry
sp = os.path.join(app_dir, ladder.step_file(to))
if not os.path.isfile(sp):
problems.append("entry %d of %d has no definition at %s — the box climbs one step at a time "
"and needs this step's own compose file" % (i + 1, len(entries), ladder.step_file(to)))
continue
got = ladder.images_in(open(sp, encoding="utf-8").read())
if got != to:
problems.append("%s names %s, but entry %d's `to` is %s" % (ladder.step_file(to), got, i + 1, to))
if entries:
current = ladder.images_in(open(comp, encoding="utf-8").read())
head = entries[-1].get("to")