fix(F17): a PDF in a folder is not proof the package can be rebuilt

Build sweep run this session: 48 sources, 46 built, 2 failed. Infineon_AI_Engineer's
cover letter fails on font expansion (the font is not scalable); cv_template.tex
fails on unfilled placeholders, which is expected for the academic legacy path.
Overfull boxes: Microsoft ISE 25.33pt, Isovalent 0.88pt. All historical, all
left alone.

New: resume_builder/helpers/package_manifest.py records, per application
package, what a rebuild would ACTUALLY do - sources, class-file hashes, PDFs
present, and the reasons a rebuild would not reproduce the submitted document.
It is descriptive; nothing in it authorises rewriting a submitted file.

What it found across 21 packages:
- 17 are DATE-DEPENDENT. The cover letters use 	oday, so recompiling a
  historical package silently restamps it with today's date.
- 1 has an INCOMPLETE SOURCE DISTRIBUTION: output/NATO_AI_ENGINEER ships no
  resume.cls. It builds on this machine only because a copy exists elsewhere,
  and the shipped PDF hid that. Not repaired by copying the newest class in -
  the repo carries THREE different resume.cls variants (13/7/1 copies) and older
  macros differ, so that "fix" could silently change a historical document.
- 1 has no PDF at all, so nothing is reviewable without a build.

Classification: 7 application packages were in no reuse category. Added to
historical_revalidate, and the register now records the default explicitly -
anything unlisted is treated as revalidate-before-reuse. Being recent is not the
same as being verified; none of the seven was re-read for this.

Prevention rather than repair: 	oday removed from the cover-letter template so
new packages stop adding to the pile. A test now fails if it comes back, if the
manifest goes stale, or if a package is left unclassified.

Also: scan_document gained a template mode. A placeholder in a TEMPLATE is what
makes it a template; the defect is one surviving into a generated document.

37/37 tests pass; counter-proof still catches all 9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM
This commit is contained in:
dennisthiessenandClaude Opus 5 committed 2026-09-11 20:48:52 +02:00
1 parent c7e8d4e058
commit eecdbdade6
7 files changed
+797 -24

No files matched your search

@@ -218,7 +218,7 @@ def _negated(haystack: str, term: str) -> bool:
return bool(hits) and all(_NEGATION.search(haystack[max(0, hit.start() - 70):hit.start()]) for hit in hits)
def scan_document(path: Path, claims: dict) -> list[str]:
def scan_document(path: Path, claims: dict, template: bool = False) -> list[str]:
"""Deterministic, mechanically checkable properties of a generated document.
This is NOT a truth check and must never be reported as one. It cannot judge whether a
@@ -266,7 +266,9 @@ def scan_document(path: Path, claims: dict) -> list[str]:
# Scan the comment-stripped RAW text: _MARKUP treats "\item [GENERATE: x]" as a command
# with an optional argument and would swallow the placeholder whole.
for match in _PLACEHOLDER.finditer(_COMMENT.sub("", raw)):
# Templates are EXEMPT: an unfilled placeholder is what makes a template a template. The
# defect is a placeholder surviving into a generated document.
for match in ([] if template else _PLACEHOLDER.finditer(_COMMENT.sub("", raw))):
errors.append(f"{path}: unresolved placeholder left in the document: {match.group(0)!r}")
identity = claims.get("identity", {})