volt: path_claim_check learns --rev WORKTREE (index inventory, disk bytes; cells 27->31); verify gains leg 2 path-claims (8 legs, cells 55->59)
This commit is contained in:
1 parent
df46ca7b5a
commit
9732094dfd
3 files changed
+182
-20
No files matched your search
+112
-6
@@ -96,6 +96,30 @@ THE BASELINE, AND WHY THE FIRST CENSUS IS ITS LEDGER
|
||||
check rather than a word that closes a red. A claim that something EXISTS is
|
||||
still MISSING until the file does.
|
||||
|
||||
REVISIONS -- AND WHY `WORKTREE` IS NOT ONE OF THEM
|
||||
`--rev` selects the tree the corpus is read from AND the tree existence is
|
||||
judged against, so the claim and its oracle come from the same revision. The
|
||||
default is `HEAD`.
|
||||
|
||||
`--rev WORKTREE` reads the files on disk and lists them from the INDEX, so it
|
||||
grades the tree you are about to commit. It is not a revision and has no tree
|
||||
object: `git rev-parse --verify WORKTREE^{commit}` fails, which is why this
|
||||
flag could not be spelled until now. The two modes were measured apart in the
|
||||
selftest before this paragraph existed, on one fixture document: a path claim
|
||||
that lives only in the staged tree is invisible at `HEAD` (rc 0, no MISSING
|
||||
row) and is a graded MISSING row under `WORKTREE` (rc 1 under
|
||||
--fail-on-missing). That window -- the tick between writing prose and
|
||||
committing it -- is exactly where the archive writes every claim it makes, so
|
||||
a grader that could only read HEAD could not grade a lane's own tick. The
|
||||
convention is `doc_command_check.py`'s and `tool_map.py`'s, and the reason is
|
||||
the same in all three: grade the tree you are about to commit, not the last
|
||||
one.
|
||||
|
||||
A path that exists on disk but is in neither the index nor HEAD is still
|
||||
MISSING under `WORKTREE`: the inventory is the index, so `git add` before the
|
||||
gate is the discipline, not a workaround. Untracked debris under a scratch
|
||||
tree therefore cannot make a claim read EXISTS.
|
||||
|
||||
EXIT CODES
|
||||
0 the census ran. Without --fail-on-missing a MISSING path is not a fault.
|
||||
1 --fail-on-missing and at least one MISSING path is not in the baseline, or
|
||||
@@ -108,13 +132,14 @@ EXIT CODES
|
||||
that file is graded with no ledger after one note on stderr.
|
||||
|
||||
USAGE
|
||||
python3 path_claim_check.py [--repo DIR] [--rev HEAD] [--corpus PATH...]
|
||||
python3 path_claim_check.py [--repo DIR] [--rev HEAD|WORKTREE] [--corpus PATH...]
|
||||
[--json OUT] [--only missing|exists|docrel|external|template|all]
|
||||
[--context] [--fail-on-missing] [--baseline PATH]
|
||||
--baseline defaults to tools/path_claim_baseline.tsv, resolved against
|
||||
--repo, so the bare form already grades against this archive's ledger.
|
||||
python3 path_claim_check.py --selftest # 27 cells
|
||||
python3 path_claim_check.py --selftest # 31 cells
|
||||
python3 path_claim_check.py --repo . --fail-on-missing
|
||||
python3 path_claim_check.py --repo . --rev WORKTREE --fail-on-missing
|
||||
python3 path_claim_check.py --repo . --only docrel --context
|
||||
"""
|
||||
import argparse
|
||||
@@ -138,6 +163,11 @@ DEFAULT_REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# direction of a defect.
|
||||
DEFAULT_BASELINE = "tools/path_claim_baseline.tsv"
|
||||
|
||||
# The pseudo-revision `doc_command_check.py` and `tool_map.py` already carry.
|
||||
# Spelled once, here, so a reader greps one name and finds every branch that
|
||||
# treats it as a state rather than as a commit.
|
||||
WORKTREE = "WORKTREE"
|
||||
|
||||
# The extensions this archive actually names. A closed list, for the reason
|
||||
# citation_resolve_check.py gives: an open `\\.[a-z]+` swallows clock times,
|
||||
# decimals and domain names.
|
||||
@@ -199,6 +229,11 @@ def precondition(repo, rev):
|
||||
p = git(repo, "rev-parse", "--is-inside-work-tree")
|
||||
if p.returncode != 0 or p.stdout.strip() != b"true":
|
||||
return "cannot read repo %s: not a git work tree" % repo
|
||||
if rev == WORKTREE:
|
||||
# WORKTREE is a state, not a revision: it has no commit to resolve to.
|
||||
# Everything below reads the index (inventory) and the disk (bytes), so
|
||||
# the work-tree test just made is the whole precondition.
|
||||
return None
|
||||
q = git(repo, "rev-parse", "--verify", rev + "^{commit}")
|
||||
if q.returncode != 0:
|
||||
why = q.stderr.decode("utf-8", "replace").strip().splitlines()
|
||||
@@ -325,13 +360,38 @@ def scan(repo, rev, corpus, store):
|
||||
|
||||
|
||||
class Store:
|
||||
"""The inventory and the bytes, at one revision or at the staged work tree.
|
||||
|
||||
WORKTREE is two reads, not one: the INVENTORY is the index (`ls-files`), so
|
||||
only what will actually be committed can make a claim read EXISTS, and the
|
||||
BYTES are the disk, so an edit that is not staged yet is still graded as the
|
||||
text a reader will see. A path the index lists but the disk has lost falls
|
||||
back to the index blob -- that is byte-for-byte what the commit would carry,
|
||||
and it is the only honest answer to "does this exist in the tree I am about
|
||||
to commit".
|
||||
"""
|
||||
|
||||
def __init__(self, repo, rev):
|
||||
self.repo, self.rev = repo, rev
|
||||
self.tree = set(git(repo, "ls-tree", "-r", "--name-only", rev)
|
||||
.stdout.decode("utf-8", "replace").splitlines())
|
||||
if rev == WORKTREE:
|
||||
raw = git(repo, "ls-files", "-z").stdout.decode("utf-8", "replace")
|
||||
names = [p for p in raw.split("\0") if p]
|
||||
else:
|
||||
names = git(repo, "ls-tree", "-r", "--name-only", rev) \
|
||||
.stdout.decode("utf-8", "replace").splitlines()
|
||||
self.tree = set(names)
|
||||
self.tops = {p.split("/")[0] for p in self.tree}
|
||||
|
||||
def read(self, path):
|
||||
if self.rev == WORKTREE:
|
||||
try:
|
||||
with open(os.path.join(self.repo, path), "rb") as fh:
|
||||
return fh.read()
|
||||
except OSError:
|
||||
p = git(self.repo, "show", ":" + path)
|
||||
if p.returncode != 0:
|
||||
raise CannotGrade("cannot read %s at WORKTREE" % path)
|
||||
return p.stdout
|
||||
p = git(self.repo, "show", "%s:%s" % (self.rev, path))
|
||||
if p.returncode != 0:
|
||||
raise CannotGrade("cannot read %s at %s" % (path, self.rev))
|
||||
@@ -394,7 +454,10 @@ def apply_baseline(rows, ledger):
|
||||
|
||||
def report(rows, stats, rev, sha, corpus, ledger, path, only, context):
|
||||
counts = collections.Counter(r["verdict"] for r in rows.values())
|
||||
print("path_claim_check: %s at %s (%s)" % ("repo", rev, sha[:7]))
|
||||
# WORKTREE has no commit of its own, so the sha printed is the HEAD it sits
|
||||
# on, labelled as such: a reader must not mistake it for the graded tree.
|
||||
where = ("HEAD " + sha[:7] + ", worktree graded") if rev == WORKTREE else sha[:7]
|
||||
print("path_claim_check: %s at %s (%s)" % ("repo", rev, where))
|
||||
print("matcher: /?(?:[A-Za-z0-9_.@+-]+/)+[A-Za-z0-9_.@+-]+\\.(closed ext list), "
|
||||
"at least one '/' required")
|
||||
print(" graded only when the first component is a tracked top-level entry; "
|
||||
@@ -494,7 +557,8 @@ def main():
|
||||
return 2
|
||||
try:
|
||||
store = Store(a.repo, a.rev)
|
||||
sha = git(a.repo, "rev-parse", a.rev).stdout.decode().strip()
|
||||
sha = git(a.repo, "rev-parse",
|
||||
"HEAD" if a.rev == WORKTREE else a.rev).stdout.decode().strip()
|
||||
corpus = a.corpus if a.corpus is not None else \
|
||||
sorted(p for p in store.tree if p.endswith((".md", ".markdown")))
|
||||
if not corpus:
|
||||
@@ -657,6 +721,48 @@ def selftest():
|
||||
cell("c17 a corpus with no markdown is rc 2, never an empty clean census",
|
||||
rc == 2 and "no markdown corpus" in out, "rc=%d %s" % (rc, out[-200:]))
|
||||
|
||||
# --- WORKTREE: the tree you are about to commit, not the last commit ----
|
||||
# One fixture document in three states: the committed claim (HEAD), an
|
||||
# unstaged edit on disk, and a claim that lives only in the index. Each cell
|
||||
# names which of the two reads it measures -- the inventory or the bytes.
|
||||
q = os.path.join(repo, "WORK-QUEUE.md")
|
||||
with open(q) as fh:
|
||||
committed_txt = fh.read()
|
||||
with open(q, "w") as fh:
|
||||
fh.write("the real docs/real.md, with the committed claim gone\n")
|
||||
_, out_h = run("--repo", repo)
|
||||
_, out_w = run("--repo", repo, "--rev", "WORKTREE")
|
||||
cell("c17a WORKTREE reads the bytes on disk; HEAD reads the blob",
|
||||
"1 missing" in out_h and "0 missing" in out_w,
|
||||
"HEAD %r | WORKTREE %r" % (out_h[-140:], out_w[-140:]))
|
||||
with open(os.path.join(repo, "docs", "new.md"), "w") as fh:
|
||||
fh.write("new\n")
|
||||
with open(q, "w") as fh:
|
||||
fh.write("the record now cites docs/new.md\n")
|
||||
subprocess.run(["git", "-C", repo, "add", "-A"], capture_output=True)
|
||||
_, out_h = run("--repo", repo, "--only", "exists")
|
||||
_, out_w = run("--repo", repo, "--rev", "WORKTREE", "--only", "exists")
|
||||
cell("c17b a path staged but never committed is invisible at HEAD, EXISTS under WORKTREE",
|
||||
"docs/new.md" not in out_h and "docs/new.md" in out_w,
|
||||
"HEAD %r | WORKTREE %r" % (out_h[-200:], out_w[-200:]))
|
||||
with open(q, "w") as fh:
|
||||
fh.write("the record cites docs/ghost.md\n")
|
||||
subprocess.run(["git", "-C", repo, "add", "-A"], capture_output=True)
|
||||
rc_h, out_h = run("--repo", repo, "--fail-on-missing", "--baseline", bl)
|
||||
rc_w, out_w = run("--repo", repo, "--rev", "WORKTREE",
|
||||
"--fail-on-missing", "--baseline", bl)
|
||||
cell("c17c a claim only the staged tree makes: rc 0 at HEAD, rc 1 under WORKTREE",
|
||||
rc_h == 0 and rc_w == 1 and "FAIL: 1 missing path" in out_w,
|
||||
"HEAD rc=%d | WORKTREE rc=%d %s" % (rc_h, rc_w, out_w[-200:]))
|
||||
rc, out = run("--repo", td, "--rev", "WORKTREE")
|
||||
cell("c17d WORKTREE outside a git work tree is rc 2, not a crash",
|
||||
rc == 2 and "not a git work tree" in out, "rc=%d %s" % (rc, out[-200:]))
|
||||
# Put the fixture back the way the committed cells below expect it.
|
||||
with open(q, "w") as fh:
|
||||
fh.write(committed_txt)
|
||||
os.remove(os.path.join(repo, "docs", "new.md"))
|
||||
subprocess.run(["git", "-C", repo, "add", "-A"], capture_output=True)
|
||||
|
||||
# --- the ledger's closed set is enforced, not decorative ----------------
|
||||
badcls = os.path.join(td, "badclass.tsv")
|
||||
with open(badcls, "w") as fh:
|
||||
|
||||
@@ -20,7 +20,7 @@ doc_census_check.py 104 cells
|
||||
doc_command_check.py 18 cells
|
||||
gate_blame.py 21 cells
|
||||
landing_check.py 26 cells
|
||||
path_claim_check.py 27 cells
|
||||
path_claim_check.py 31 cells
|
||||
queue_append_audit.py 25 cells
|
||||
queue_claim_check.py 24 fixtures
|
||||
queue_index.py 58 cells
|
||||
@@ -28,5 +28,5 @@ reply_check.py 49 cells
|
||||
selftest_sweep.py 39 cells
|
||||
served_drift_check.py 25 cells
|
||||
tool_map.py 20 cells
|
||||
verify.py 55 cells
|
||||
verify.py 59 cells
|
||||
write_probe.py 15 cells
|
||||
|
+68
-12
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""verify.py -- the HOME the index gate did not have: one command, seven legs,
|
||||
"""verify.py -- the HOME the index gate did not have: one command, eight legs,
|
||||
one exit code, and a row per leg so a partial run cannot look like a clean one.
|
||||
|
||||
WHY THIS EXISTS
|
||||
@@ -45,6 +45,18 @@ WHAT IT RUNS -- one row per leg, cheapest first, never in parallel
|
||||
unknown handles, dates, emptiness), where the ledger states the
|
||||
faults the archive already carried; only a class it does not
|
||||
name is a fault. `--checkin-day DATE` adds a `--since` floor
|
||||
path-claims tools/path_claim_check.py --rev WORKTREE --fail-on-missing
|
||||
every path this archive's prose names, at the tree about to be
|
||||
committed: the INVENTORY is the index, the BYTES are the disk,
|
||||
so a claim written this tick and not yet committed is graded
|
||||
rather than invisible. `tools/path_claim_baseline.tsv` states
|
||||
the readings already reviewed; only a MISSING path the ledger
|
||||
does not name is a fault, and every tolerated row is still
|
||||
printed. WORKTREE, not HEAD, for the reason doc-commands gives
|
||||
below -- HEAD cannot see the tick a lane is writing in -- and
|
||||
the reason the leg exists at all: the instrument shipped
|
||||
2026-10-20 could only read a commit, so it could not grade the
|
||||
only tree that matters before the commit was made
|
||||
reply-headings tools/reply_check.py --baseline tools/reply_baseline.tsv
|
||||
every reply's heading against its own filename (addressee and
|
||||
number), on the same ledger rule: `replies/` is the second
|
||||
@@ -146,7 +158,7 @@ TOOLS ARE RESOLVED FROM --repo, NOT FROM THIS FILE'S DIRECTORY
|
||||
The path must still be the ROOT: a subdirectory is rc 2 naming the root,
|
||||
because widening `--repo` silently would grade a tree nobody named.
|
||||
|
||||
`--selftest` fixture corpus, 55 cells
|
||||
`--selftest` fixture corpus, 59 cells
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -170,6 +182,10 @@ LEGS = (
|
||||
{"key": "checkin", "tool": "checkin_check.py", "slow": False,
|
||||
"what": "grade the newest day's check-ins against the naming and "
|
||||
"duplicate rules"},
|
||||
{"key": "path-claims", "tool": "path_claim_check.py", "slow": False,
|
||||
"what": "every path the archive's prose names, graded at the tree about to "
|
||||
"be committed (index inventory, disk bytes), against the ledger "
|
||||
"that states the readings already reviewed"},
|
||||
{"key": "reply-headings", "tool": "reply_check.py", "slow": False,
|
||||
"what": "grade every reply's heading against its own filename"},
|
||||
{"key": "index-current", "tool": "queue_index.py", "slow": False,
|
||||
@@ -345,6 +361,17 @@ def leg_argv(key, repo, queue, index, checkin_day=None):
|
||||
if checkin_day:
|
||||
argv += ["--since", checkin_day]
|
||||
return argv
|
||||
if key == "path-claims":
|
||||
# WORKTREE, and the ledger from the tree being graded: a revision is
|
||||
# graded against the tolerance it committed, and a lane is graded
|
||||
# against the tree it is about to commit. HEAD would grade the last
|
||||
# commit while the tick's own prose went unread -- the blind spot this
|
||||
# leg exists to close.
|
||||
return [sys.executable, os.path.join(repo, "tools",
|
||||
"path_claim_check.py"),
|
||||
"--repo", repo, "--rev", "WORKTREE", "--fail-on-missing",
|
||||
"--baseline", os.path.join(repo, "tools",
|
||||
"path_claim_baseline.tsv")]
|
||||
if key == "reply-headings":
|
||||
# The ledger comes from the tree being graded, for the same reason the
|
||||
# check-in ledger does: a revision is graded with the tolerance it
|
||||
@@ -624,7 +651,8 @@ def _verify(*argv):
|
||||
return proc.returncode, (proc.stdout or "") + (proc.stderr or "")
|
||||
|
||||
|
||||
GREEN_ALL = {"checkin_check.py": 0, "reply_check.py": 0, "queue_index.py": 0,
|
||||
GREEN_ALL = {"checkin_check.py": 0, "path_claim_check.py": 0,
|
||||
"reply_check.py": 0, "queue_index.py": 0,
|
||||
"queue_append_audit.py": 0, "doc_command_check.py": 0,
|
||||
"selftest_sweep.py": 0, "write_probe.py": 0}
|
||||
|
||||
@@ -643,11 +671,11 @@ def selftest():
|
||||
failures.append("%s\n got: %r\n want: %r" % (label, got, want))
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="verify-selftest-") as td:
|
||||
# 1. seven green stubs -> rc 0
|
||||
# 1. eight green stubs -> rc 0
|
||||
repo = _fixture(os.path.join(td, "c1"), GREEN_ALL)
|
||||
rc, out = _verify("--repo", repo)
|
||||
check("1 all seven legs green -> rc 0", rc, 0)
|
||||
check("1b summary counts the seven legs", "verify: 7 legs, 7 pass, 0 fault, "
|
||||
check("1 all eight legs green -> rc 0", rc, 0)
|
||||
check("1b summary counts the eight legs", "verify: 8 legs, 8 pass, 0 fault, "
|
||||
"0 ungradable, 0 skipped" in out, True)
|
||||
|
||||
# 2. a stale index is a FAULT, and its own line reaches the row
|
||||
@@ -702,8 +730,8 @@ def selftest():
|
||||
rc, out = _verify("--repo", repo, "--json", jpath)
|
||||
obj = json.load(open(jpath)) if os.path.exists(jpath) else {}
|
||||
check("7 --json writes the rows", [r["key"] for r in obj.get("rows", [])],
|
||||
["checkin", "reply-headings", "index-current", "append-audit",
|
||||
"doc-commands", "selftest-sweep", "write-axis"])
|
||||
["checkin", "path-claims", "reply-headings", "index-current",
|
||||
"append-audit", "doc-commands", "selftest-sweep", "write-axis"])
|
||||
check("7b --json carries the same rc the process returned",
|
||||
obj.get("rc"), rc)
|
||||
|
||||
@@ -732,9 +760,10 @@ def selftest():
|
||||
dict(GREEN_ALL, **{"queue_index.py": 1}),
|
||||
outs={"queue_index.py": "STALE: line 1\n"})
|
||||
rc, out = _verify("--repo", repo)
|
||||
order = [out.find(k) for k in ("checkin", "reply-headings", "index-current",
|
||||
"append-audit", "doc-commands",
|
||||
"selftest-sweep", "write-axis")]
|
||||
order = [out.find(k) for k in ("checkin", "path-claims", "reply-headings",
|
||||
"index-current", "append-audit",
|
||||
"doc-commands", "selftest-sweep",
|
||||
"write-axis")]
|
||||
check("11 legs print in fixed order", order == sorted(order)
|
||||
and -1 not in order, True)
|
||||
|
||||
@@ -746,7 +775,7 @@ def selftest():
|
||||
check("12 skipped leg is its own count in --json",
|
||||
obj.get("counts", {}).get("SKIP"), 1)
|
||||
check("12b and the skipped leg is not counted as a pass",
|
||||
obj.get("counts", {}).get(PASS), 6)
|
||||
obj.get("counts", {}).get(PASS), 7)
|
||||
|
||||
# 13. the detail line must not contradict the verdict: a row graded
|
||||
# FAULT beside "0 fatal" is the SILENT failure this gate exists to catch.
|
||||
@@ -945,6 +974,33 @@ def selftest():
|
||||
check("29b the fault row is named doc-commands and carries its reason",
|
||||
"fault: doc-commands" in out and "FLAG_NOT_ACCEPTED" in out, True)
|
||||
|
||||
# 30. THE PATH-CLAIMS LEG, and the tree it must read. The tool is called
|
||||
# with `--rev WORKTREE --fail-on-missing` and the ledger taken from the
|
||||
# tree being graded. HEAD cannot see the tick a lane writes in, so a leg
|
||||
# reading HEAD would grade the last commit while this tick's new prose
|
||||
# went unread -- measured apart from the mode itself in
|
||||
# path_claim_check.py's c17a-c17c.
|
||||
av = os.path.join(td, "c30-argv")
|
||||
repo = _fixture(os.path.join(td, "c30"), GREEN_ALL,
|
||||
argv_markers={"path_claim_check.py": av})
|
||||
rc, out = _verify("--repo", repo)
|
||||
seen = open(av).read() if os.path.exists(av) else ""
|
||||
check("30 the path-claims leg runs --rev WORKTREE --fail-on-missing",
|
||||
"--rev WORKTREE" in seen and "--fail-on-missing" in seen, True)
|
||||
check("30b and it grades against the ledger inside the tree it grades",
|
||||
os.path.join(repo, "tools", "path_claim_baseline.tsv") in seen, True)
|
||||
|
||||
# 31. a path claim the ledger does not name is a FAULT on the gate, named
|
||||
# on the row -- the class that was a report nobody ran until this leg.
|
||||
repo = _fixture(os.path.join(td, "c31"),
|
||||
dict(GREEN_ALL, **{"path_claim_check.py": 1}),
|
||||
outs={"path_claim_check.py":
|
||||
"FAIL: 1 missing path(s) the baseline does not name\n"})
|
||||
rc, out = _verify("--repo", repo)
|
||||
check("31 an unledgered missing path -> rc 1", rc, 1)
|
||||
check("31b the fault row is named path-claims and carries its reason",
|
||||
"fault: path-claims" in out and "does not name" in out, True)
|
||||
|
||||
# 27. the count in this file's own docstring is a CLAIM, so it is graded
|
||||
# against the count that actually ran. On 2026-10-19 the docstring and
|
||||
# the run said 50 while the argparse help string still said 42 and
|
||||
|
||||
Reference in new issue
Block a user