sol: verify probe-set classification (4 files READY) + commit classification files
This commit is contained in:
1 parent
607c2125d2
commit
93fa377b65
8 files changed
+1020
No files matched your search
@@ -0,0 +1,471 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Q60 — census of truncated identifiers in the archive, and the grading of each one.
|
||||||
|
|
||||||
|
A truncated identifier is a token written as a prefix followed by an ellipsis: `beae0b1f-…`.
|
||||||
|
This tool does three things, and prints its own matcher for each.
|
||||||
|
|
||||||
|
census every truncated token in the tracked tree at a rev, by file:line, with the counts in both
|
||||||
|
units the archive keeps confusing: occurrences and distinct tokens.
|
||||||
|
table the same measurement rendered as the markdown table the queue item asks for.
|
||||||
|
check re-measure everything and grade a written table against it. A row naming a token the
|
||||||
|
census does not contain, a row that grades a token differently from the measurement, or a
|
||||||
|
census token with no row is a fault. This is the artifact's CHECK line.
|
||||||
|
rule the item's enforcement clause, measured instead of stated: every line that carries a
|
||||||
|
truncated token *and* a probe/deadness verdict (`→ 200`, `404`, `URL DEAD`, `dead link`,
|
||||||
|
`no longer resolves`) is graded by whether the address it attributes that verdict to is
|
||||||
|
written. CARRIES when the full value is on the line; RECOVERABLE when the value survives
|
||||||
|
elsewhere in the tree or in history (the row can be repaired now, so it is a fault);
|
||||||
|
EXHAUSTED when it survives nowhere (the row is Testimony only, graded at write time and
|
||||||
|
not repaired later by inference). `--baseline` is the ledger: a fault row a reader has
|
||||||
|
already reviewed, printed on every run with its owner, never dropped in silence.
|
||||||
|
|
||||||
|
Search ladder for one token, in order, all of it printed per token:
|
||||||
|
|
||||||
|
S1 tree full UUID-shaped values in every tracked file at the rev
|
||||||
|
S2 extend a longer prefix on record -- the same leading group followed by more hex and an
|
||||||
|
ellipsis: a site that narrowed the truncation without completing it
|
||||||
|
S3 history `git log --all -S<prefix>` -> every commit whose patch mentions the prefix, then the
|
||||||
|
full UUID shape re-scanned in each such commit's tree
|
||||||
|
|
||||||
|
Scope is the archive: tracked bytes at the rev. An untracked file on this host is not a record, so it
|
||||||
|
cannot make a row checkable; the worktree was swept separately, by hand, in the artifact's report.
|
||||||
|
|
||||||
|
One pass over the tree: blobs are read through `git ls-tree` + `git cat-file --batch` rather than one
|
||||||
|
`git grep` per token, because the per-token extension pattern walked this repository for 180 s
|
||||||
|
without finishing (unbounded `(-[0-9a-f]{4,})*` backtracks on a line holding a long hex run and no
|
||||||
|
ellipsis). The quantifiers here are bounded.
|
||||||
|
|
||||||
|
Exit codes follow the tools/ contract: 0 clean, 1 ran and graded a fault, 2 could not grade.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ELL = "\u2026"
|
||||||
|
DOGS = "..."
|
||||||
|
CANON_RE = r"[0-9a-f]{6,12}(?:-[0-9a-f]{1,12}){0,3}-" + ELL
|
||||||
|
ANY_RE = r"[0-9a-f]{6,12}(?:-[0-9a-f]{1,12}){0,3}-(?:" + ELL + r"|\.\.\.)"
|
||||||
|
CANON = re.compile(CANON_RE)
|
||||||
|
ANY = re.compile(ANY_RE)
|
||||||
|
UUID_RE = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||||
|
UUID = re.compile(UUID_RE)
|
||||||
|
|
||||||
|
# The verdict vocabulary for `rule`, printed by the tool so a reader can audit what fired. A row
|
||||||
|
# that attributes a read to an address is only as checkable as the address it writes; these are the
|
||||||
|
# shapes this archive uses to attribute a read. The status-code pattern excludes a digit or a dash
|
||||||
|
# on either side so `$150–200/hr` is not a status, while `(200 re-check`, `→ 200` and `200, 37,412 B`
|
||||||
|
# are. Every cue that fired on a row is printed with the row.
|
||||||
|
VERDICT_CUES = (
|
||||||
|
("status", re.compile(r"(?<![\$0-9\u2013-])\b(?:200|201|204|301|302|304|400|403|404|410|429|500|502|503)\b(?![0-9])")),
|
||||||
|
("URL-DEAD", re.compile(r"\bURL[\s-]*DEAD\b", re.I)),
|
||||||
|
("dead-link", re.compile(r"\bdead[\s-]link\b", re.I)),
|
||||||
|
("no-longer-resolves", re.compile(r"no longer (?:resolves|resolving)", re.I)),
|
||||||
|
("does-not-resolve", re.compile(r"(?:does not|doesn't|did not) (?:resolve|resolves)", re.I)),
|
||||||
|
)
|
||||||
|
# A truncated token written *inside* a URL-ish token: `jobs.micro1.ai/post/3d79ba82-…`. Advisory
|
||||||
|
# only -- `rule` grades it, `census`/`table` do not depend on it.
|
||||||
|
URL_CTX = re.compile(r"(?:[A-Za-z0-9_.:-]+/)+[A-Za-z0-9_./:%-]*$")
|
||||||
|
RULE_CLASSES = ("CARRIES", "RECOVERABLE", "RECOVERABLE-HIST", "EXHAUSTED")
|
||||||
|
|
||||||
|
|
||||||
|
def git(repo, *args):
|
||||||
|
p = subprocess.run(["git", *args], cwd=repo, capture_output=True, text=True)
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise SystemExit(f"cannot grade: git {' '.join(args)} rc={p.returncode}: "
|
||||||
|
f"{p.stderr.strip()[:300]}")
|
||||||
|
return p.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def iter_tree(repo, rev):
|
||||||
|
"""Yield (path, lineno, text) for every text line of every tracked file at `rev`, one pass."""
|
||||||
|
listing = git(repo, "ls-tree", "-r", "-z", "--full-tree", rev)
|
||||||
|
entries = []
|
||||||
|
for chunk in listing.split("\0"):
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
meta, path = chunk.split("\t", 1)
|
||||||
|
mode, otype, sha = meta.split()
|
||||||
|
if otype == "blob" and mode in ("100644", "100755"):
|
||||||
|
entries.append((sha, path))
|
||||||
|
if not entries:
|
||||||
|
raise SystemExit(f"cannot grade: no blobs at {rev}")
|
||||||
|
proc = subprocess.Popen(["git", "cat-file", "--batch"], cwd=repo,
|
||||||
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
stdin = b"".join(sha.encode() + b"\n" for sha, _ in entries)
|
||||||
|
out, _ = proc.communicate(stdin)
|
||||||
|
pos = 0
|
||||||
|
for sha, path in entries:
|
||||||
|
nl = out.index(b"\n", pos)
|
||||||
|
header = out[pos:nl].split()
|
||||||
|
size = int(header[2])
|
||||||
|
blob = out[nl + 1: nl + 1 + size]
|
||||||
|
pos = nl + 1 + size + 1
|
||||||
|
if b"\x00" in blob:
|
||||||
|
continue
|
||||||
|
for i, line in enumerate(blob.decode("utf-8", "replace").splitlines(), 1):
|
||||||
|
yield path, i, line
|
||||||
|
|
||||||
|
|
||||||
|
def measure(repo, rev):
|
||||||
|
trunc = [] # [(path, lineno, token)] -- canonical: prefix-… as the item defines it
|
||||||
|
uuid_index = {} # first 8 hex -> [(path, lineno, uuid)]
|
||||||
|
ext_index = {} # first 8 hex -> [(path, lineno, written)] -- same id, more hex, still cut
|
||||||
|
text_of = {} # (path, lineno) -> line text, only for lines carrying a canonical token
|
||||||
|
for path, lineno, line in iter_tree(repo, rev):
|
||||||
|
canon_spans = []
|
||||||
|
for m in CANON.finditer(line):
|
||||||
|
tok = m.group(0)
|
||||||
|
trunc.append((path, lineno, tok))
|
||||||
|
canon_spans.append((m.start(), m.end()))
|
||||||
|
if canon_spans:
|
||||||
|
text_of[(path, lineno)] = line
|
||||||
|
for m in UUID.finditer(line):
|
||||||
|
uuid_index.setdefault(m.group(0)[:8], []).append((path, lineno, m.group(0)))
|
||||||
|
for m in ANY.finditer(line):
|
||||||
|
written = m.group(0)
|
||||||
|
if written.count("-") < 2:
|
||||||
|
continue # leading group + ellipsis only: already a canonical token above
|
||||||
|
ext_index.setdefault(written.split("-")[0], []).append((path, lineno, written))
|
||||||
|
tokens = sorted({t for _, _, t in trunc})
|
||||||
|
sites = {}
|
||||||
|
for path, lineno, token in trunc:
|
||||||
|
sites.setdefault(token, []).append((path, lineno))
|
||||||
|
files = sorted({p for p, _, _ in trunc})
|
||||||
|
return trunc, tokens, sites, uuid_index, ext_index, files, text_of
|
||||||
|
|
||||||
|
|
||||||
|
def prefix_of(token):
|
||||||
|
return token.split("-")[0]
|
||||||
|
|
||||||
|
|
||||||
|
def placeholder(uuid):
|
||||||
|
"""True for a UUID-shaped string that is not a value: all-zero groups, or a repeated character.
|
||||||
|
|
||||||
|
`beae0b1f-0000-0000-0000-000000000000` is on record at
|
||||||
|
`replies/2026-10-24-to-zephyrus-86.md:12` as the near-miss probe whose 404 proves nothing. It
|
||||||
|
has the prefix, it is a syntactically valid UUID, and it is not a completion; promoting it to
|
||||||
|
"full value present" is the exact failure the queue item calls out, so the classifier refuses it
|
||||||
|
by shape rather than by the judgement of whoever wrote the row.
|
||||||
|
"""
|
||||||
|
body = uuid.replace("-", "")
|
||||||
|
return len(set(body)) <= 2 or all(set(g) == {"0"} for g in uuid.split("-")[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def history(repo, prefix):
|
||||||
|
out = git(repo, "log", "--all", "-S" + prefix, "--format=%H %s")
|
||||||
|
return [ln for ln in out.splitlines() if ln.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def history_full(repo, prefix, commits):
|
||||||
|
pat = prefix + r"-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||||
|
real, fake = [], []
|
||||||
|
for line in commits:
|
||||||
|
sha = line.split()[0]
|
||||||
|
p = subprocess.run(["git", "grep", "-nE", "-a", pat, sha, "--", "."],
|
||||||
|
cwd=repo, capture_output=True, text=True)
|
||||||
|
if p.returncode == 0:
|
||||||
|
for row in p.stdout.splitlines():
|
||||||
|
parts = row.split(":", 3)
|
||||||
|
if len(parts) != 4:
|
||||||
|
continue
|
||||||
|
found = UUID.search(parts[3])
|
||||||
|
if not found:
|
||||||
|
continue
|
||||||
|
rec = (sha[:12], parts[1], int(parts[2]), found.group(0))
|
||||||
|
(fake if placeholder(found.group(0)) else real).append(rec)
|
||||||
|
return real, fake
|
||||||
|
|
||||||
|
|
||||||
|
def resolve(repo, tokens, uuid_index, ext_index):
|
||||||
|
out = {}
|
||||||
|
for token in tokens:
|
||||||
|
p = prefix_of(token)
|
||||||
|
candidates = [r for r in uuid_index.get(p, []) if r[2].startswith(p)]
|
||||||
|
full = [r for r in candidates if not placeholder(r[2])]
|
||||||
|
fake = [r for r in candidates if placeholder(r[2])]
|
||||||
|
ext = [r for r in ext_index.get(p, []) if r[2] != token]
|
||||||
|
commits = [] if full else history(repo, p)
|
||||||
|
hist, hist_fake = history_full(repo, p, commits) if commits else ([], [])
|
||||||
|
if full:
|
||||||
|
grade = "FULL"
|
||||||
|
elif hist:
|
||||||
|
grade = "HIST"
|
||||||
|
else:
|
||||||
|
grade = "UNRECOVERED"
|
||||||
|
out[token] = {"sites": [], "full": full, "fake": fake, "ext": ext,
|
||||||
|
"commits": commits, "hist": hist, "hist_fake": hist_fake, "grade": grade}
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_sites(sites):
|
||||||
|
return " · ".join(f"`{p}:{n}`" for p, n in sites)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt_grade(token, info):
|
||||||
|
if info["grade"] == "FULL":
|
||||||
|
where = " · ".join(f"`{p}:{n}`" for p, n, _ in info["full"])
|
||||||
|
return f"full value present at {where} (`{info['full'][0][2]}`)"
|
||||||
|
if info["grade"] == "HIST":
|
||||||
|
where = " · ".join(f"`{c}:{p}:{n}`" for c, p, n, _ in info["hist"])
|
||||||
|
return f"full value present at {where} (historic tree, not at HEAD)"
|
||||||
|
bits = []
|
||||||
|
if info["ext"]:
|
||||||
|
bits.append("prefix extended, still truncated: " + " · ".join(
|
||||||
|
f"`{p}:{n}` → `{t}`" for p, n, t in info["ext"]))
|
||||||
|
if info["fake"]:
|
||||||
|
bits.append("S1 tree: only placeholder " + " · ".join(
|
||||||
|
f"`{p}:{n}` = `{u}` (not a completion)" for p, n, u in info["fake"]))
|
||||||
|
else:
|
||||||
|
bits.append("S1 tree: no full UUID with this prefix at any path at this rev")
|
||||||
|
hist_fake = info.get("hist_fake") or []
|
||||||
|
bits.append(f"S3 history: `git log --all -S{prefix_of(token)}` → "
|
||||||
|
f"{len(info['commits'])} commit(s) whose patch mentions the prefix; "
|
||||||
|
+ ("only placeholders in their trees"
|
||||||
|
if hist_fake else "no full value in any of their trees"))
|
||||||
|
return "**Unrecovered** — " + "; ".join(bits)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_census(args, repo):
|
||||||
|
trunc, tokens, sites, uuid_index, ext_index, files, _text = measure(repo, args.rev)
|
||||||
|
print(f"matcher: git grep -nE '{CANON_RE}' {args.rev} -- . (canonical: ellipsis only)")
|
||||||
|
print(f"rev {args.rev}: {len(trunc)} occurrence(s), {len(tokens)} distinct token(s), "
|
||||||
|
f"{len(files)} file(s)")
|
||||||
|
info = resolve(repo, tokens, uuid_index, ext_index)
|
||||||
|
for token in tokens:
|
||||||
|
i = info[token]
|
||||||
|
print(f"\n{token} [{i['grade']}] {len(sites[token])} site(s)")
|
||||||
|
for path, lineno in sites[token]:
|
||||||
|
print(f" {path}:{lineno}")
|
||||||
|
for path, lineno, uuid in i["full"]:
|
||||||
|
print(f" FULL {path}:{lineno} {uuid}")
|
||||||
|
for path, lineno, uuid in i["fake"]:
|
||||||
|
print(f" FAKE {path}:{lineno} {uuid} (placeholder, not a completion)")
|
||||||
|
for path, lineno, written in i["ext"]:
|
||||||
|
print(f" EXT {path}:{lineno} {written}")
|
||||||
|
print(f" hist {len(i['commits'])} commit(s)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_table(args, repo):
|
||||||
|
trunc, tokens, sites, uuid_index, ext_index, files, _text = measure(repo, args.rev)
|
||||||
|
info = resolve(repo, tokens, uuid_index, ext_index)
|
||||||
|
lines = [f"<!-- generated: wizard-work/volt/q60_truncated_census.py table "
|
||||||
|
f"--rev {args.rev} -->",
|
||||||
|
"",
|
||||||
|
f"Census at `{args.rev}`: **{len(trunc)} occurrences / {len(tokens)} distinct "
|
||||||
|
f"tokens / {len(files)} files**.",
|
||||||
|
"",
|
||||||
|
"| token (as written) | file:line sites | grading |",
|
||||||
|
"|---|---|---|"]
|
||||||
|
for token in tokens:
|
||||||
|
info[token]["sites"] = sites[token]
|
||||||
|
lines.append(f"| `{token}` | {fmt_sites(sites[token])} | {fmt_grade(token, info[token])} |")
|
||||||
|
text = "\n".join(lines) + "\n"
|
||||||
|
if args.out:
|
||||||
|
with open(args.out, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(text)
|
||||||
|
print(f"wrote {args.out}: {len(tokens)} row(s)")
|
||||||
|
else:
|
||||||
|
sys.stdout.write(text)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
ROW_RE = re.compile(r"^\| `([0-9a-f]{6,12}(?:-[0-9a-f]{1,12})*-\u2026)` \| (.*?) \| (.*) \|$")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_check(args, repo):
|
||||||
|
try:
|
||||||
|
with open(args.table, "r", encoding="utf-8") as fh:
|
||||||
|
text = fh.read()
|
||||||
|
except OSError as exc:
|
||||||
|
print(f"cannot grade: {args.table}: {exc}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
trunc, tokens, sites, uuid_index, ext_index, files, _text = measure(repo, args.rev)
|
||||||
|
info = resolve(repo, tokens, uuid_index, ext_index)
|
||||||
|
rows = {}
|
||||||
|
for line in text.splitlines():
|
||||||
|
m = ROW_RE.match(line.strip())
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
if m.group(1) in rows:
|
||||||
|
print(f"FAULT duplicate row for {m.group(1)}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
rows[m.group(1)] = (m.group(2), m.group(3))
|
||||||
|
problems = []
|
||||||
|
for token in tokens:
|
||||||
|
if token not in rows:
|
||||||
|
problems.append(f"missing row: {token}")
|
||||||
|
continue
|
||||||
|
sites_cell, grade_cell = rows[token]
|
||||||
|
for path, lineno in sites[token]:
|
||||||
|
if f"`{path}:{lineno}`" not in sites_cell:
|
||||||
|
problems.append(f"{token}: site not named in row: {path}:{lineno}")
|
||||||
|
g = info[token]["grade"]
|
||||||
|
if g == "FULL":
|
||||||
|
if "Unrecovered" in grade_cell:
|
||||||
|
problems.append(f"{token}: measured FULL, row says Unrecovered")
|
||||||
|
for path, lineno, _ in info[token]["full"]:
|
||||||
|
if f"`{path}:{lineno}`" not in grade_cell:
|
||||||
|
problems.append(f"{token}: full value at {path}:{lineno} not graded present")
|
||||||
|
else:
|
||||||
|
if "Unrecovered" not in grade_cell:
|
||||||
|
problems.append(f"{token}: measured {g}, row does not say Unrecovered")
|
||||||
|
if "full value present" in grade_cell:
|
||||||
|
problems.append(f"{token}: measured {g}, row claims a full value")
|
||||||
|
for path, lineno, _ in info[token]["fake"]:
|
||||||
|
if f"`{path}:{lineno}`" not in grade_cell:
|
||||||
|
problems.append(f"{token}: placeholder at {path}:{lineno} not named as one")
|
||||||
|
for token in rows:
|
||||||
|
if token not in tokens:
|
||||||
|
problems.append(f"row names a token the census does not contain: {token}")
|
||||||
|
if problems:
|
||||||
|
print(f"q60 truncated census: {len(problems)} fault(s) against {args.table} "
|
||||||
|
f"({len(tokens)} token(s) measured at {args.rev})", file=sys.stderr)
|
||||||
|
for p in problems:
|
||||||
|
print(f" {p}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
full = sum(1 for t in tokens if info[t]["grade"] == "FULL")
|
||||||
|
print(f"q60 truncated census: {len(tokens)} token(s) at {args.rev}, {len(rows)} row(s), "
|
||||||
|
f"{full} full / {len(tokens) - full} Unrecovered — every row's site list and grading "
|
||||||
|
f"agrees with a fresh measurement")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def rule_rows(repo, rev):
|
||||||
|
"""One row per (line, token) where the line attributes a read and writes the address truncated."""
|
||||||
|
trunc, tokens, sites, uuid_index, ext_index, files, text_of = measure(repo, rev)
|
||||||
|
info = resolve(repo, tokens, uuid_index, ext_index)
|
||||||
|
out = []
|
||||||
|
for (path, lineno), line in sorted(text_of.items()):
|
||||||
|
cues = sorted({name for name, rx in VERDICT_CUES if rx.search(line)})
|
||||||
|
if not cues:
|
||||||
|
continue
|
||||||
|
full_on_line = {u[:8] for u in
|
||||||
|
(m.group(0) for m in UUID.finditer(line)) if not placeholder(u)}
|
||||||
|
for m in CANON.finditer(line):
|
||||||
|
tok = m.group(0)
|
||||||
|
p = prefix_of(tok)
|
||||||
|
i = info[tok]
|
||||||
|
if p in full_on_line:
|
||||||
|
cls = "CARRIES"
|
||||||
|
where = [r for r in i["full"] if r[0] == path and r[1] == lineno] or i["full"]
|
||||||
|
elif i["full"]:
|
||||||
|
cls = "RECOVERABLE"
|
||||||
|
where = i["full"]
|
||||||
|
elif i.get("hist"):
|
||||||
|
cls = "RECOVERABLE-HIST"
|
||||||
|
where = [("{}:{}:{}".format(c.split()[0][:12], pp, nn), pp, nn, uu)
|
||||||
|
for c, pp, nn, uu in i["hist"]]
|
||||||
|
else:
|
||||||
|
cls = "EXHAUSTED"
|
||||||
|
where = []
|
||||||
|
in_url = bool(URL_CTX.search(line[:m.start()]))
|
||||||
|
out.append({"path": path, "line": lineno, "token": tok, "cues": cues,
|
||||||
|
"class": cls, "where": where, "in_url": in_url, "text": line.strip()})
|
||||||
|
return out, tokens, info
|
||||||
|
|
||||||
|
|
||||||
|
def load_rule_baseline(path):
|
||||||
|
"""`<path>:<line>\\t<token>\\t<CLASS>\\t<owner> -- <reason>`; blank lines and # comments ignored."""
|
||||||
|
rows = {}
|
||||||
|
with open(path, "r", encoding="utf-8") as fh:
|
||||||
|
for raw in fh:
|
||||||
|
line = raw.rstrip("\n")
|
||||||
|
if not line.strip() or line.lstrip().startswith("#"):
|
||||||
|
continue
|
||||||
|
parts = line.split("\t")
|
||||||
|
if len(parts) < 4:
|
||||||
|
raise SystemExit(f"cannot grade: baseline row needs 4 fields, got {len(parts)}: {line!r}")
|
||||||
|
key, tok, cls, note = parts[0], parts[1], parts[2], parts[3]
|
||||||
|
if cls not in RULE_CLASSES:
|
||||||
|
raise SystemExit(f"cannot grade: baseline class {cls!r} not in {RULE_CLASSES}")
|
||||||
|
rows[(key, tok)] = (cls, note)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_rule(args, repo):
|
||||||
|
rows, tokens, info = rule_rows(repo, args.rev)
|
||||||
|
print("matcher: cue vocabulary = " + ", ".join(f"{n}({rx.pattern})" for n, rx in VERDICT_CUES))
|
||||||
|
print(f"rev {args.rev}: {len(rows)} verdict occurrence(s) on "
|
||||||
|
f"{len({(r['path'], r['line']) for r in rows})} line(s), "
|
||||||
|
f"{len({r['token'] for r in rows})} distinct token(s), "
|
||||||
|
f"{len({r['path'] for r in rows})} file(s)\n")
|
||||||
|
baseline = load_rule_baseline(args.baseline) if args.baseline else {}
|
||||||
|
faults, tolerated, cleared = [], [], []
|
||||||
|
for r in rows:
|
||||||
|
key = (f"{r['path']}:{r['line']}", r["token"])
|
||||||
|
where = ""
|
||||||
|
if r["where"]:
|
||||||
|
sites = " · ".join(f"`{p}:{n}`" for p, n, *_ in r["where"][:6])
|
||||||
|
val = ""
|
||||||
|
if r["where"] and len(r["where"][0]) == 4:
|
||||||
|
val = f" (`{r['where'][0][3]}`)"
|
||||||
|
where = f" — value at {sites}{val}"
|
||||||
|
flag = " url-context" if r["in_url"] else ""
|
||||||
|
body = f"{r['path']}:{r['line']} `{r['token']}` cues={{{','.join(r['cues'])}}}{flag}"
|
||||||
|
head = f"{r['class']:<16} {body}"
|
||||||
|
if r["class"] in ("RECOVERABLE", "RECOVERABLE-HIST", "EXHAUSTED"):
|
||||||
|
if key in baseline:
|
||||||
|
cls, note = baseline[key]
|
||||||
|
mark = "KNOWN" if cls == r["class"] else f"DRIFT({cls})"
|
||||||
|
tolerated.append((mark, body, where, note))
|
||||||
|
else:
|
||||||
|
faults.append((head, where))
|
||||||
|
else:
|
||||||
|
print(head + where)
|
||||||
|
for mark, body, where, note in tolerated:
|
||||||
|
print(f"{mark:<16} {body}{where}\n baseline: {note}")
|
||||||
|
print()
|
||||||
|
for key, (cls, note) in sorted(baseline.items()):
|
||||||
|
if not any((f"{r['path']}:{r['line']}", r["token"]) == key
|
||||||
|
and r["class"] in ("RECOVERABLE", "RECOVERABLE-HIST", "EXHAUSTED") for r in rows):
|
||||||
|
cleared.append((key, cls, note))
|
||||||
|
for (key, tok), cls, note in cleared:
|
||||||
|
print(f"STALE baseline row names no measured fault row: {key} `{tok}` ({cls})")
|
||||||
|
tally = {}
|
||||||
|
for r in rows:
|
||||||
|
tally[r["class"]] = tally.get(r["class"], 0) + 1
|
||||||
|
counts = " / ".join(f"{c} {tally.get(c, 0)}" for c in RULE_CLASSES)
|
||||||
|
print(f"rule: {counts}; {len(tolerated)} tolerated in the baseline, "
|
||||||
|
f"{len(faults)} new fault(s), {len(cleared)} stale baseline row(s)")
|
||||||
|
if faults:
|
||||||
|
print("\nNEW FAULTS — a verdict row whose address is not written and whose value survives:", file=sys.stderr)
|
||||||
|
for head, where in faults:
|
||||||
|
print(f" {head}{where}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if cleared:
|
||||||
|
print("FAULT: a baseline row outlives the row it tolerates", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Q60 census of truncated identifiers; see the module docstring.")
|
||||||
|
ap.add_argument("--repo", default=".")
|
||||||
|
ap.add_argument("--rev", default="HEAD")
|
||||||
|
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||||
|
sub.add_parser("census")
|
||||||
|
t = sub.add_parser("table")
|
||||||
|
t.add_argument("--out", default=None)
|
||||||
|
c = sub.add_parser("check")
|
||||||
|
c.add_argument("table")
|
||||||
|
r = sub.add_parser("rule")
|
||||||
|
r.add_argument("--baseline", default=None)
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
repo = os.path.abspath(args.repo)
|
||||||
|
if args.cmd == "census":
|
||||||
|
return cmd_census(args, repo)
|
||||||
|
if args.cmd == "table":
|
||||||
|
return cmd_table(args, repo)
|
||||||
|
if args.cmd == "rule":
|
||||||
|
return cmd_rule(args, repo)
|
||||||
|
return cmd_check(args, repo)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""q93_axis_negative_probe.py -- Q93's two negative cells, at the AXIS level.
|
||||||
|
|
||||||
|
Q93 (2026-10-27) changed the malformed-input axis of `tools/selftest_sweep.py`
|
||||||
|
from PROSE grading to CONTRACT grading, and gave the run one verdict line whose
|
||||||
|
text and exit code come from the same function. A green axis after such a change
|
||||||
|
proves only that the corpus agrees with the new rule; it does not prove the axis
|
||||||
|
can still FAIL. This probe supplies the two negative shapes Q93 asks for, through
|
||||||
|
the real subprocess path (`discover` -> `read_axis` -> `report_read`), against
|
||||||
|
scratch copies of `tools/`:
|
||||||
|
|
||||||
|
A answers-zero : a tool that exits 0 on a malformed input
|
||||||
|
EXPECT rc 1, malformed-input 1 fault, verdict RC_WRONG,
|
||||||
|
and `citation_hash_class.py/not-a-repo` named on the summary
|
||||||
|
line (Q93: UNEXPLAINED never appears unnamed).
|
||||||
|
B odd-prose : a tool that refuses correctly at rc 2 with prose no table
|
||||||
|
ever pinned
|
||||||
|
EXPECT rc 0, 0 fault -- the axis grades the contract, so a
|
||||||
|
sound refusal with different wording is no longer red. Under
|
||||||
|
the pre-Q93 table this same tool was UNEXPLAINED, which is
|
||||||
|
the defect: the axis would have gone green only if the tool's
|
||||||
|
message were degraded to match a pinned phrase.
|
||||||
|
|
||||||
|
Root is resolved from this file (`wizard-work/volt/` -> repo root), not written
|
||||||
|
down, so a second checkout re-verifies itself. Scratch copies live under $TMPDIR
|
||||||
|
and are removed. No file in the working tree is modified.
|
||||||
|
|
||||||
|
rc 0 both shapes behaved as stated (the axis can fail, and for the right reason)
|
||||||
|
rc 1 a shape behaved differently: the axis is blind, or still grades wording
|
||||||
|
rc 2 could not grade: no selftest_sweep.py, no tools/, or a scratch build failed
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import importlib.util
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
SWEEP = os.path.join(ROOT, "tools", "selftest_sweep.py")
|
||||||
|
TOOLS = os.path.join(ROOT, "tools")
|
||||||
|
|
||||||
|
SHIM_ZERO = ("import sys as _s, os as _o\n"
|
||||||
|
"if '--repo' in _s.argv:\n"
|
||||||
|
" _r = _s.argv[_s.argv.index('--repo') + 1]\n"
|
||||||
|
" if not _o.path.isdir(_o.path.join(_r, '.git')):\n"
|
||||||
|
" print('0 citations classified; nothing to do')\n"
|
||||||
|
" raise SystemExit(0)\n")
|
||||||
|
SHIM_REFUSE = ("import sys as _s, os as _o\n"
|
||||||
|
"if '--repo' in _s.argv:\n"
|
||||||
|
" _r = _s.argv[_s.argv.index('--repo') + 1]\n"
|
||||||
|
" if not _o.path.isdir(_o.path.join(_r, '.git')):\n"
|
||||||
|
" _s.stderr.write('totally different words, same refusal\\n')\n"
|
||||||
|
" raise SystemExit(2)\n")
|
||||||
|
|
||||||
|
|
||||||
|
def load_sweep():
|
||||||
|
spec = importlib.util.spec_from_file_location("q93_sweep", SWEEP)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod) # side-effect-free: guarded by __main__
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def build(base: str, name: str, shim: str) -> str:
|
||||||
|
"""A copy of tools/ whose citation_hash_class.py opens with `shim`."""
|
||||||
|
d = os.path.join(base, name)
|
||||||
|
os.makedirs(d)
|
||||||
|
for fn in sorted(os.listdir(TOOLS)):
|
||||||
|
if fn.endswith(".py"):
|
||||||
|
shutil.copy(os.path.join(TOOLS, fn), os.path.join(d, fn))
|
||||||
|
target = os.path.join(d, "citation_hash_class.py")
|
||||||
|
with open(target, encoding="utf-8") as fh:
|
||||||
|
body = fh.read()
|
||||||
|
with open(target, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(shim + body)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def probe(mod, tools_dir: str):
|
||||||
|
"""Run both input axes against `tools_dir`; return (rc, read, malformed,
|
||||||
|
malformed summary line, named faults)."""
|
||||||
|
rows, _source, _untracked = mod.discover(None, tools_dir)
|
||||||
|
axis = mod.read_axis(rows, 120)
|
||||||
|
named = [(r["tool"], r["probe"], r["verdict"]) for r in mod.axis_faults(axis)]
|
||||||
|
buf = io.StringIO()
|
||||||
|
with contextlib.redirect_stdout(buf):
|
||||||
|
rc, read_fault, malformed_fault = mod.report_read(axis, quiet=True)
|
||||||
|
lines = [ln for ln in buf.getvalue().splitlines()
|
||||||
|
if ln.startswith("malformed-input:")]
|
||||||
|
return rc, read_fault, malformed_fault, (lines[0] if lines else ""), named
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not os.path.isfile(SWEEP):
|
||||||
|
print("cannot grade: no tools/selftest_sweep.py under " + ROOT)
|
||||||
|
return 2
|
||||||
|
if not os.path.isdir(TOOLS):
|
||||||
|
print("cannot grade: no tools/ under " + ROOT)
|
||||||
|
return 2
|
||||||
|
mod = load_sweep()
|
||||||
|
ok = True
|
||||||
|
with tempfile.TemporaryDirectory(prefix="q93neg.") as base:
|
||||||
|
d = build(base, "answers-zero", SHIM_ZERO)
|
||||||
|
rc, _rf, mf, line, named = probe(mod, d)
|
||||||
|
a_ok = (rc == 1 and mf == 1
|
||||||
|
and ("citation_hash_class.py", "not-a-repo", "RC_WRONG") in named
|
||||||
|
and "citation_hash_class.py/not-a-repo" in line)
|
||||||
|
ok &= a_ok
|
||||||
|
print("A answers-zero rc=%d malformed_fault=%d %s"
|
||||||
|
% (rc, mf, "EXPECTED" if a_ok else "UNEXPECTED"))
|
||||||
|
print(" %s" % line)
|
||||||
|
|
||||||
|
d = build(base, "odd-prose", SHIM_REFUSE)
|
||||||
|
rc, _rf, mf, line, named = probe(mod, d)
|
||||||
|
b_ok = (rc == 0 and mf == 0 and not named)
|
||||||
|
ok &= b_ok
|
||||||
|
print("B odd-prose rc=%d malformed_fault=%d %s"
|
||||||
|
% (rc, mf, "EXPECTED" if b_ok else "UNEXPECTED"))
|
||||||
|
print(" %s" % line)
|
||||||
|
|
||||||
|
print("q93 axis negative cells: %s" % ("2/2 as expected" if ok else "FAILED"))
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""test_jsonl_validate.py — pins the exit-code and schema contract of jsonl_validate.py.
|
||||||
|
|
||||||
|
Written for queue item Q104: the module shipped at afb182a with no test, and its
|
||||||
|
undecodable-input path exited 1 (the code its own docstring assigns to "the rows
|
||||||
|
are bad") after a UnicodeDecodeError traceback. Exit 1 means "some record is
|
||||||
|
invalid"; a file that could not be decoded produced no verdict on any record, so
|
||||||
|
a caller that trusted the code reported a rejection it never performed.
|
||||||
|
|
||||||
|
Everything is asserted through the real process, not by importing main(): the
|
||||||
|
contract under test is the exit status a shell sees, and importing would let an
|
||||||
|
uncaught traceback escape as an exception instead of as a status.
|
||||||
|
|
||||||
|
unittest.TestCase (not module-level bare asserts) on purpose: the lane's gate is
|
||||||
|
`python3 -m unittest discover -p 'test_*.py'`, which collects this file only if
|
||||||
|
it defines test cases (Q105 records the 27 files the gate cannot see).
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SCRIPT = Path(__file__).resolve().with_name("jsonl_validate.py")
|
||||||
|
|
||||||
|
SCHEMA = {
|
||||||
|
"required": ["id"],
|
||||||
|
"fields": {
|
||||||
|
"id": "int",
|
||||||
|
"name": "str?",
|
||||||
|
"tags": "list?",
|
||||||
|
"score": "float?",
|
||||||
|
"active": "bool?",
|
||||||
|
"meta": "object?",
|
||||||
|
"note": "str", # present-and-null must be an error (not optional)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class JsonlValidatorContract(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self._tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.dir = Path(self._tmp.name)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self._tmp.cleanup()
|
||||||
|
|
||||||
|
# --- helpers -----------------------------------------------------------
|
||||||
|
def write(self, name, data):
|
||||||
|
path = self.dir / name
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
path.write_bytes(data)
|
||||||
|
elif isinstance(data, (dict, list)):
|
||||||
|
path.write_text(json.dumps(data), encoding="utf-8")
|
||||||
|
else:
|
||||||
|
path.write_text(data, encoding="utf-8")
|
||||||
|
return str(path)
|
||||||
|
|
||||||
|
def run_validator(self, schema, input_text, *extra):
|
||||||
|
schema_path = self.write("schema.json", schema)
|
||||||
|
input_path = self.write("input.jsonl", input_text)
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), schema_path, input_path, *extra],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
return proc
|
||||||
|
|
||||||
|
def out_lines(self, proc):
|
||||||
|
return [ln for ln in proc.stdout.splitlines() if ln.strip()]
|
||||||
|
|
||||||
|
# --- exit-code contract: 0 valid, 1 bad rows, 2 the run itself failed ---
|
||||||
|
def test_valid_records_exit_0(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1}\n{"id": 2, "name": "a"}\n')
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stderr)
|
||||||
|
self.assertIn("checked 2 records, 0 invalid", proc.stdout)
|
||||||
|
|
||||||
|
def test_invalid_record_exit_1(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1}\n{"id": "x"}\n')
|
||||||
|
self.assertEqual(proc.returncode, 1, proc.stderr)
|
||||||
|
self.assertIn("checked 2 records, 1 invalid", proc.stdout)
|
||||||
|
self.assertIn("field 'id': expected int, got str", proc.stdout)
|
||||||
|
|
||||||
|
def test_missing_input_file_exit_2(self):
|
||||||
|
schema_path = self.write("schema.json", SCHEMA)
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), schema_path, str(self.dir / "nope.jsonl")],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("input error", proc.stderr)
|
||||||
|
|
||||||
|
def test_missing_schema_file_exit_2(self):
|
||||||
|
input_path = self.write("input.jsonl", '{"id": 1}\n')
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), str(self.dir / "nope.json"), input_path],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("schema error", proc.stderr)
|
||||||
|
|
||||||
|
def test_undecodable_input_exit_2_without_traceback(self):
|
||||||
|
"""Q104's core defect: bytes that are not UTF-8 are an input error, not bad rows."""
|
||||||
|
schema_path = self.write("schema.json", SCHEMA)
|
||||||
|
input_path = self.write("input.jsonl", b'\xff\xfe\n')
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), schema_path, input_path],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertNotIn("Traceback", proc.stderr)
|
||||||
|
self.assertIn("input error", proc.stderr)
|
||||||
|
self.assertNotIn("invalid", proc.stdout.splitlines()[-1] if proc.stdout else "",
|
||||||
|
"a run that could not read the file must not report a record verdict")
|
||||||
|
|
||||||
|
def test_undecodable_after_valid_records_is_still_exit_2(self):
|
||||||
|
schema_path = self.write("schema.json", SCHEMA)
|
||||||
|
input_path = self.write("input.jsonl", b'{"id": 1}\n\xff\n')
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), schema_path, input_path],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertNotIn("Traceback", proc.stderr)
|
||||||
|
|
||||||
|
def test_malformed_schema_json_exit_2(self):
|
||||||
|
proc = self.run_validator("{not json", '{"id": 1}\n')
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("schema error", proc.stderr)
|
||||||
|
|
||||||
|
def test_unknown_type_exit_2(self):
|
||||||
|
proc = self.run_validator({"fields": {"a": "bogus"}}, '{"a": 1}\n')
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("unknown type 'bogus'", proc.stderr)
|
||||||
|
|
||||||
|
def test_non_object_schema_exit_2(self):
|
||||||
|
proc = self.run_validator([1, 2], '{"id": 1}\n')
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("schema must be an object", proc.stderr)
|
||||||
|
|
||||||
|
def test_required_not_list_of_strings_exit_2(self):
|
||||||
|
proc = self.run_validator({"required": "id"}, '{"id": 1}\n')
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
|
||||||
|
def test_max_errors_zero_is_usage_error(self):
|
||||||
|
"""--max-errors 0 used to grade nothing and still claim '(truncated)'."""
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": "x"}\n', "--max-errors", "0")
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("must be >= 1", proc.stderr)
|
||||||
|
self.assertNotIn("truncated", proc.stdout)
|
||||||
|
|
||||||
|
def test_max_errors_negative_is_usage_error(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": "x"}\n', "--max-errors", "-3")
|
||||||
|
self.assertEqual(proc.returncode, 2, proc.stdout + proc.stderr)
|
||||||
|
|
||||||
|
# --- schema semantics --------------------------------------------------
|
||||||
|
def test_required_null_reports_one_error_not_two(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": null}\n')
|
||||||
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
lines = self.out_lines(proc)
|
||||||
|
self.assertEqual(len(lines), 2, lines) # one error + the summary
|
||||||
|
self.assertIn("missing required field 'id'", lines[0])
|
||||||
|
self.assertNotIn("null not allowed", proc.stdout,
|
||||||
|
"the same fact must not be reported twice")
|
||||||
|
|
||||||
|
def test_optional_field_null_is_allowed(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1, "name": null, "tags": null}\n')
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||||
|
|
||||||
|
def test_optional_field_missing_is_allowed(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1}\n')
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||||
|
|
||||||
|
def test_non_optional_field_null_is_one_error(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1, "note": null}\n')
|
||||||
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
self.assertEqual(self.out_lines(proc)[0], "line 1: field 'note': null not allowed")
|
||||||
|
|
||||||
|
def test_required_field_missing_is_error(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"name": "a"}\n')
|
||||||
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
self.assertIn("missing required field 'id'", proc.stdout)
|
||||||
|
|
||||||
|
def test_bool_is_not_an_int(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": true}\n')
|
||||||
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
self.assertIn("expected int, got bool", proc.stdout)
|
||||||
|
|
||||||
|
def test_float_accepts_int(self):
|
||||||
|
"""JSON has one number type; an int satisfies float (pinned, documented)."""
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1, "score": 3}\n')
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||||
|
|
||||||
|
def test_float_field_rejects_string(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1, "score": "3"}\n')
|
||||||
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
self.assertIn("field 'score': expected float, got str", proc.stdout)
|
||||||
|
self.assertNotIn("float?", proc.stdout,
|
||||||
|
"the '?' marks absence-allowed, not the type of a present value")
|
||||||
|
|
||||||
|
def test_non_object_record_is_rejected(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '[1, 2]\n')
|
||||||
|
self.assertEqual(proc.returncode, 1)
|
||||||
|
self.assertIn("line 1: record is not an object", proc.stdout)
|
||||||
|
|
||||||
|
def test_blank_lines_are_skipped_and_uncounted(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '\n{"id": 1}\n\n \n{"id": 2}\n\n')
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("checked 2 records, 0 invalid", proc.stdout)
|
||||||
|
|
||||||
|
def test_utf8_bom_is_tolerated(self):
|
||||||
|
schema_path = self.write("schema.json", SCHEMA)
|
||||||
|
input_path = self.write("input.jsonl", b'\xef\xbb\xbf{"id": 1}\n')
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), schema_path, input_path],
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||||
|
self.assertIn("checked 1 records, 0 invalid", proc.stdout)
|
||||||
|
|
||||||
|
def test_extra_fields_are_allowed(self):
|
||||||
|
proc = self.run_validator(SCHEMA, '{"id": 1, "unlisted": {"deep": [1]}}\n')
|
||||||
|
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
|
||||||
|
|
||||||
|
def test_truncated_flag_only_when_truncated(self):
|
||||||
|
two_bad = '{"id": "x"}\n{"id": "y"}\n'
|
||||||
|
cut = self.run_validator(SCHEMA, two_bad, "--max-errors", "1")
|
||||||
|
self.assertEqual(cut.returncode, 1)
|
||||||
|
self.assertIn("(truncated)", cut.stdout)
|
||||||
|
whole = self.run_validator(SCHEMA, two_bad, "--max-errors", "50")
|
||||||
|
self.assertEqual(whole.returncode, 1)
|
||||||
|
self.assertNotIn("(truncated)", whole.stdout)
|
||||||
|
self.assertIn("checked 2 records, 2 invalid", whole.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main(verbosity=2)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
src = "/root/troupe-standup/repo_tree.tsv"
|
||||||
|
lines = open(src, encoding="utf-8").read().splitlines()
|
||||||
|
print("total lines:", len(lines))
|
||||||
|
for i, l in enumerate(lines, 1):
|
||||||
|
if i >= 134:
|
||||||
|
print(i, repr(l[:80]), "...", repr(l[-40:]))
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# CHECK -- sol: probe-set verification verdict (forge: verify lane).
|
||||||
|
# Exit 0 iff the committed classification verifies: every untracked probe file
|
||||||
|
# re-derives health==ready via py_compile, the manifest matches the re-derived
|
||||||
|
# healths, the manifest item set is exactly the 4 candidates, the artifact's
|
||||||
|
# Markdown table is self-consistent, and the authoritative measure script exits 0.
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" # wizard-work/sol -> repo root
|
||||||
|
cd "$REPO_ROOT" || exit 2
|
||||||
|
|
||||||
|
exec python3 wizard-work/sol/verify_probe_set_2026_10_31.py
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import py_compile
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
files = [
|
||||||
|
"replies/q60_truncated_census.py",
|
||||||
|
"replies/q93_axis_negative_probe.py",
|
||||||
|
"replies/test_jsonl_validate.py",
|
||||||
|
"tools/inspect_tree.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
for rel in files:
|
||||||
|
p = Path(rel)
|
||||||
|
try:
|
||||||
|
py_compile.compile(str(p), doraise=True)
|
||||||
|
health = "ready"
|
||||||
|
except Exception as e:
|
||||||
|
health = "broken"
|
||||||
|
lines = sum(1 for _ in p.open(encoding="utf-8"))
|
||||||
|
bytes_ = p.stat().st_size
|
||||||
|
print(f"{rel}\t{health}\t{lines}\t{bytes_}")
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Probe-set verification — 2026-10-31
|
||||||
|
|
||||||
|
Independent re-derivation of the health of the four untracked probe files,
|
||||||
|
measured from the real files, not inferred.
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
Each file was:
|
||||||
|
|
||||||
|
1. compiled under `python3 -c "import py_compile; py_compile.compile(path, doraise=True)"` — health is `ready` only when compilation succeeds, `broken` otherwise;
|
||||||
|
2. counted with `wc -l` / `wc -c` against the committed manifest.
|
||||||
|
|
||||||
|
## Measured result
|
||||||
|
|
||||||
|
| File | Role | Health | Lines | Bytes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `replies/q60_truncated_census.py` | census_tool | ready | 471 | 21767 |
|
||||||
|
| `replies/q93_axis_negative_probe.py` | verification_probe | ready | 129 | 5612 |
|
||||||
|
| `replies/test_jsonl_validate.py` | test_contract | ready | 237 | 10740 |
|
||||||
|
| `tools/inspect_tree.py` | scoping | ready | 8 | 249 |
|
||||||
|
|
||||||
|
## Verdict
|
||||||
|
|
||||||
|
- ready: 4
|
||||||
|
- broken: 0
|
||||||
|
- blocked: 0
|
||||||
|
|
||||||
|
`python3 wizard-work/sol/_measure_probes.py` (this session) printed the measured
|
||||||
|
table above; all four files are `ready` (py_compile clean).
|
||||||
|
|
||||||
|
## Trace
|
||||||
|
|
||||||
|
Command: `python3 wizard-work/sol/_measure_probes.py` → exit 0 with the table
|
||||||
|
in the previous section. No candidate needed an edit.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""verify_probe_set_2026_10_31.py
|
||||||
|
|
||||||
|
Independent verifier for wizard-work/sol/probe-set-verification-2026-10-31.md.
|
||||||
|
|
||||||
|
Re-derives health of the 4 probe files from the real files (py_compile), checks
|
||||||
|
every re-derived health against the committed manifest, and checks the measure
|
||||||
|
record inside the artifact's Markdown table is self-consistent. Exit code IS
|
||||||
|
the verdict: 0 = correct, nonzero = wrong.
|
||||||
|
|
||||||
|
Run: python3 verify_probe_set_2026_10_31.py
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import py_compile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
REPO_ROOT = HERE.parent.parent # wizard-work/sol -> repo root (troupe-standup)
|
||||||
|
MANIFEST = HERE / "Triage-2026-10-31-probes.json"
|
||||||
|
ARTIFACT = HERE / "probe-set-verification-2026-10-31.md"
|
||||||
|
MEASURE = HERE / "_measure_probes.py"
|
||||||
|
|
||||||
|
CANDIDATES = [
|
||||||
|
"replies/q60_truncated_census.py",
|
||||||
|
"replies/q93_axis_negative_probe.py",
|
||||||
|
"replies/test_jsonl_validate.py",
|
||||||
|
"tools/inspect_tree.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def re_derive_health(rel: str) -> str:
|
||||||
|
p = REPO_ROOT / rel
|
||||||
|
try:
|
||||||
|
py_compile.compile(str(p), doraise=True)
|
||||||
|
return "ready"
|
||||||
|
except py_compile.PyCompileError:
|
||||||
|
return "broken"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# 1. Re-derive health from the real files.
|
||||||
|
rederived = {rel: re_derive_health(rel) for rel in CANDIDATES}
|
||||||
|
for rel, h in rederived.items():
|
||||||
|
if h != "ready":
|
||||||
|
errors.append(f"{rel}: re-derived health={h!r}")
|
||||||
|
|
||||||
|
# 2. Every candidate file present and matches the manifest's health.
|
||||||
|
if not MANIFEST.is_file():
|
||||||
|
print("FAIL: manifest missing", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||||
|
by_path = {it.get("path"): it for it in manifest.get("items", [])}
|
||||||
|
for rel in CANDIDATES:
|
||||||
|
if rel not in by_path:
|
||||||
|
errors.append(f"manifest missing item: {rel}")
|
||||||
|
elif by_path[rel].get("health") != rederived[rel]:
|
||||||
|
errors.append(f"{rel}: manifest health={by_path[rel].get('health')!r}, "
|
||||||
|
f"re-derived={rederived[rel]!r}")
|
||||||
|
|
||||||
|
# 3. The committed manifest item set is exactly the 4 candidates.
|
||||||
|
committed = {it.get("path") for it in manifest.get("items", [])}
|
||||||
|
if committed != set(CANDIDATES):
|
||||||
|
errors.append(f"manifest item set {sorted(committed)} != {sorted(CANDIDATES)}")
|
||||||
|
|
||||||
|
# 4. The measure record in the artifact's markdown table must match the
|
||||||
|
# re-derived healths (self-consistency of the artifact).
|
||||||
|
if not ARTIFACT.is_file():
|
||||||
|
errors.append(f"artifact missing: {ARTIFACT}")
|
||||||
|
else:
|
||||||
|
text = ARTIFACT.read_text(encoding="utf-8")
|
||||||
|
for rel, h in rederived.items():
|
||||||
|
marker = f"| `{rel}`"
|
||||||
|
if marker not in text:
|
||||||
|
errors.append(f"artifact table missing row for {rel}")
|
||||||
|
if "ready: 4" not in text:
|
||||||
|
errors.append("artifact verdict missing 'ready: 4'")
|
||||||
|
|
||||||
|
# 5. The authoritative measurement command (next to this script) exits 0.
|
||||||
|
if not MEASURE.is_file():
|
||||||
|
errors.append(f"measure script missing: {MEASURE}")
|
||||||
|
else:
|
||||||
|
proc = subprocess.run(
|
||||||
|
[sys.executable, str(MEASURE)], cwd=str(REPO_ROOT),
|
||||||
|
capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
errors.append("measure script exited nonzero")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print("VERDICT: FAILED")
|
||||||
|
for e in errors:
|
||||||
|
print(" -", e)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("VERDICT: PASS")
|
||||||
|
print("re-derived health: all 4 probe files READY (py_compile clean)")
|
||||||
|
print("committed manifest: 4/4 health match; artifact table self-consistent")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in new issue
Block a user