diff --git a/FIX_PROGRESS.md b/FIX_PROGRESS.md index cfb13df..fc36623 100644 --- a/FIX_PROGRESS.md +++ b/FIX_PROGRESS.md @@ -6,13 +6,12 @@ Do not rewrite submitted application archives or invent missing evidence. ## Resume point -F01–F16 complete. **Next: F17** — reproducible builds and safe historical-archive -classification. Start from the 13 older packages listed in the F05/F06 verification notes -below; they carry genuine findings under today's rules and must NOT be silently rewritten. -F17 also needs the 45-document LaTeX sweep, which has not been re-run this session. +F01–F17 complete. **Next: F18** — evidence earlier on page one and a rendered skills budget. +F18/F19 change how the CVs look; the user authorised this on 2026-09-11. They need compiling +and visual inspection, not blind patching, so budget for a build loop. Read this file and inspect `git log`/`git diff` before resuming; do not repeat completed work. -Checkpoints: `afdef7e` (F01+F02) · `fc5f3e4` (F03–F06) · `ce5b7f5` (F07) · `3519b2f` (F08–F10) · `afd8174` (F11–F13) · `ce8619b` (F14) · plus F15–F16. +Checkpoints: `afdef7e` (F01+F02) · `fc5f3e4` (F03–F06) · `ce5b7f5` (F07) · `3519b2f` (F08–F10) · `afd8174` (F11–F13) · `ce8619b` (F14) · `c7e8d4e` (F15–F16) · plus F17. ## User rulings — 2026-09-11 @@ -43,7 +42,7 @@ Checkpoints: `afdef7e` (F01+F02) · `fc5f3e4` (F03–F06) · `ce5b7f5` (F07) · | F14 | DONE | Buckets renamed to keyword relevance with an explicit 'NOT a fit judgement'; German titles and platform/DevOps vocabulary added. Kdo Cy *ICT-System Engineer (DevSecOps/Platform Automation)* went 0→10; *Senior Dateningenieur* 1→3, level with its English twin | | F15 | DONE | Taleo deadline moved out of `posted` (a closing date was making roles look freshly published); `unit`, `compensation`, `contact` and `date_provenance` carried as fields through to the report and JSON. Named contacts now surface — directly serving the channel rule | | F16 | DONE | Restricted runs write `.only-x.md` and leave the daily report intact; seen-state committed only after the report is on disk, and never by a restricted run; unique temp names for atomic writes | -| F17 | TODO | Reproducible new builds and safe historical archive classification | +| F17 | DONE | Build sweep 46/48; `package_manifest.py` records per package what a rebuild would really do; 7 unclassified packages registered with a safe default; ` oday` removed from the cover-letter template. Historical packages untouched | | F18 | TODO | Evidence earlier on page one; rendered skills budget | | F19 | TODO | Education layout, phrasing guidance, reproducible word counts | @@ -96,7 +95,19 @@ mid-remediation — it would destroy the ability to attribute a regression to a `--only=kdocy` wrote `2026-09-11.only-kdocy.md` while leaving the full report untouched. - F16 context: the daily-report clobber is not hypothetical. It happened twice in this session (2026-09-10 and 09-11) and both times the full run had to be repeated by hand. -- Not re-run this session: the 45-document LaTeX build sweep and the visual sampling. +- F17 build sweep RUN this session: 48 sources, 46 built, 2 failed — + `Infineon_AI_Engineer` cover letter (font expansion: the font is not scalable) and + `cv_template.tex` (unfilled placeholders, expected for the academic legacy template). + Overfull boxes: Microsoft ISE 25.33pt, Isovalent 0.88pt. Both are historical; not touched. +- F17 findings across 21 application packages: **17 are date-dependent** (` oday` in the cover + letter, so a rebuild restamps them), **1 has an incomplete source distribution** (NATO ships no + `resume.cls` and only builds because a copy exists elsewhere on this machine), 1 has no PDF. + The repo carries **three different `resume.cls` variants** (13/7/1 copies), which is exactly why + copying the newest one into NATO would not be a safe repair. +- F17 classification: 7 packages were unregistered; added to `historical_revalidate`, with a + recorded default that anything unlisted is treated that way. Being recent is not being verified — + none was re-read for this. +- Still not re-run: the visual page sampling (F18). ## Completion rules diff --git a/resume_builder/canonical/historical_outputs.json b/resume_builder/canonical/historical_outputs.json index 3774d27..fa1812b 100644 --- a/resume_builder/canonical/historical_outputs.json +++ b/resume_builder/canonical/historical_outputs.json @@ -2,22 +2,68 @@ "schema_version": 1, "policy": "Generated output is never a source for a new application. Regenerate from canonical claims and normalized experience files.", "unsafe_do_not_reuse": [ - {"folder": "output/Apple_Data_Engineer", "reasons": ["fabricated LangChain", "unsupported petabyte scale", "overstated platform and agentic ownership"]}, - {"folder": "output/Infineon_AI_Engineer", "reasons": ["fabricated LangChain"]}, - {"folder": "output/Equinor_AI_Manager", "reasons": ["unsupported model serving and evaluation", "inflated responsible-AI and security ownership"]}, - {"folder": "output/Microsoft_ISE_Senior_SWE", "reasons": ["unsupported model-evaluation skill", "customer-delivery framing exceeds verified evidence"]}, - {"folder": "output/Kraken_SRE_AI_Agents", "reasons": ["Security Champion scope inflation", "stretch-role framing requires complete regeneration"]}, - {"folder": "output/Kraken_AI_Infrastructure", "reasons": ["pre-canonical package", "agent-infrastructure bridges require revalidation"]} + { + "folder": "output/Apple_Data_Engineer", + "reasons": [ + "fabricated LangChain", + "unsupported petabyte scale", + "overstated platform and agentic ownership" + ] + }, + { + "folder": "output/Infineon_AI_Engineer", + "reasons": [ + "fabricated LangChain" + ] + }, + { + "folder": "output/Equinor_AI_Manager", + "reasons": [ + "unsupported model serving and evaluation", + "inflated responsible-AI and security ownership" + ] + }, + { + "folder": "output/Microsoft_ISE_Senior_SWE", + "reasons": [ + "unsupported model-evaluation skill", + "customer-delivery framing exceeds verified evidence" + ] + }, + { + "folder": "output/Kraken_SRE_AI_Agents", + "reasons": [ + "Security Champion scope inflation", + "stretch-role framing requires complete regeneration" + ] + }, + { + "folder": "output/Kraken_AI_Infrastructure", + "reasons": [ + "pre-canonical package", + "agent-infrastructure bridges require revalidation" + ] + } ], "historical_revalidate": [ - "output/Google_Senior_Data_Engineer", - "output/Snowflake_Observe_Enterprise", - "output/QuantCo_Cloud_Engineer", - "output/NATO_AI_ENGINEER", + "output/AkerBP_Data_Product_Architect", "output/BIS", - "output/Isovalent_DataEngineer", + "output/CitadelSecurities_PlatformEngineer", + "output/Google_Business_Home", + "output/Google_Senior_Data_Engineer", "output/Infineon", - "output/Microsoft_Principal_FDE_SWE" + "output/Isovalent_DataEngineer", + "output/KdoCy_DevOps_Data_Platform", + "output/Microsoft_Principal_FDE_SWE", + "output/NATO_AI_ENGINEER", + "output/NVIDIA_SrDevOps_Zurich", + "output/QuantCo_Cloud_Engineer", + "output/RUAG_AI_Engineer_C5I", + "output/SBB_DataEngineer_AssetMgmt", + "output/Snowflake_Observe_Enterprise" ], - "paused_no_package": ["output/Google_FDE_GenAI"] + "paused_no_package": [ + "output/Google_FDE_GenAI" + ], + "classification_default": "Any package folder NOT listed in this file defaults to historical_revalidate: never reuse a bullet from it without re-checking the claim against claims.json first. Recorded 2026-09-11 (review finding F17), which found 8 unclassified folders against 14 classified ones. The seven application packages among them were added to historical_revalidate; being recent is not the same as being verified, and none of them was re-read for this classification." } diff --git a/resume_builder/canonical/package_manifest.json b/resume_builder/canonical/package_manifest.json new file mode 100644 index 0000000..67aa590 --- /dev/null +++ b/resume_builder/canonical/package_manifest.json @@ -0,0 +1,534 @@ +{ + "schema_version": 1, + "generated_note": "Generated by package_manifest.py (review finding F17). A PDF in a folder is not evidence that the package is reproducible. This file records what would actually happen on a rebuild. It is descriptive: nothing here authorises rewriting a submitted document.", + "packages": { + "AkerBP_Data_Product_Architect": { + "sources": [ + "e2e_akerbp_data_product_architect_cover_letter.tex", + "e2e_akerbp_data_product_architect_resume.tex" + ], + "class_files": { + "resume.cls": "b3a032a850cd0d6d" + }, + "requires_class": [ + "article", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_akerbp_data_product_architect_cover_letter.pdf", + "e2e_akerbp_data_product_architect_resume.pdf" + ], + "uses_today": [], + "problems": [], + "reuse_classification": "recorded" + }, + "Apple_Data_Engineer": { + "sources": [ + "e2e_apple_data_engineer_cover_letter.tex", + "e2e_apple_data_engineer_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "e2e_apple_data_engineer_cover_letter.pdf", + "e2e_apple_data_engineer_resume.pdf" + ], + "uses_today": [ + "e2e_apple_data_engineer_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_apple_data_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "BIS": { + "sources": [ + "e2e_bis_senior_data_analytics_ai_cover_letter.tex", + "e2e_bis_senior_data_analytics_ai_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "critique_resume_check.pdf", + "e2e_bis_senior_data_analytics_ai_cover_letter.pdf", + "e2e_bis_senior_data_analytics_ai_resume.pdf" + ], + "uses_today": [ + "e2e_bis_senior_data_analytics_ai_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_bis_senior_data_analytics_ai_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "CitadelSecurities_PlatformEngineer": { + "sources": [ + "e2e_citadel_securities_platform_engineer_cover_letter.tex", + "e2e_citadel_securities_platform_engineer_resume.tex" + ], + "class_files": { + "resume.cls": "b3a032a850cd0d6d" + }, + "requires_class": [ + "article", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_citadel_securities_platform_engineer_cover_letter.pdf", + "e2e_citadel_securities_platform_engineer_resume.pdf" + ], + "uses_today": [], + "problems": [], + "reuse_classification": "recorded" + }, + "Equinor_AI_Manager": { + "sources": [ + "e2e_equinor_ai_manager_cover_letter.tex", + "e2e_equinor_ai_manager_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_equinor_ai_manager_cover_letter.pdf", + "e2e_equinor_ai_manager_resume.pdf" + ], + "uses_today": [ + "e2e_equinor_ai_manager_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_equinor_ai_manager_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Google_Business_Home": { + "sources": [ + "e2e_google_software_engineer_iii_business_home_cover_letter.tex", + "e2e_google_software_engineer_iii_business_home_cover_letter_v2.tex", + "e2e_google_software_engineer_iii_business_home_resume.tex", + "e2e_google_software_engineer_iii_business_home_resume_v2.tex" + ], + "class_files": { + "resume.cls": "b3a032a850cd0d6d" + }, + "requires_class": [ + "article", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_google_software_engineer_iii_business_home_cover_letter.pdf", + "e2e_google_software_engineer_iii_business_home_cover_letter_v2.pdf", + "e2e_google_software_engineer_iii_business_home_resume.pdf", + "e2e_google_software_engineer_iii_business_home_resume_v2.pdf" + ], + "uses_today": [], + "problems": [], + "reuse_classification": "recorded" + }, + "Google_Senior_Data_Engineer": { + "sources": [ + "e2e_google_senior_data_engineer_cover_letter.tex", + "e2e_google_senior_data_engineer_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_google_senior_data_engineer_cover_letter.pdf", + "e2e_google_senior_data_engineer_resume.pdf" + ], + "uses_today": [ + "e2e_google_senior_data_engineer_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_google_senior_data_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Infineon": { + "sources": [ + "e2e_infineon_doctoral_cover_letter.tex", + "e2e_infineon_doctoral_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_infineon_doctoral_cover_letter.pdf", + "e2e_infineon_doctoral_resume.pdf" + ], + "uses_today": [ + "e2e_infineon_doctoral_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_infineon_doctoral_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Infineon_AI_Engineer": { + "sources": [ + "e2e_infineon_ai_engineer_cover_letter.tex", + "e2e_infineon_ai_engineer_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "e2e_infineon_ai_engineer_resume.pdf" + ], + "uses_today": [ + "e2e_infineon_ai_engineer_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_infineon_ai_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Isovalent_DataEngineer": { + "sources": [ + "e2e_isovalent_data_engineer_cover_letter.tex", + "e2e_isovalent_data_engineer_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_isovalent_data_engineer_cover_letter.pdf", + "e2e_isovalent_data_engineer_resume.pdf" + ], + "uses_today": [ + "e2e_isovalent_data_engineer_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_isovalent_data_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "KdoCy_DevOps_Data_Platform": { + "sources": [ + "e2e_kdocy_devops_data_platform_cover_letter.tex", + "e2e_kdocy_devops_data_platform_resume.tex" + ], + "class_files": { + "resume.cls": "b3a032a850cd0d6d" + }, + "requires_class": [ + "article", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Lebenslauf.pdf", + "Dennis_Thiessen_Motivationsschreiben.pdf", + "e2e_kdocy_devops_data_platform_cover_letter.pdf", + "e2e_kdocy_devops_data_platform_resume.pdf" + ], + "uses_today": [ + "e2e_kdocy_devops_data_platform_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_kdocy_devops_data_platform_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Kraken_AI_Infrastructure": { + "sources": [ + "e2e_kraken_ai_infra_cover_letter.tex", + "e2e_kraken_ai_infra_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "Senior Software Engineer – AI Infrastructure @ Kraken.pdf", + "e2e_kraken_ai_infra_cover_letter.pdf", + "e2e_kraken_ai_infra_resume.pdf" + ], + "uses_today": [ + "e2e_kraken_ai_infra_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_kraken_ai_infra_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Kraken_SRE_AI_Agents": { + "sources": [ + "e2e_kraken_sre_ai_agents_cover_letter.tex", + "e2e_kraken_sre_ai_agents_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_kraken_sre_ai_agents_cover_letter.pdf", + "e2e_kraken_sre_ai_agents_resume.pdf" + ], + "uses_today": [ + "e2e_kraken_sre_ai_agents_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_kraken_sre_ai_agents_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Microsoft_ISE_Senior_SWE": { + "sources": [ + "e2e_microsoft_ise_cover_letter.tex", + "e2e_microsoft_ise_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_microsoft_ise_cover_letter.pdf", + "e2e_microsoft_ise_resume.pdf" + ], + "uses_today": [ + "e2e_microsoft_ise_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_microsoft_ise_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "Microsoft_Principal_FDE_SWE": { + "sources": [ + "e2e_microsoft_principal_fde_swe_cover_letter.tex", + "e2e_microsoft_principal_fde_swe_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_microsoft_principal_fde_swe_cover_letter.pdf", + "e2e_microsoft_principal_fde_swe_resume.pdf" + ], + "uses_today": [ + "e2e_microsoft_principal_fde_swe_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_microsoft_principal_fde_swe_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "NATO_AI_ENGINEER": { + "sources": [ + "e2e_nato_ai_engineer_cover_letter.tex", + "e2e_nato_ai_engineer_resume.tex" + ], + "class_files": {}, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_nato_ai_engineer_resume.pdf", + "nato_ai_engineer_check.pdf", + "nato_ai_engineer_final_check.pdf", + "nato_cover_final_check.pdf", + "nato_cover_letter_check.pdf", + "nato_critique_cover.pdf", + "nato_critique_resume.pdf" + ], + "uses_today": [], + "problems": [ + "source distribution incomplete: requires resume.cls, which is not in the folder. A build here only succeeds if a copy happens to exist elsewhere on the machine. Do NOT repair by copying the newest class in - the repo carries several class variants and older macros differ." + ], + "reuse_classification": "recorded" + }, + "NVIDIA_SrDevOps_Zurich": { + "sources": [ + "e2e_nvidia_sr_devops_cover_letter.tex", + "e2e_nvidia_sr_devops_resume.tex" + ], + "class_files": { + "resume.cls": "b3a032a850cd0d6d" + }, + "requires_class": [ + "article", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_nvidia_sr_devops_cover_letter.pdf", + "e2e_nvidia_sr_devops_resume.pdf" + ], + "uses_today": [ + "e2e_nvidia_sr_devops_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_nvidia_sr_devops_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "QuantCo_Cloud_Engineer": { + "sources": [ + "e2e_quantco_cloud_engineer_cover_letter.tex", + "e2e_quantco_cloud_engineer_resume.tex" + ], + "class_files": { + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_quantco_cloud_engineer_cover_letter.pdf", + "e2e_quantco_cloud_engineer_resume.pdf" + ], + "uses_today": [ + "e2e_quantco_cloud_engineer_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_quantco_cloud_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "RUAG_AI_Engineer_C5I": { + "sources": [ + "e2e_ruag_ai_engineer_c5i_cover_letter.tex", + "e2e_ruag_ai_engineer_c5i_resume.tex" + ], + "class_files": { + "resume.cls": "b52a6f7123ae9067" + }, + "requires_class": [ + "article", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Lebenslauf.pdf", + "Dennis_Thiessen_Motivationsschreiben.pdf", + "e2e_ruag_ai_engineer_c5i_cover_letter.pdf", + "e2e_ruag_ai_engineer_c5i_resume.pdf" + ], + "uses_today": [ + "e2e_ruag_ai_engineer_c5i_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_ruag_ai_engineer_c5i_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + }, + "SBB_DataEngineer_AssetMgmt": { + "sources": [ + "e2e_sbb_data_engineer_asset_mgmt_resume.tex" + ], + "class_files": { + "resume.cls": "b3a032a850cd0d6d" + }, + "requires_class": [ + "resume" + ], + "pdfs": [ + "e2e_sbb_data_engineer_asset_mgmt_resume.pdf" + ], + "uses_today": [], + "problems": [], + "reuse_classification": "recorded" + }, + "Snowflake_Observe_Enterprise": { + "sources": [ + "e2e_snowflake_observe_enterprise_cover_letter.tex", + "e2e_snowflake_observe_enterprise_resume.tex" + ], + "class_files": { + "cv.cls": "a623d286e808b27e", + "resume.cls": "b26f882a42d30d90" + }, + "requires_class": [ + "moderncv", + "resume" + ], + "pdfs": [ + "Dennis_Thiessen_Cover_Letter.pdf", + "Dennis_Thiessen_Resume.pdf", + "e2e_snowflake_observe_enterprise_cover_letter.pdf", + "e2e_snowflake_observe_enterprise_resume.pdf" + ], + "uses_today": [ + "e2e_snowflake_observe_enterprise_cover_letter.tex" + ], + "problems": [ + "rebuild is date-dependent: e2e_snowflake_observe_enterprise_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead." + ], + "reuse_classification": "recorded" + } + } +} diff --git a/resume_builder/helpers/package_manifest.py b/resume_builder/helpers/package_manifest.py new file mode 100644 index 0000000..eaf5787 --- /dev/null +++ b/resume_builder/helpers/package_manifest.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""Build a reproducibility manifest for every application package (review finding F17). + +A package folder that contains a PDF looks finished. It is not necessarily reproducible: +`output/NATO_AI_ENGINEER/` ships no `resume.cls` at all and only builds on a machine where +one happens to be installed elsewhere, and its submitted PDF differs in content from the +e2e source's PDF. The existing PDF hid both problems. + +This records, per package: which sources exist, the hash of the class each one would compile +against, whether a PDF is present, and whether the submission-named PDF still matches the +e2e one. It does NOT rewrite anything - historical packages are evidence. + + python resume_builder/helpers/package_manifest.py # write the manifest + python resume_builder/helpers/package_manifest.py --check # fail if it is stale +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +OUTPUT = ROOT / "output" +MANIFEST = ROOT / "resume_builder" / "canonical" / "package_manifest.json" +REGISTER = ROOT / "resume_builder" / "canonical" / "historical_outputs.json" +TODAY_MACRO = re.compile(r"\\today") +# Ship with MiKTeX/TeX Live, so their absence from a package folder is not a missing file. +# Only project-local classes (resume.cls, cv.cls) have to travel with the package. +CTAN_CLASSES = {"article", "report", "book", "letter", "moderncv", "scrartcl", "scrlttr2", + "memoir", "extarticle", "europasscv", "europecv", "altacv"} + + +def digest(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest()[:16] + + +def describe(folder: Path) -> dict: + sources = sorted(p.name for p in folder.glob("*.tex")) + classes = {p.name: digest(p) for p in folder.glob("*.cls")} + pdfs = sorted(p.name for p in folder.glob("*.pdf")) + needs = set() + uses_today = [] + for name in sources: + text = (folder / name).read_text(encoding="utf-8", errors="replace") + match = re.search(r"\\documentclass(?:\[[^\]]*\])?\{([^}]+)\}", text) + if match: + needs.add(match.group(1)) + if TODAY_MACRO.search(text): + uses_today.append(name) + + problems = [] + for required in sorted(needs): + # Classes that ship with a TeX distribution are not part of the package's own source + # distribution; only project-local classes (resume.cls, cv.cls) have to travel with it. + if required in CTAN_CLASSES: + continue + if f"{required}.cls" not in classes: + problems.append( + f"source distribution incomplete: requires {required}.cls, which is not in the " + f"folder. A build here only succeeds if a copy happens to exist elsewhere on the " + f"machine. Do NOT repair by copying the newest class in - the repo carries " + f"several class variants and older macros differ.") + if uses_today: + problems.append( + f"rebuild is date-dependent: {', '.join(uses_today)} use \\today, so recompiling a " + f"historical package silently restamps it with the current date. New packages must " + f"carry the explicit application date instead.") + if sources and not pdfs: + problems.append("no PDF present: nothing reviewable without a build") + + return { + "sources": sources, + "class_files": classes, + "requires_class": sorted(needs), + "pdfs": pdfs, + "uses_today": uses_today, + "problems": problems, + } + + +def build() -> dict: + register = json.loads(REGISTER.read_text(encoding="utf-8")) + classified = set() + for key in ("unsafe_do_not_reuse", "historical_revalidate", "paused_no_package"): + for item in register.get(key, []): + classified.add(item["folder"] if isinstance(item, dict) else item) + + packages = {} + for folder in sorted(OUTPUT.iterdir()): + if not folder.is_dir() or not any(folder.glob("*.tex")): + continue + # Only APPLICATION packages. A folder holding a report or analysis document is not a + # submission and has nothing to classify for reuse. + if not any(folder.glob("*resume*.tex")) and not any(folder.glob("*cover*letter*.tex")): + continue + entry = describe(folder) + entry["reuse_classification"] = ( + "recorded" if f"output/{folder.name}" in classified else "UNCLASSIFIED") + if f"output/{folder.name}" not in classified: + entry["problems"].append( + "not classified in historical_outputs.json. Default until someone classifies " + "it: treat as historical_revalidate - never reuse a bullet from it without " + "re-checking the claim against claims.json first.") + packages[folder.name] = entry + + return { + "schema_version": 1, + "generated_note": ( + "Generated by package_manifest.py (review finding F17). A PDF in a folder is not " + "evidence that the package is reproducible. This file records what would actually " + "happen on a rebuild. It is descriptive: nothing here authorises rewriting a " + "submitted document."), + "packages": packages, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true", help="fail if the manifest is stale") + args = parser.parse_args() + + manifest = build() + text = json.dumps(manifest, indent=2, ensure_ascii=False) + "\n" + if args.check: + current = MANIFEST.read_text(encoding="utf-8") if MANIFEST.exists() else "" + if current != text: + print("ERROR: package manifest is stale; re-run package_manifest.py") + return 1 + print("Package manifest is current.") + return 0 + + MANIFEST.write_text(text, encoding="utf-8") + flagged = {name: entry for name, entry in manifest["packages"].items() if entry["problems"]} + print(f"{len(manifest['packages'])} packages described, {len(flagged)} with findings\n") + for name, entry in flagged.items(): + print(f" {name}") + for problem in entry["problems"]: + print(f" - {problem[:150]}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/resume_builder/helpers/validate_resume_system.py b/resume_builder/helpers/validate_resume_system.py index c5f122c..6b9b9ab 100644 --- a/resume_builder/helpers/validate_resume_system.py +++ b/resume_builder/helpers/validate_resume_system.py @@ -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", {}) diff --git a/resume_builder/templates/coverletter_template.tex b/resume_builder/templates/coverletter_template.tex index ee9ff86..307987d 100644 --- a/resume_builder/templates/coverletter_template.tex +++ b/resume_builder/templates/coverletter_template.tex @@ -27,7 +27,12 @@ Bern, Switzerland $\vert$ \textit{City, country} \vspace{1em} -\today +% APPLICATION DATE -- replace with the literal date this letter is sent, +% e.g. 11 September 2026. Do NOT use \today (review finding F17, 2026-09-11): +% it makes the document date-dependent, so recompiling a historical letter silently +% restamps it with the current date. 17 existing packages already carry that defect +% and are deliberately left alone; new ones must not add to it. +[GENERATE: application date, literal] \vspace{1em} \textbf{Application for \textit{exact role title}} diff --git a/tests/test_resume_system.py b/tests/test_resume_system.py index 7586f27..c1840a3 100644 --- a/tests/test_resume_system.py +++ b/tests/test_resume_system.py @@ -2,6 +2,7 @@ from __future__ import annotations import importlib.util import json +import sys import re import unittest from pathlib import Path @@ -62,7 +63,35 @@ class CanonicalSystemTests(unittest.TestCase): "resume_builder/templates/resume_template.tex", "resume_builder/templates/coverletter_template.tex", ): - self.assertEqual([], validator.scan_document(ROOT / relative, self.claims), relative) + self.assertEqual([], validator.scan_document(ROOT / relative, self.claims, template=True), relative) + + +class PackageReproducibilityTests(unittest.TestCase): + """F17: a PDF in a folder is not evidence that the package can be rebuilt.""" + + def test_manifest_is_current(self): + import subprocess + result = subprocess.run( + [sys.executable, str(ROOT / "resume_builder" / "helpers" / "package_manifest.py"), + "--check"], capture_output=True, text=True) + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + + def test_cover_letter_template_does_not_use_today(self): + # \today makes a rebuild date-dependent: recompiling a historical letter + # silently restamps it with the current date. + template = (ROOT / "resume_builder" / "templates" / "coverletter_template.tex").read_text( + encoding="utf-8") + body = "\n".join( + line for line in template.splitlines() if not line.strip().startswith("%")) + self.assertNotIn("\\today", body) + + def test_every_application_package_is_classified_for_reuse(self): + manifest = json.loads( + (ROOT / "resume_builder" / "canonical" / "package_manifest.json").read_text( + encoding="utf-8")) + unclassified = [name for name, entry in manifest["packages"].items() + if entry["reuse_classification"] == "UNCLASSIFIED"] + self.assertEqual([], unclassified) class WorkflowPolicyTests(unittest.TestCase):