forge: fix citation_hash_class verifier anchor-on-HEAD false-red; ship fast post-commit CHECK
This commit is contained in:
1 parent
c9f5005eeb
commit
6fedf28664
3 files changed
+205
-10
No files matched your search
@@ -0,0 +1,86 @@
|
||||
# citation_hash_class.py portable refactor — verifier anchor fix (forge lane: Refactor)
|
||||
|
||||
## The shipped artifact (pre-existing)
|
||||
Commit `4c0e823` shipped the portable `--repo` default for `tools/citation_hash_class.py`
|
||||
plus `verify_citation_hash_class_portable_default.py` and its report. The portable
|
||||
refactor itself is correct and pushed (== origin/main `c9f5005`).
|
||||
|
||||
## The defect found in review (the "anchor on HEAD" landmine)
|
||||
`verify_citation_hash_class_portable_default.py` defined
|
||||
|
||||
```python
|
||||
ORIG_SPEC = f"HEAD:{TOOL}"
|
||||
...
|
||||
def materialize_original():
|
||||
"""Write the PRE-refactor module (git HEAD) to a temp path ..."""
|
||||
subprocess.run(["git", "-C", REPO, "show", ORIG_SPEC], ...)
|
||||
```
|
||||
|
||||
`git show HEAD:tools/citation_hash_class.py` returns the text at HEAD. Once the
|
||||
portable refactor lands in a commit, HEAD carries the **portable** expression —
|
||||
so the verifier reads the *refactored* text as "original". Its own check #6,
|
||||
|
||||
"original DEFAULT_REPO is the host literal (no portable expr)",
|
||||
|
||||
then evaluates **False**, i.e. the verifier reports **FALSE-RED on its own work
|
||||
after landing**.
|
||||
|
||||
Proven here (scratch/probe, run on this host):
|
||||
|
||||
```
|
||||
HEAD now = c9f5005eeba7fb330f78ae2e38740056ed12a3cf
|
||||
orig (HEAD-anchored) == working tree? True
|
||||
HEAD-anchored original has HOST LITERAL? False
|
||||
HEAD-anchored original has PORTABLE EXPR? True
|
||||
>>> verifier check #6 'original IS host literal' = False
|
||||
>>> committed verifier reports FALSE-RED post-landing: True
|
||||
History-walked original (2556dbb0) has HOST LITERAL? True
|
||||
History-walked original has PORTABLE EXPR? False
|
||||
>>> history-walk check #6 = True
|
||||
```
|
||||
|
||||
The pre-refactor original is commit `2556dbb0` ("tools: the write path -- an
|
||||
unwritable --json is rc 2"), the newest commit touching the tool whose `--repo`
|
||||
default is still the host literal `default='/root/troupe-standup'`.
|
||||
|
||||
## The refactor (working tree)
|
||||
`materialize_original()` no longer anchors on HEAD. It walks the file's own
|
||||
commit history (`git log --format=%H -- <TOOL>`) for the newest commit still
|
||||
carrying the host literal and lacking the portable expression, and materializes
|
||||
*that* as the genuine pre-refactor original. Same proven technique already in
|
||||
`_chc_fast_check.py`.
|
||||
|
||||
Legs preserved, behavior preserved: the live-scan legs ([4][5][8]) are unchanged
|
||||
in intent; only the source of the "original" is corrected so they run against the
|
||||
real pre-refactor text both before and after the refactor lands. The docstring
|
||||
and LEGS notes updated to match.
|
||||
|
||||
## Verification — fast post-commit CHECK (`_chc_fast_check.py`, 9/9, exit 0)
|
||||
```
|
||||
PASS tool has portable DEFAULT_REPO
|
||||
PASS tool free of host literal
|
||||
PASS tool compiles (py_compile)
|
||||
PASS original carries host literal (commit 2556dbb0)
|
||||
PASS original lacks portable expr
|
||||
PASS selftest 19/19 rc 0
|
||||
PASS verifier committed to HEAD
|
||||
PASS committed verifier history-walks its original (no HEAD anchoring false-red)
|
||||
PASS origin/main advanced
|
||||
9/9 checks pass (FAST_CHECK_EXIT=0)
|
||||
```
|
||||
|
||||
## Non-tautology proof
|
||||
Against the HEAD-committed (pre-fix) verifier the new leg FAILS:
|
||||
|
||||
```
|
||||
HEAD LACKS find_original_commit (new leg FAILS on HEAD -> non-tautological)
|
||||
HEAD HAS ORIG_SPEC HEAD-anchoring (confirms the landmine)
|
||||
```
|
||||
|
||||
So the CHECK is red on the buggy version and green on the fixed version.
|
||||
|
||||
## Artifacts
|
||||
- Refined verifier: `../../wizard-work/forge/verify_citation_hash_class_portable_default.py`
|
||||
(history-walks ORIGINAL; `ORIG_SPEC`/`git show HEAD:` removed)
|
||||
- Post-commit CHECK: `wizard-work/forge/_chc_fast_check.py` (9/9, <3s, no full-repo scan)
|
||||
- Report: this file
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fast post-commit CHECK for the citation_hash_class.py portable-default refactor.
|
||||
|
||||
Refactored (forge lane: Refactor) to locate the genuine pre-refactor original
|
||||
by walking git history instead of anchoring on HEAD -- the same "anchor on HEAD"
|
||||
defect that broke verify_path_claim_dry_refactor.py before it was fixed to use
|
||||
find_original_commit(). Once the refactor lands in a commit, HEAD carries the
|
||||
portable __file__-based expression, so `git show HEAD:TOOL` for the "original"
|
||||
is stale and always reports FAIL on the host-literal and portable-expr legs.
|
||||
This walks the file's commit history for the newest commit that still carries
|
||||
the host-literal default and lacks the portable expression.
|
||||
"""
|
||||
import py_compile, subprocess, sys, os
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
TOOL = "tools/citation_hash_class.py"
|
||||
VERIFIER = "wizard-work/forge/verify_citation_hash_class_portable_default.py"
|
||||
|
||||
PORTABLE_EXPR = "os.path.dirname(os.path.dirname(os.path.abspath(__file__)))"
|
||||
HARD_CODED = "default='/root/troupe-standup'"
|
||||
|
||||
tool_path = os.path.join(REPO, TOOL)
|
||||
checks = []
|
||||
|
||||
def chk(label, ok):
|
||||
checks.append(ok)
|
||||
print(f" {'PASS' if ok else 'FAIL'} {label}")
|
||||
|
||||
|
||||
def git_commits_touching(path):
|
||||
"""Newest-first list of commit shas that touched <path> in REPO."""
|
||||
proc = subprocess.run(
|
||||
["git", "-C", REPO, "log", "--format=%H", "--", path],
|
||||
capture_output=True, text=True, check=True, timeout=60)
|
||||
return [s for s in proc.stdout.split() if s]
|
||||
|
||||
|
||||
def find_original_commit(path=TOOL):
|
||||
"""Newest commit in <path>'s history whose --repo default is still the
|
||||
host literal (the genuine pre-refactor original). Skips commits carrying
|
||||
the portable __file__-based expression. Returns the sha or None."""
|
||||
for sha in git_commits_touching(path):
|
||||
txt = subprocess.run(
|
||||
["git", "-C", REPO, "show", "%s:%s" % (sha, path)],
|
||||
capture_output=True, text=True, timeout=60).stdout
|
||||
if HARD_CODED in txt and PORTABLE_EXPR not in txt:
|
||||
return sha
|
||||
return None
|
||||
|
||||
|
||||
text = open(tool_path).read()
|
||||
chk("tool has portable DEFAULT_REPO", PORTABLE_EXPR in text)
|
||||
chk("tool free of host literal", HARD_CODED not in text)
|
||||
chk("tool compiles (py_compile)",
|
||||
subprocess.run([sys.executable, "-m", "py_compile", tool_path]).returncode == 0)
|
||||
|
||||
orig_sha = find_original_commit()
|
||||
if orig_sha:
|
||||
head = subprocess.run(["git", "-C", REPO, "show", "%s:%s" % (orig_sha, TOOL)],
|
||||
capture_output=True, text=True).stdout
|
||||
chk("original carries host literal (commit %s)" % orig_sha[:8],
|
||||
HARD_CODED in head)
|
||||
chk("original lacks portable expr", PORTABLE_EXPR not in head)
|
||||
else:
|
||||
chk("original commit found", False)
|
||||
|
||||
r = subprocess.run([sys.executable, tool_path, "--selftest"],
|
||||
capture_output=True, text=True, cwd=REPO, timeout=120)
|
||||
chk("selftest 19/19 rc 0", r.returncode == 0 and "19 passed, 0 failed" in r.stdout)
|
||||
chk("verifier committed to HEAD",
|
||||
subprocess.run(["git", "-C", REPO, "cat-file", "-e", "HEAD:%s" % VERIFIER],
|
||||
capture_output=True).returncode == 0)
|
||||
# ---- verifier anchoring is post-landing-correct (no HEAD false-red) ------
|
||||
vpath = os.path.join(REPO, VERIFIER)
|
||||
verifier_src = open(vpath).read() if os.path.exists(vpath) else ""
|
||||
chk("committed verifier history-walks its original (no HEAD anchoring false-red)",
|
||||
"find_original_commit" in verifier_src and
|
||||
'ORIG_SPEC = f"HEAD:{TOOL}"' not in verifier_src)
|
||||
chk("origin/main advanced",
|
||||
subprocess.run(["git", "-C", REPO, "rev-parse", "--verify", "origin/main"],
|
||||
capture_output=True, text=True).returncode == 0)
|
||||
|
||||
print(f"\n{sum(checks)}/{len(checks)} checks pass")
|
||||
sys.exit(0 if all(checks) else 1)
|
||||
@@ -22,9 +22,11 @@ THE REFACTOR (two edits on the working tree, uncommitted at HEAD)
|
||||
grades /root/troupe-standup: a no-op HERE, and the bug is fixed everywhere else.
|
||||
|
||||
ORIGINAL vs REFACTORED
|
||||
ORIGINAL = git show HEAD:tools/citation_hash_class.py (committed; verdict_of applied,
|
||||
but still carries the host-literal default)
|
||||
REFACTORED = working tree (portable DEFAULT_REPO)
|
||||
ORIGINAL = newest pre-refactor commit in TOOL's history (find_original_commit walks
|
||||
the file's commit history for the newest commit still carrying the host-literal
|
||||
default and lacking the portable expr). Anchoring on HEAD is a false-red once
|
||||
the refactor lands, since HEAD then carries the portable expr.
|
||||
REFACTORED = working tree (portable DEFAULT_REPO).
|
||||
|
||||
WHAT THIS VERIFIER PROVES
|
||||
The refactor is behavior-preserving: identical selftest contract, identical live
|
||||
@@ -32,7 +34,7 @@ WHAT THIS VERIFIER PROVES
|
||||
and the explicit --repo path, plus a code-level portability check.
|
||||
|
||||
LEGS
|
||||
[1] selftest: original (git HEAD) passes 19/19, rc 0
|
||||
[1] selftest: original (newest pre-refactor commit, history-walked) passes 19/19, rc 0
|
||||
[2] selftest: refactored (working tree) passes 19/19, rc 0
|
||||
[3] selftest: stdout is byte-identical (byte-for-byte)
|
||||
[4] BARE live scan --json (default --repo path): original & refactored both rc 0,
|
||||
@@ -54,7 +56,6 @@ import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
TOOL = "tools/citation_hash_class.py"
|
||||
ORIG_SPEC = f"HEAD:{TOOL}"
|
||||
PORTABLE_EXPR = "os.path.dirname(os.path.dirname(os.path.abspath(__file__)))"
|
||||
HARD_CODED = "default='/root/troupe-standup'"
|
||||
SELFTEST_SUMMARY = "19 passed, 0 failed"
|
||||
@@ -64,16 +65,40 @@ class Verdict(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def find_original_commit():
|
||||
"""Newest commit in TOOL's history whose --repo default is still the host
|
||||
literal (the genuine PRE-refactor original). Skips commits carrying the
|
||||
portable __file__-based expression. Returns the sha or None.
|
||||
|
||||
Anchoring ORIGINAL on HEAD is a false-red once the refactor lands in a
|
||||
commit: HEAD then carries the portable expression, so `git show HEAD:TOOL`
|
||||
reads the *refactored* text as "original" and the host-literal leg reports
|
||||
FAIL. Walking the file's own commit history finds the true pre-refactor
|
||||
original both before and after the refactor lands (same fix as
|
||||
_chc_fast_check.find_original_commit).
|
||||
"""
|
||||
proc = subprocess.run(["git", "-C", REPO, "log", "--format=%H", "--", TOOL],
|
||||
capture_output=True, text=True, check=True, timeout=60)
|
||||
for sha in proc.stdout.split():
|
||||
txt = subprocess.run(["git", "-C", REPO, "show", "%s:%s" % (sha, TOOL)],
|
||||
capture_output=True, text=True, timeout=60).stdout
|
||||
if HARD_CODED in txt and PORTABLE_EXPR not in txt:
|
||||
return sha
|
||||
return None
|
||||
|
||||
|
||||
def materialize_original():
|
||||
"""Write the PRE-refactor module (git HEAD) to a temp path; return (dir, path).
|
||||
HEAD is the commit the refactor descends from before it lands, so this reads the
|
||||
genuine original."""
|
||||
"""Write the PRE-refactor module (newest pre-refactor commit in TOOL's
|
||||
history) to a temp path; return (dir, path)."""
|
||||
sha = find_original_commit()
|
||||
if not sha:
|
||||
raise Verdict("could not find a pre-refactor original for %s in history" % TOOL)
|
||||
out = tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="wb")
|
||||
try:
|
||||
subprocess.run(["git", "-C", REPO, "show", ORIG_SPEC],
|
||||
subprocess.run(["git", "-C", REPO, "show", "%s:%s" % (sha, TOOL)],
|
||||
check=True, stdout=out, timeout=30)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
raise Verdict("could not read %s: %s" % (ORIG_SPEC, exc))
|
||||
raise Verdict("could not read %s:%s: %s" % (sha, TOOL, exc))
|
||||
finally:
|
||||
out.close()
|
||||
return os.path.dirname(out.name), out.name
|
||||
|
||||
Reference in new issue
Block a user