Files
claude-resume-kit/tests/test_document_scanner.py
dennisthiessenandClaude Opus 5 fc5f3e49ba fix: review findings F03-F06 — quotas, rule hierarchy and a validator that checks
F03 — application records
- Quota already removed with F02's tracker; verified uncapped and that
  "interview reached" survives a later withdrawal.
- Aker BP: application date confirmed 2026-07-29 by the user, settling the
  one-day conflict. Its Adjacent-despite-79 classification is kept, NOT
  reclassified: the user does not remember the rationale and judged it
  immaterial on a closed role, and silently reclassifying to make the
  arithmetic tidy is the exact objection the review raised. Recorded as an
  explicit override reason with a rubric version instead.

F04 — rule hierarchy
- Three levels stated in AGENTS.md and CLAUDE.md: immutable evidence rules,
  standing user preferences, market/employer presentation.
- Deliverables separated from QA. The .tex stays the artefact of record; you
  still compile, into the application's own output/ folder, because the user
  cannot approve what he cannot open.
- `resume_reference.md` said "Capgemini/Bundeswehr: include only when directly
  relevant", contradicting the standing never-include rule. Split and fixed.

F05 — a green validator now means something
- Per-claim `forbidden` wording is enforced. 14 of 26 claims carried such a
  list and NONE of it was checked, which is why "owned the full ML lifecycle"
  passed. Matching tolerates inserted adjectives: the real regression read
  "Built a decentralized Data Mesh" and one word defeated the literal list.
- Added identity (e-mail/phone), employer-date, placeholder and
  document-exclusion checks. Capgemini's omission rule now has a canonical
  home in claims.json instead of living only in memory and config.md.
- Registered Groovy and Snowflake as forbidden skills. Both were absent from
  claims.json entirely, so "forbidden" existed in prose with nothing enforcing
  it.
- PASS no longer reads as a truth certificate: it prints what it did not check
  — scope implications, bullet-to-claim support, whether a number is real.

F06 — the scanner stopped fighting the documents
- LaTeX comments are no longer claims; `% never claim Terraform` was a finding
  before, which taught the workflow to write around the checker.
- Markup, accent macros and umlauts are normalized, so `\textbf{Snow}flake`
  cannot smuggle a forbidden skill past a substring test and
  `Universit\"at der Bundeswehr M\"unchen` is recognisably a school.
- Honest denials pass in English and German.
- The triple-list cadence hint knew only English "and", so it silently did
  nothing on every German package. Advisory only, and now labelled as such.

Verification
- The six counter-proofs from the report are caught: 9 errors on one synthetic
  document, where the old scanner found none.
- All six packages built under current rules scan clean — SBB, NVIDIA, RUAG,
  Kdo Cy, Citadel, Aker BP. Four false positives of my own were found and fixed
  on the way there; each is now a regression test.
- 13 older packages do report findings. Those look like genuine historical
  violations and are deliberately NOT fixed here: F17 governs archives and they
  must not be silently rewritten.
- `tests/test_document_scanner.py` added. Full suite 31/31 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM
2026-09-11 19:33:24 +02:00

109 lines
4.6 KiB
Python

"""Negative regression tests for the document scanner (review findings F05 and F06).
Each case below is a counter-proof from `output/Project_Review_2026-09-11/project_review.tex`
that the scanner accepted before 2026-09-11. The point of these tests is that a green
validator run means something; they are not a claim that the scanner proves truthfulness.
See `validate_resume_system.UNCHECKED` for what deliberately stays a reading task.
"""
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "resume_builder" / "helpers"))
import validate_resume_system as validator # noqa: E402
CLAIMS = validator.load_json(validator.CLAIMS_PATH)
def scan(body: str) -> str:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "candidate.tex"
path.write_text(
"\\documentclass{resume}\n\\begin{document}\n"
"\\contactline{dennis@thiessen.io $\\vert$ +41 795 955 585}\n"
f"{body}\n\\end{{document}}\n",
encoding="utf-8")
return " | ".join(validator.scan_document(path, CLAIMS))
class ScannerCatchesTests(unittest.TestCase):
"""Things that must fail. Every one of these passed before the fix."""
def test_solo_ownership_of_a_company_scale_platform(self):
# The recorded regression wording carried an adjective the literal list missed.
self.assertIn("SW-7", scan(r"\item Built a decentralized Data Mesh for the group."))
def test_full_ml_lifecycle_ownership(self):
self.assertIn("BS-1", scan(r"\item I owned the full ML lifecycle end to end."))
def test_employer_excluded_from_all_documents(self):
self.assertIn("Capgemini", scan(r"\item Capgemini Deutschland GmbH, Nov 2014 -- May 2015."))
def test_certification_only_skill_without_its_context(self):
self.assertIn("PyTorch", scan(r"\item Trained production models in PyTorch."))
def test_forbidden_skill_split_by_markup(self):
# \textbf{Snow}flake defeated a plain substring comparison.
self.assertIn("Snowflake", scan(r"\item Delivered \textbf{Snow}flake pipelines."))
def test_unregistered_but_forbidden_skill(self):
self.assertIn("Groovy", scan(r"\item Maintained Groovy build jobs."))
def test_unresolved_placeholder(self):
self.assertIn("placeholder", scan(r"\item [GENERATE: Bullet]"))
def test_wrong_contact_details(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "wrong.tex"
path.write_text(
"\\documentclass{resume}\n\\begin{document}\n"
"\\contactline{wrong@example.com $\\vert$ +41 000 000 000}\n"
"\\end{document}\n", encoding="utf-8")
found = " | ".join(validator.scan_document(path, CLAIMS))
self.assertIn("e-mail", found)
self.assertIn("phone", found)
class ScannerAllowsTests(unittest.TestCase):
"""Things that must NOT fail. False positives taught the workflow to write around the checker."""
def test_latex_comment_is_not_a_claim(self):
self.assertEqual("", scan("% Never claim Terraform experience\n\\item Built pipelines."))
def test_honestly_denying_a_skill_is_not_claiming_it(self):
self.assertEqual("", scan(r"\item I have no Terraform experience and say so openly."))
def test_german_denial_is_not_claiming_it(self):
self.assertEqual("", scan(r"\item Keine Terraform-Erfahrung vorhanden."))
def test_education_institution_is_not_the_employment_entry(self):
# "Universitaet der Bundeswehr Muenchen" is a school, not the Bundeswehr employment
# record, and in German packages it is written with LaTeX accent macros.
body = r'\compactentry{M.Eng., Universit\"at der Bundeswehr M\"unchen}{Apr 2012 -- Oct 2013}'
self.assertNotIn("BUNDESWEHR", scan(body))
class ShippedDocumentsTests(unittest.TestCase):
"""The packages built under the current rules must stay clean."""
CURRENT = (
"SBB_DataEngineer_AssetMgmt", "NVIDIA_SrDevOps_Zurich", "RUAG_AI_Engineer_C5I",
"KdoCy_DevOps_Data_Platform", "CitadelSecurities_PlatformEngineer",
"AkerBP_Data_Product_Architect",
)
def test_current_packages_have_no_findings(self):
for folder in self.CURRENT:
for document in (ROOT / "output" / folder).glob("e2e_*resume.tex"):
with self.subTest(document=document.name):
self.assertEqual([], validator.scan_document(document, CLAIMS))
if __name__ == "__main__":
unittest.main()