Apply ground-game's rulings: mastery in points, four boards, and a
Some checks failed
ci / check (push) Failing after 4s

vendor tool that covers what the gate checks

They ruled on all seven items the same day. Two were actionable here.

F28 RULED: points. Modes.csv MODE_COOP clarified upstream to say
"penalties apply to points, not card count"; mastery is now
total - blame - denied. A recorded scenario went red on it --
gr-e02-shared-ground pinned 0 (2 claimed CARDS - 1 - 1) and now expects
2 (4 POINTS - 1 - 1). The number moved because the rule was decided, not
because the engine drifted, and the scenario records both rulings; its
schema has no field for a second one, so both live in ruled_note with
`ruled` carrying the LATEST date.

F29 RULED not-intended and APPLIED upstream: SCN_02's suits re-tuned the
same day. The characterisation test is how we found out -- it pinned the
duplication, went red on the re-tune, and that red WAS the notification.
It now asserts every pair distinct, the stronger statement the
duplication had made unavailable. SCN_02 re-measures at 73 at 2p, not
67: its own board now.

F26/F30 ruled and recorded. F30's ruling incidentally confirms our
reading -- they name priority-2's suit as the first lever, which is the
difference we identified without having measured causation.

vendor-editions grew twice, both times because it covered less than the
gate it exists to satisfy:

  - It refused to touch ground-darvo-r0/ on the reasoning that the
    baseline is "a separate record". That was wrong within the hour:
    ground-game clarified Modes.csv and `make vendor` reported a clean
    sync while edition-check went red. A sync tool that covers less than
    its check reports success into a red gate.
  - Its two-block rewrite DETECTED which fence held which set and
    preserved the arrangement -- faithfully preserving a swap an earlier
    write had introduced, leaving each fence under a heading describing
    the other. edition-check reads every sha256 line flat and passed
    throughout: a document can be self-consistently wrong and green.
    Order is now asserted, with a control that goes red on a swap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-09 00:15:14 +02:00
parent 98f600b1d3
commit 704b99975b
15 changed files with 373 additions and 154 deletions

View file

