Merge remote-tracking branch 'origin/human/r1-register-reclassification' into auto/frame-blend-draw-path
This commit is contained in:
@@ -133,6 +133,42 @@ Refutation is cheapest where the other agent is most confident. Prefer:
|
||||
centroid estimator that is 19.8° out on a known rotation cannot measure an
|
||||
unknown one. A filter that fails its own known-positive is dead, not tuneable.
|
||||
|
||||
⚠️ **And a control verifies CAPABILITY, not CONFIGURATION.** Both agents ran
|
||||
controls and both were still wrong: one tested whether the method *can* detect a
|
||||
blend difference, not whether *that run* had `blend_mode` set — it was left at
|
||||
the engine default. The other tested whether NDC→pixel conversion is right, not
|
||||
whether the dump captured all six quads; it captured two, with a well-formed line
|
||||
and no ellipsis. Assert the run's configuration, not just the method's power.
|
||||
|
||||
### R1 — a refutation is only as good as its instrument
|
||||
|
||||
**A refutation whose instrument is one of our renderers is not a refutation.** It
|
||||
is *"our renderer disagrees"* — 🟡, not ❌.
|
||||
|
||||
Agreed by both agents 2026-08-31, applied to
|
||||
[`../re/REFUTED.md`](../re/REFUTED.md) by the human on 2026-09-01. The register
|
||||
is not yours to reclassify: it is the file you both read to decide what *not* to
|
||||
try, and two agents agreeing is not the authority for changing it. Propose;
|
||||
do not enact.
|
||||
|
||||
What this asks of you, in practice:
|
||||
|
||||
* **Every claim you retire names its instrument** — `⟨capture⟩`, `⟨disc⟩`,
|
||||
`⟨image⟩`, `⟨render-vs-capture⟩`, `⟨harness⟩`… The register's reading guide
|
||||
holds the vocabulary and says which tags are ours.
|
||||
* **When you improve a renderer, a reader or the harness, run
|
||||
`tools/stale-instrument <that instrument>`.** It lists what that instrument
|
||||
killed. Those claims re-open. This is the mechanism the rule exists for: the
|
||||
motivating failure was not that anyone was careless, it is that **nothing
|
||||
re-opened a claim when the instrument that killed it improved**, and a real
|
||||
disc field sat dead for weeks as a result.
|
||||
* **A 🟡 carries what would settle it.** A re-opened claim with no next
|
||||
experiment is an unanswered question wearing a colour.
|
||||
|
||||
The exception, from R5: our tool is the right instrument for a question **about
|
||||
our tool**. *"Can `screen render` draw the developer splash?"* is ours to answer.
|
||||
*"What does the game draw?"* is not.
|
||||
|
||||
**Disagreements escalate to the human with both positions.** They are not
|
||||
resolved by seniority, by who wrote it down first, or by whoever is more certain.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
255
tools/stale-instrument
Executable file
255
tools/stale-instrument
Executable file
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
"""What did this instrument kill? -- the re-open query for `docs/re/REFUTED.md`.
|
||||
|
||||
tools/stale-instrument everything, grouped
|
||||
tools/stale-instrument render-vs-capture what one instrument killed
|
||||
tools/stale-instrument --ours only instruments that are ours
|
||||
tools/stale-instrument --check self-test; exits non-zero if broken
|
||||
|
||||
Why this exists
|
||||
---------------
|
||||
Rule R1. A refutation whose instrument is one of our renderers is not a
|
||||
refutation -- it is *"our renderer disagrees"*. The register carries that as a
|
||||
🟡, but a colour alone does not re-open anything: the failure it is there to
|
||||
prevent was a real disc field sitting dead for weeks because **nothing re-opens a
|
||||
claim when the instrument that killed it improves.**
|
||||
|
||||
So this is the query you run *after* you fix a renderer, a reader or the harness.
|
||||
It answers "what did I just invalidate?" in one command, instead of relying on
|
||||
someone remembering which of 222 entries rested on the thing they changed.
|
||||
|
||||
The register is the only source. There is no second list to drift out of sync
|
||||
with it -- the tags live at the end of each entry, in the file people actually
|
||||
read, which is this corpus's own repeatedly-learned lesson about where a fact has
|
||||
to live to exist.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
|
||||
REGISTER = os.environ.get("SYLPH_REGISTER", "docs/re/REFUTED.md")
|
||||
|
||||
# Ours, in the sense R1 means: an instrument whose defects we cannot rule out,
|
||||
# because we built it. `our-tool` is ours too, but it is not disqualifying --
|
||||
# see the note below and in the register's reading guide.
|
||||
OURS = {"render-vs-capture", "screen-render", "our-reader", "harness", "our-tool"}
|
||||
|
||||
# Not a verdict. It means nobody recorded how the claim died.
|
||||
UNRECORDED = "unrecorded"
|
||||
|
||||
TAG = re.compile(r"⟨([a-z][a-z-]*)⟩\s*$")
|
||||
|
||||
|
||||
def entries(text: str):
|
||||
"""Every bullet, with its instrument tag and whether it is already 🟡.
|
||||
|
||||
An entry is a `* ` bullet plus its continuation lines, ending at a blank
|
||||
line or a heading -- the same span rule the register's own enumeration uses.
|
||||
"""
|
||||
out, cur, start = [], None, 0
|
||||
for i, line in enumerate(text.split("\n"), 1):
|
||||
if line.startswith("* "):
|
||||
if cur is not None:
|
||||
out.append((start, cur))
|
||||
cur, start = [line], i
|
||||
elif line.startswith("#"):
|
||||
if cur is not None:
|
||||
out.append((start, cur))
|
||||
cur = None
|
||||
elif cur is not None:
|
||||
if not line.strip():
|
||||
out.append((start, cur))
|
||||
cur = None
|
||||
else:
|
||||
cur.append(line)
|
||||
if cur is not None:
|
||||
out.append((start, cur))
|
||||
|
||||
for ln, body in out:
|
||||
joined = " ".join(x.strip() for x in body)
|
||||
m = TAG.search(body[-1].rstrip())
|
||||
# 🔴 THE VERDICT IS THE LEADING GLYPH, NOT ANY 🟡 IN THE ENTRY. Matching
|
||||
# anywhere counted four entries as re-opened that merely carry an inline
|
||||
# 🟡 caveat mid-sentence ("🟡 a third recorded run implies NEW GAME").
|
||||
# An entry whose verdict is ❌ and whose *reach* is qualified is not an
|
||||
# open claim, and reporting it as one inflates exactly the number this
|
||||
# tool exists to make trustworthy.
|
||||
yield {
|
||||
"line": ln,
|
||||
"instrument": m.group(1) if m else None,
|
||||
"open": body[0].startswith("* 🟡"),
|
||||
"settled": body[0].startswith("* ✅"),
|
||||
"text": re.sub(r"\s+", " ", joined),
|
||||
}
|
||||
|
||||
|
||||
def summary(text: str) -> str:
|
||||
"""The claim, short enough to scan a list of them."""
|
||||
t = re.sub(r"^\*\s*(🟡|❌|✅)?\s*", "", text)
|
||||
t = re.sub(r"[*`~]", "", t)
|
||||
return (t[:118] + "…") if len(t) > 118 else t
|
||||
|
||||
|
||||
def die(msg: str):
|
||||
"""A HARNESS fault, exit 2 -- distinct from 'no such instrument', exit 1.
|
||||
|
||||
🔴 `sys.exit("text")` exits **1**, not 2. Every one of these sites used it,
|
||||
so three faults that this file's own docstring calls exit-2 were reporting
|
||||
the same code as an ordinary miss -- a real failure wearing a wrong
|
||||
diagnosis, the exact shape the register catalogues elsewhere. Its own
|
||||
`--check` caught it on the first run, which is the argument for having one.
|
||||
"""
|
||||
print(msg, file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
|
||||
def load():
|
||||
if not os.path.exists(REGISTER):
|
||||
die(
|
||||
f"stale-instrument: {REGISTER} is not here.\n"
|
||||
" Run from the repository root, or set SYLPH_REGISTER.\n"
|
||||
" Exit 2: the harness is broken, not the corpus."
|
||||
)
|
||||
rows = list(entries(open(REGISTER, encoding="utf-8").read()))
|
||||
# 🔴 LIVENESS. A reader that parses nothing reports "no claims rest on that
|
||||
# instrument" -- which reads exactly like the good news you were hoping for.
|
||||
# A renamed file, a changed bullet shape or a lost tag all produce it. This
|
||||
# corpus has already produced one false zero from a reader invented minutes
|
||||
# earlier, so the empty case is a harness fault with its own exit code.
|
||||
if not rows:
|
||||
die("stale-instrument: parsed 0 entries from "
|
||||
f"{REGISTER} -- exit 2, the reader is broken.")
|
||||
if not any(r["instrument"] for r in rows):
|
||||
die(f"stale-instrument: {len(rows)} entries, none tagged ⟨…⟩ -- "
|
||||
"exit 2, the reader is broken or the register is untagged.")
|
||||
return rows
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if "--check" in argv:
|
||||
return selftest()
|
||||
|
||||
rows = load()
|
||||
only_ours = "--ours" in argv
|
||||
wanted = [a for a in argv if not a.startswith("-")]
|
||||
|
||||
by = OrderedDict()
|
||||
for r in rows:
|
||||
by.setdefault(r["instrument"] or UNRECORDED, []).append(r)
|
||||
|
||||
if wanted:
|
||||
name = wanted[0]
|
||||
if name not in by:
|
||||
print(f"no entry names ⟨{name}⟩. Known instruments:")
|
||||
for k in sorted(by):
|
||||
print(f" {k}")
|
||||
return 1
|
||||
group = by[name]
|
||||
mine = " -- OURS, so these are re-openable" if name in OURS else ""
|
||||
print(f"⟨{name}⟩ killed {len(group)} claim(s){mine}\n")
|
||||
for r in group:
|
||||
mark = "🟡" if r["open"] else ("✅" if r["settled"] else "❌")
|
||||
print(f" {mark} L{r['line']:<4} {summary(r['text'])}")
|
||||
if name in OURS:
|
||||
print(f"\n If you have improved {name}, every ❌ above is a claim that"
|
||||
"\n died to an instrument that no longer exists in that form.")
|
||||
return 0
|
||||
|
||||
print(f"{len(rows)} entries in {REGISTER}\n")
|
||||
order = sorted(by, key=lambda k: (k not in OURS, k == UNRECORDED, -len(by[k])))
|
||||
for k in order:
|
||||
g = by[k]
|
||||
tag = " ← OURS" if k in OURS else (" ← nobody wrote it down"
|
||||
if k == UNRECORDED else "")
|
||||
opened = sum(1 for r in g if r["open"])
|
||||
note = f", {opened} already 🟡" if opened else ""
|
||||
print(f" {k:20s} {len(g):3d} claim(s){note}{tag}")
|
||||
|
||||
ours = sum(len(by[k]) for k in by if k in OURS)
|
||||
unrec = len(by.get(UNRECORDED, []))
|
||||
print(f"\n {ours} claim(s) died to an instrument WE BUILT.")
|
||||
print(f" {unrec} claim(s) record no instrument at all -- "
|
||||
"neither safe nor suspect,\n just unauditable. That is the backfill queue.")
|
||||
print("\n tools/stale-instrument <instrument> to list one")
|
||||
return 0
|
||||
|
||||
|
||||
def selftest() -> int:
|
||||
"""Cases that must hold, executed -- not reasoned about.
|
||||
|
||||
A control that does not run is not a control; this file's neighbours in
|
||||
`tools/` say so about other people's tools, so it is asserted here.
|
||||
"""
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
me = os.path.abspath(__file__)
|
||||
ok = 0
|
||||
|
||||
def run(reg, args=()):
|
||||
env = dict(os.environ, SYLPH_REGISTER=reg)
|
||||
p = subprocess.run([sys.executable, me, *args], capture_output=True,
|
||||
text=True, env=env)
|
||||
return p.returncode, p.stdout + p.stderr
|
||||
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
good = os.path.join(d, "good.md")
|
||||
open(good, "w", encoding="utf-8").write(
|
||||
'* "a" → refuted. ⟨disc⟩\n\n* 🟡 "b" — our renderer disagrees. ⟨render-vs-capture⟩\n'
|
||||
)
|
||||
cases = [
|
||||
("a tagged register parses", (good, ()), 0, "2 entries"),
|
||||
("one instrument lists", (good, ("disc",)), 0, '"a"'),
|
||||
("an OURS instrument says so", (good, ("render-vs-capture",)),
|
||||
0, "re-openable"),
|
||||
("an unknown instrument fails", (good, ("nope",)), 1, "no entry names"),
|
||||
]
|
||||
for name, (reg, args), want_rc, want_txt in cases:
|
||||
rc, out = run(reg, args)
|
||||
good_rc = rc == want_rc
|
||||
good_tx = want_txt in out
|
||||
if good_rc and good_tx:
|
||||
print(f" {name:34s} exit {rc} ✅")
|
||||
else:
|
||||
print(f" {name:34s} exit {rc} (wanted {want_rc}), "
|
||||
f"text {'found' if good_tx else 'MISSING'} 🔴")
|
||||
ok = 1
|
||||
|
||||
# The two harness faults, which must be distinguishable from a clean run.
|
||||
empty = os.path.join(d, "empty.md")
|
||||
open(empty, "w").write("# nothing here\n")
|
||||
rc, out = run(empty)
|
||||
if rc == 2 and "reader is broken" in out:
|
||||
print(f" {'an empty register is a fault':34s} exit 2 ✅")
|
||||
else:
|
||||
print(f" {'an empty register is a fault':34s} exit {rc}, wanted 2 🔴")
|
||||
ok = 1
|
||||
|
||||
untagged = os.path.join(d, "untagged.md")
|
||||
open(untagged, "w", encoding="utf-8").write('* "a" → refuted.\n')
|
||||
rc, out = run(untagged)
|
||||
if rc == 2 and "none tagged" in out:
|
||||
print(f" {'an untagged register is a fault':34s} exit 2 ✅")
|
||||
else:
|
||||
print(f" {'an untagged register is a fault':34s} exit {rc}, wanted 2 🔴")
|
||||
ok = 1
|
||||
|
||||
rc, _ = run(os.path.join(d, "does-not-exist.md"))
|
||||
if rc == 2:
|
||||
print(f" {'a missing register is a fault':34s} exit 2 ✅")
|
||||
else:
|
||||
print(f" {'a missing register is a fault':34s} exit {rc}, wanted 2 🔴")
|
||||
ok = 1
|
||||
|
||||
print()
|
||||
print("the query fails when it must, and says so distinctly" if not ok
|
||||
else "🔴 the query's own control is broken")
|
||||
return ok
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user