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
109 lines
4.6 KiB
Python
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()
|