@ -28,9 +28,13 @@ precisely the case a maintained list loses.
files upstream does not have. `edition-check` reports both, and a sync
tool that silently resolved them would remove the only signal that the
mirror and upstream disagree about what exists.
- It does not touch `editions/ground-darvo-r0/` except to write the
digest block. That directory is the baseline package (ADR-0011) and its
own provenance is a separate record.
- It does not delete or add; see above. It **does** refresh the baseline
package's CSVs, which the first version refused to do on the reasoning
that `ground-darvo-r0/` is "a separate record". That was wrong within
the hour: ground-game clarified `Modes.csv` and `make vendor` reported
a clean sync while `make edition-check` went red on it. **A sync tool
that covers less than the check it exists to satisfy is a tool that
reports success into a red gate.**
"""
import hashlib
@ -51,6 +55,20 @@ def digest(path):
return hashlib.sha256(open(path, "rb").read()).hexdigest()
def edition_csvs():
"""The baseline package's own CSVs, as bare filenames.
Mirrors `edition-check`'s `vendored_files()`, including its limit:
only `.csv`. A non-CSV added to that directory is invisible to both,
which is recorded as a known hole rather than fixed here.
"""
return sorted(
(n, os.path.join(EDITION_DIR, n))
for n in os.listdir(EDITION_DIR)
if n.endswith(".csv")
)
def siblings():
"""Every file beside the edition directory, as `../`-relative paths.
@ -77,6 +95,16 @@ def refresh():
return 1
copied, missing, extra = [], [], []
# The baseline package first: it is the one `edition-check` checks
# by a different code path, and the one this tool used to skip.
for name, full in edition_csvs():
up = os.path.join(UPSTREAM, "ground-darvo-r0", name)
if not os.path.exists(up):
extra.append(f"ground-darvo-r0/{name}")
continue
if digest(up) != digest(full):
shutil.copy2(up, full)
copied.append(f"ground-darvo-r0/{name}")
for rel, full in siblings():
up = os.path.normpath(os.path.join(UPSTREAM, rel.replace("../", "", 1)))
if not os.path.exists(up):
@ -102,16 +130,57 @@ def refresh():
if not os.path.exists(here):
missing.append(rel)
rows = [(digest(full), rel) for rel, full in siblings()]
rows.sort(key=lambda r: r[1])
block = "\n".join(f"sha256 {h} {r}" for h, r in rows)
# Two digest fences, and **which holds which is fixed by the prose
# above them, not sniffed from their contents.**
#
# The first version detected the current arrangement and preserved
# it — which faithfully preserved a swap an earlier buggy write had
# introduced, leaving each fence under a heading describing the other
# one. `edition-check` reads every `sha256` line and so passed
# throughout: a document can be self-consistently wrong and green.
#
# Order is now asserted. `SIBLING_FENCE` is the fence whose prose is
# about the packages beside the edition; it is the second in the file
# and this refuses to write if that stops being true.
text = open(PROVENANCE, encoding="utf-8").read()
a = text.index("```\nsha256")
b = text.index("```", a + 3) + 3
open(PROVENANCE, "w", encoding="utf-8").write(
text[:a] + "```\n" + block + "\n```" + text[b:]
fences = []
i = 0
while True:
j = text.find("```", i)
if j < 0:
break
fences.append(j)
i = j + 3
if len(fences) < 4:
raise SystemExit(
f"{PROVENANCE}: expected two digest fences, found {len(fences) // 2}"
)
edition_fence, sibling_fence = fences[0], fences[2]
# The heading immediately above the sibling fence must be about
# siblings, or the file has been reorganised and this tool would be
# writing digests under the wrong claim.
lead = text[max(0, sibling_fence - 400) : sibling_fence]
if "Digests" not in lead and "beside" not in lead and "outside" not in lead:
raise SystemExit(
f"{PROVENANCE}: the second fence is not the sibling digest block; "
"the file was reorganised and this tool will not guess"
)
rows_edition = sorted(
((digest(full), name) for name, full in edition_csvs()), key=lambda r: r[1]
)
rows_sibling = sorted(
((digest(full), rel) for rel, full in siblings()), key=lambda r: r[1]
)
rows = rows_edition + rows_sibling
out = text
# Last fence first, so earlier offsets stay valid.
for start, rowset in ((sibling_fence, rows_sibling), (edition_fence, rows_edition)):
end = out.index("```", start + 3) + 3
body = "\n".join(f"sha256 {h} {r}" for h, r in rowset)
out = out[:start] + "```\n" + body + "\n```" + out[end:]
open(PROVENANCE, "w", encoding="utf-8").write(out)
print("vendor-editions — the mirror, and what it now records")
print(f" refreshed from upstream {len(copied)}")
@ -152,6 +221,16 @@ def recorded_paths():
return sorted(out)
def recorded_all():
"""Every path the digest blocks name, siblings and edition alike."""
text = open(PROVENANCE, encoding="utf-8").read()
return sorted(
line.split()[2]
for line in text.splitlines()
if len(line.split()) == 3 and line.split()[0] == "sha256"
)
def self_test():
"""A sync tool that cannot detect an unrecorded file is decoration.
@ -192,6 +271,31 @@ def self_test():
disk = set(found)
check("every vendored file has a recorded digest", not (disk - rec),
f"unrecorded: {sorted(disk - rec)}" if disk - rec else "")
check("the baseline package's CSVs are recorded too",
set(n for n, _ in edition_csvs()) <= set(recorded_all()),
"the first version skipped ground-darvo-r0/ and reported a clean "
"sync while edition-check went red on Modes.csv")
# **Each fence under its own heading** — the control that was
# missing. An earlier write swapped the two blocks and everything
# stayed green, because `edition-check` reads every `sha256` line and
# does not care which fence it came from. A document can be
# self-consistently wrong and pass every check that reads it flat.
text = open(PROVENANCE, encoding="utf-8").read()
fences = []
i = 0
while True:
j = text.find("```", i)
if j < 0:
break
fences.append(j)
i = j + 3
first_body = text[fences[0] : text.index("```", fences[0] + 3)]
second_body = text[fences[2] : text.index("```", fences[2] + 3)]
check("the first digest fence holds the edition's own CSVs",
" ../" not in first_body and "sha256" in first_body)
check("the second digest fence holds the siblings",
all(" ../" in ln for ln in second_body.splitlines() if "sha256" in ln))
check("no digest is recorded for a file that is not here", not (rec - disk),
f"recorded but absent: {sorted(rec - disk)}" if rec - disk else "")