SSA pipeline quality hardening (Phases 1-6)#15
Conversation
Captures the agreed plan for porting rpd-ssa-builder's quality patterns into gatekeeper's SSA report-docx output path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two runs over the same inputs now produce byte-identical output; docxcompose-style paraId drift and dcterms wall-clock stamps no longer leak into the deliverable. Lifted from rpd-ssa-builder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps Microsoft's OpenXmlValidator (via rpd-ssa-builder's .NET CLI) and runs it after the determinism post-pass. Findings surface on the diagnostics dict and as log warnings; the build does not abort on errors so callers retain hard-fail policy. Soft-skips when dotnet is unreachable. Reuses rpd-ssa-builder's cached DLL at %LOCALAPPDATA%\rpd-ssa-validator; rebuilds from its source on miss (override path via GATEKEEPER_SSA_VALIDATOR_SOURCE). The clean-output test is currently xfail(strict=True): current gatekeeper output emits <w:tblBorders> in the wrong position inside <w:tblPr>. Follow-up commit fixes the schema-order bug and unxfails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_apply_all_cell_borders was emitting <w:tblBorders> at the end of <w:tblPr> via plain .append(). The OOXML schema places tblBorders at position 11; <w:tblLook> (position 15) is already on every template table, so appending put tblBorders after tblLook and tripped four Sch_UnexpectedElementContentExpectingComplex errors per built docx. Word's recovery prompt tolerates this; OpenXmlValidator does not. Fixed by inserting before the first child whose tag falls in the positions-after-tblBorders set (shd, tblLayout, tblCellMar, tblLook, tblCaption, tblDescription, tblPrChange). Removes the xfail mark on test_validate_docx_clean_output_has_no_errors — current output now validates clean against Microsoft 365. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standalone module wrapping `soffice --headless --convert-to pdf` so the pipeline can catch "python-docx accepts it but LibreOffice / Word won't render it" defects that the OOXML schema validator may miss. Mirrors the OOXML validator's soft-skip pattern: returns SmokeResult(available=False) when soffice is not installed; never raises. A PDF that's missing or zero-byte counts as a failure even when soffice exits 0. Module is standalone for this commit — no production wire-in into build_ssa_report_docx or run_ssa_pipeline.py yet. Wiring lands with the --check preflight in Phase 4 (call-site level, not per-build) so the 14 build_ssa_report_docx unit tests don't each spawn soffice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gatekeeper-native preflight module. Checks:
- folder name matches YYYY-MM-DD-{RPD,SDG}[-NN]
- Evidence_Master.csv exists in folder root
- Evidence_Master.csv has the (timestamp, observation, filename) headers
- Evidence_Master.csv has >= 1 data row
- folder contains >= 1 .jpg / .jpeg / .png
- SSA report template readable at configured path
- LibreOffice reachable (informational)
- dotnet reachable (informational)
- ANTHROPIC_API_KEY set (informational)
Borrows the CheckResult / run_preflight / format_results shape from
rpd-ssa-builder's src/preflight.py but replaces all RPD-specific
checks (SafetyCulture docx, score pattern, Site_Visit_Report xlsx)
with the gatekeeper input contract.
Module is standalone for this commit. --check CLI wiring into
run_ssa_pipeline.py lands in the follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eline --check runs run_preflight on the audit folder and exits without building deliverables. Hard failures return exit 1; informational warnings don't block. After a successful full or from-state build, the report docx is smoke-tested via headless LibreOffice. Wire-in is at CLI-main level (not inside run_once) so the build_ssa_report_docx unit tests don't each spawn soffice. Also fixes a latent circular import: preflight.py now lazy-imports SSA_REPORT_TEMPLATE inside _check_template. Top-level import was OK when tests entered via test_ssa_preflight.py but blew up when entry was run_ssa_pipeline.py (different cycle traversal order). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tools/check_ssa_drift.py exits 0 if all paired constants agree, 1 otherwise. v1 covers the one drift case introduced in Phase 4a: ssa_pipeline._REQUIRED_HEADER must equal preflight._EXPECTED_CSV_HEADERS. A full drift checker on the rpd-ssa-builder model would need a refactor-first pass to extract gatekeeper's inline placeholder dicts (build_ssa_report_docx body_replacements / footer_replacements) into a named constants surface. Deferred until that placeholder convention matures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scoped CHANGELOG-SSA.md covering the determinism / OOXML validator / LibreOffice smoke / preflight / drift-checker landings plus the tblBorders schema-order fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e41015d8c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| else: | ||
| print(f"libreoffice smoke: FAILED - {smoke.failure_detail}") |
There was a problem hiding this comment.
Fail the CLI when LibreOffice smoke fails
When LibreOffice is installed and the generated report cannot be converted, this branch only prints FAILED and then falls through to the unconditional return 0. That means CI/wrappers or operators running the full/from-state build will treat a malformed DOCX as a successful run and may ship it despite the smoke test detecting the defect; the failure path should return non-zero or raise while still soft-skipping only when LibreOffice is unavailable.
Useful? React with 👍 / 👎.
| if result.returncode == 3: | ||
| return ValidationResult( | ||
| available=True, errors=[], | ||
| skip_reason=f"validator crashed: {result.stderr.strip()}", | ||
| ) |
There was a problem hiding this comment.
Don't report validator crashes as clean results
If the validator executable crashes with return code 3, available=True with an empty errors list makes callers treat the document as successfully validated; build_ssa_report_docx records [] for oxml_validation_errors and ignores skip_reason. In environments where the cached validator is broken or hits an unhandled DOCX case, the schema guard silently passes instead of surfacing that validation did not run successfully.
Useful? React with 👍 / 👎.
Summary
rpd-ssa-builderand adapted to gatekeeper's input contract.<w:tblBorders>schema-order bug in_apply_all_cell_borders(pims/services/ssa_pipeline.py:1089) that was emitting 4 schema errors per built docx. Word's recovery prompt tolerated it; the new OpenXmlValidator caught it.--checkpreflight CLI flag:python -m pims.scripts.run_ssa_pipeline --check <folder>. After successful full / from-state builds the report docx is round-tripped through headless LibreOffice as a smoke test.Test plan
python -m pytest tests/test_ssa_pipeline.py tests/test_ssa_determinism.py tests/test_ssa_oxml_validator.py tests/test_ssa_libreoffice_smoke.py tests/test_ssa_preflight.py tests/test_ssa_drift_checker.py tests/test_ssa_run_status.py tests/test_ssa_resume.py— full SSA suite greenpython tools/check_ssa_drift.py— exits 0 cleanlypython -m pims.scripts.run_ssa_pipeline --check <real audit folder>on a real SDG audit folderNotes for reviewer
4a8380b pims: promote paths auto-create missing sites rows); will merge cleanly — no file overlap with this work.rpd-ssa-builder's cached DLL at%LOCALAPPDATA%\rpd-ssa-validator\on machines that have it. Source path defaults toG:\My Drive\alan_mcxico\SSA-evidence\SSA rules\_validator\; override withGATEKEEPER_SSA_VALIDATOR_SOURCEenv var for installs without rpd-ssa-builder.--enrich-onlyexits before docx build;--from-reportflows back to xlsx).sdg-ssa-builderrepo) and a full drift checker (would need refactor-first pass onbuild_ssa_report_docx's inline placeholder dicts) remain deferred per the plan doc.🤖 Generated with Claude Code