forge: verify PDF skill refactor (pdf_create/pdf_make_form -> _pdf_common), fix harness false-green

This commit is contained in:
forge committed 2026-10-21 16:00:47 +00:00
1 parent 6f4bba6a8f
commit dfb33add87
4 files changed
+322

No files matched your search

@@ -0,0 +1,86 @@
# Refactor Verification: PDF skill CLI helpers migrated to `_pdf_common`
## Lane
Refactor — code review and refactors.
## Target
The two reportlab-based CLI builders `pdf_create.py` and `pdf_make_form.py`
(in `/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/`) were the
last holdouts still carrying a duplicated `_reconfigure_stdio()` shim and an
inlined `print(json.dumps({...}))`. Both have been migrated in place to the
shared `_pdf_common` helper module, consistent with the three already-migrated
siblings (`pdf_fill_form.py`, `pdf_secure.py`, `pdf_watermark.py`) and the
module's documented contract ("Eliminates duplication across the 12 CLI helpers").
## What I verified (real execution)
Ran `verify_pdf_refactor.py`, which runs the backed-up original (`before/`)
and the in-place refactored script with the SAME `--output` path and identical
JSON specs, then asserts behavior preservation:
```
=== pdf_create.py ===
[PASS] stdout byte-identical
[PASS] exit code identical
[PASS] stderr identical
[PASS] sut ran real path (not import-error exit 2)
[PASS] pdf page count identical
[PASS] pdf extracted text identical
[PASS] normalized pdf bytes identical
stdout_len orig=95 ref=95 | exit 0/0 | stderr_len 0/0
pdf: pages=2/2 text_eq=True norm_bytes_eq=True
=== pdf_make_form.py ===
[PASS] stdout byte-identical
[PASS] exit code identical
[PASS] stderr identical
[PASS] sut ran real path (not import-error exit 2)
[PASS] pdf page count identical
[PASS] pdf extracted text identical
[PASS] normalized pdf bytes identical
stdout_len orig=298 ref=298 | exit 0/0 | stderr_len 0/0
pdf: pages=1/1 text_eq=True norm_bytes_eq=True
SUMMARY: ALL PASS (exit 0)
```
`stdout_len` is non-empty (95 / 298 bytes) and exit codes are `0/0` — i.e. the
SUT actually executed its PDF-generation + `emit_json` path. reportlab's
per-run `/CreationDate` is stripped via a pypdf re-serialize before hashing.
## The defect I found and fixed (the real work here)
The previous verification was a FALSE GREEN. `verify_pdf_refactor.py` used
`PY = sys.executable`. Launched under the system `python3`
(`/root/.hermes/installs/.../venv/bin/python3`), reportlab and pypdf are NOT
installed, so BOTH the original and the refactored script failed identically at
import (`exit 2`, stderr "Missing dependency..."). The gates then compared
`"" == ""` (empty stdout) -> vacuous PASS. The harness reported success while
never exercising the refactored `emit_json`/`reconfigure_stdio` code paths.
Fix applied to `verify_pdf_refactor.py`:
1. **Self-validating interpreter.** On startup, the harness checks that `PY`
can import `reportlab` and `pypdf`. If not, it globs for a capable hermes
tools `python3.14` and `os.execv`s itself under it; if none is found, it
exits non-zero with a clear message instead of false-greening.
2. **Hard gate against the degenerate case.** Added an explicit
`sut ran real path (not import-error exit 2)` gate that fails if either run
exits 2, and the `dep-present runs to completion (exit 0)` gate now fails
(was a vacuous `True`) when the SUT could not run.
Launched under the dep-less system `python3`, the harness now prints
`[guard] re-execing under .../python3.14 ...` and passes every REAL gate above.
Launched directly under the capable interpreter, it runs the same real gates.
## Interpreter note (evidence)
- `reportlab` 5.0.1 and `pypdf` 6.21.1 are installed under
`/root/.hermes/tools/python-3.14.7+202<redacted>-linux-x64/bin/python3.14`.
- System `python3` (venv) has NEITHER — which is exactly why the original
harness false-greened.
## Files
- `/root/.hermes/wizard-outputs/forge/verify_pdf_refactor.py` (harness, hardened)
- `/root/.hermes/wizard-outputs/forge/before/pdf_create.py` (original backup)
- `/root/.hermes/wizard-outputs/forge/before/pdf_make_form.py` (original backup)
- `/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/pdf_create.py` (refactored)
- `/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/pdf_make_form.py` (refactored)
- `/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/_pdf_common.py` (shared helper)
- This report.
@@ -0,0 +1 @@
[guard] re-execing under /root/.hermes/tools/python-3.14.7+20260901-linux-x64/bin/python3.14 (current /root/.hermes/installs/76f6e7e5145b5f39/environments/c5b879e2036d4c909805dd1858587925/venv/bin/python3 lacks reportlab/pypdf)
@@ -0,0 +1,24 @@
=== pdf_create.py ===
[PASS] stdout byte-identical
[PASS] exit code identical
[PASS] stderr identical
[PASS] sut ran real path (not import-error exit 2)
[PASS] pdf page count identical
[PASS] pdf extracted text identical
[PASS] normalized pdf bytes identical
stdout_len orig=95 ref=95 | exit 0/0 | stderr_len 0/0
pdf: pages=2/2 text_eq=True norm_bytes_eq=True
-> PASS
=== pdf_make_form.py ===
[PASS] stdout byte-identical
[PASS] exit code identical
[PASS] stderr identical
[PASS] sut ran real path (not import-error exit 2)
[PASS] pdf page count identical
[PASS] pdf extracted text identical
[PASS] normalized pdf bytes identical
stdout_len orig=298 ref=298 | exit 0/0 | stderr_len 0/0
pdf: pages=1/1 text_eq=True norm_bytes_eq=True
-> PASS
SUMMARY: ALL PASS
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""Behavior-preservation proof for the pdf_create / pdf_make_form refactor.
Compares the BACKED-UP original (before/) against the refactored in-place script
(skills/.../) with identical inputs. The refactor only touches two code paths:
stdio reconfiguration and JSON emission (both live in _pdf_common). PDF
generation bodies are untouched, so we additionally assert page-count + extracted
text equivalence and byte-identity of the normalized PDF.
The JSON payload embeds the --output path, so both runs are pointed at the SAME
output-path string (run sequentially; each PDF is copied aside before the next
run overwrites it). This makes the embedded `output` field identical across
runs, yielding a fair byte-level stdout comparison.
reportlab embeds a per-run /CreationDate timestamp, so raw PDF bytes differ even
for identical specs; we normalize by stripping volatile Info keys before hashing.
FALSE-GREEN GUARD (the defect this harness previously had):
reportlab and pypdf must be importable in the interpreter running the SUT runs,
otherwise BOTH runs fail identically at import with exit code 2, stdout is empty
for both, and the "stdout byte-identical" gate degenerates to empty==empty ->
a false PASS. To eliminate that, on startup we:
1. confirm `sys.executable` (PY) can import reportlab+pypdf;
2. if not, re-exec ourselves under the first discovered hermes tools
python3.14 that can import both;
3. if none is found, exit non-zero with a clear message (never PASS).
Exits 0 only if every hard gate passes.
"""
from __future__ import annotations
import glob
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
CREATE_ORIG = Path("/root/.hermes/wizard-outputs/forge/before/pdf_create.py")
CREATE_REF = Path("/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/pdf_create.py")
FORM_ORIG = Path("/root/.hermes/wizard-outputs/forge/before/pdf_make_form.py")
FORM_REF = Path("/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/pdf_make_form.py")
CREATE_SPEC = {
"title": "Ref Test", "author": "forge",
"page_size": "A4", "page_numbers": True,
"elements": [
{"type": "heading", "text": "Section 1", "level": 1},
{"type": "paragraph", "text": "Hello world."},
{"type": "table", "rows": [["H1", "H2"], ["a", "b"]], "header": True},
{"type": "pagebreak"},
{"type": "paragraph", "text": "Second page text."},
],
}
FORM_SPEC = {
"title": "Intake", "author": "forge", "page_size": "A4", "page_count": 1,
"fields": [
{"name": "surname", "type": "text", "page": 1, "label": "Surname",
"label_box": [72, 700, 150, 714], "entry_box": [160, 696, 400, 716],
"value": "Smith", "tooltip": "Family name"},
{"name": "agree", "type": "checkbox", "page": 1, "label": "I agree",
"label_box": [72, 660, 150, 674], "entry_box": [160, 658, 176, 674],
"checked": True},
{"name": "color", "type": "radio", "page": 1, "label": "Color",
"label_box": [72, 620, 150, 634], "entry_box": [160, 616, 400, 636],
"options": ["red", "blue"], "value": "red"},
{"name": "size", "type": "dropdown", "page": 1, "label": "Size",
"label_box": [72, 580, 150, 594], "entry_box": [160, 576, 300, 596],
"options": ["small", "large"], "value": "small"},
],
}
def _has_deps(interp: str) -> bool:
"""True iff *interp* can import reportlab and pypdf (the SUT's runtime deps)."""
return subprocess.run(
[interp, "-c", "import reportlab, pypdf"],
capture_output=True,
).returncode == 0
def _ensure_capable_interpreter() -> None:
"""Re-exec under an interpreter that has reportlab+pypdf, or fail loudly.
This is the fix for the prior false-green: without it, launching under an
interpreter lacking the deps made every SUT run fail at import (exit 2) and
every gate a vacuous truth.
"""
if _has_deps(sys.executable):
return
candidates = sorted(glob.glob("/root/.hermes/tools/python-*linux-x64/bin/python3.14"))
for c in candidates:
if _has_deps(c):
print(f"[guard] re-execing under {c} (current {sys.executable} lacks reportlab/pypdf)",
file=sys.stderr)
os.execv(c, [c, *sys.argv])
print("[guard] ERROR: no interpreter with reportlab+pypdf found "
"(cannot run SUT gates). Aborting instead of false-greening.",
file=sys.stderr)
sys.exit(2)
_ensure_capable_interpreter()
PY = sys.executable # now guaranteed to import reportlab+pypdf
def _run(script: Path, spec_path: Path, out_pdf: Path):
return subprocess.run(
[PY, str(script), str(spec_path), "-o", str(out_pdf)],
capture_output=True, text=True, timeout=120,
)
def normalize_pdf_bytes(path: Path) -> bytes:
"""Re-serialize via pypdf and strip volatile Info keys -> deterministic bytes."""
from pypdf import PdfReader, PdfWriter
r = PdfReader(str(path))
w = PdfWriter()
w.append(r)
info = w._root_object["/Info"] if "/Info" in w._root_object else None
if info is not None:
for k in ("/CreationDate", "/ModDate", "/Producer", "/Creator"):
try:
del info[k]
except KeyError:
pass
fd, tmp = tempfile.mkstemp(suffix=".pdf")
os.close(fd)
try:
with open(tmp, "wb") as f:
w.write(f)
return Path(tmp).read_bytes()
finally:
Path(tmp).unlink(missing_ok=True)
def pdf_text_and_pages(path: Path):
from pypdf import PdfReader
r = PdfReader(str(path))
pages = len(r.pages)
text = "".join((p.extract_text() or "") for p in r.pages)
return pages, text
def check(label, orig_script, ref_script, spec):
work = Path(tempfile.mkdtemp())
spec_path = work / "spec.json"
spec_path.write_text(json.dumps(spec), encoding="utf-8")
out_pdf = work / "result.pdf" # SAME path string used by both runs
or_ = _run(orig_script, spec_path, out_pdf)
orig_pdf = work / "orig.pdf"
if out_pdf.exists():
shutil.copyfile(out_pdf, orig_pdf)
out_pdf.unlink()
rr = _run(ref_script, spec_path, out_pdf)
ref_pdf = work / "ref.pdf"
if out_pdf.exists():
shutil.copyfile(out_pdf, ref_pdf)
out_pdf.unlink()
gates = [
("stdout byte-identical", or_.stdout == rr.stdout),
("exit code identical", or_.returncode == rr.returncode),
("stderr identical", or_.stderr == rr.stderr),
]
# HARD gate: the SUT must actually run its PDF path, not fail at import.
if or_.returncode == 2 or rr.returncode == 2:
gates.append(("sut ran real path (not import-error exit 2)", False))
else:
gates.append(("sut ran real path (not import-error exit 2)", True))
notes = []
if or_.returncode == 0 and rr.returncode == 0 and orig_pdf.exists() and ref_pdf.exists():
op, ot = pdf_text_and_pages(orig_pdf)
rp, rt = pdf_text_and_pages(ref_pdf)
gates.append(("pdf page count identical", op == rp))
gates.append(("pdf extracted text identical", ot == rt))
nb_eq = normalize_pdf_bytes(orig_pdf) == normalize_pdf_bytes(ref_pdf)
gates.append(("normalized pdf bytes identical", nb_eq))
notes.append(f"pages={op}/{rp} text_eq={ot==rt} norm_bytes_eq={nb_eq}")
else:
gates.append(("dep-present runs to completion (exit 0)", False))
print(f"=== {label} ===")
for name, ok in gates:
print(f" [{'PASS' if ok else 'FAIL'}] {name}")
print(f" stdout_len orig={len(or_.stdout)} ref={len(rr.stdout)} | "
f"exit {or_.returncode}/{rr.returncode} | stderr_len {len(or_.stderr)}/{len(rr.stderr)}")
if notes:
print(f" pdf: {' '.join(notes)}")
ok_exit2 = or_.returncode == 2 or rr.returncode == 2
all_ok = all(ok for _, ok in gates) and not ok_exit2
print(f" -> {'PASS' if all_ok else 'FAIL'}")
return all_ok
def main() -> int:
ok = True
ok &= check("pdf_create.py", CREATE_ORIG, CREATE_REF, CREATE_SPEC)
ok &= check("pdf_make_form.py", FORM_ORIG, FORM_REF, FORM_SPEC)
print("\nSUMMARY:", "ALL PASS" if ok else "FAILURES DETECTED")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())