This document defines the comparison contract and CI gate behavior used by parity workflows.
The repo's mission per CLAUDE.md is "validate OOXML files to determine if they will open successfully in their target apps." Specs 010 and 011 deliberately ship divergence from the .NET Open XML SDK because the SDK accepts files Word rejects. The parity-gate roles reflect that:
| Gate | Role | Status |
|---|---|---|
SDK parity comparison (compare_to_baseline.py) |
Advisory — trend visibility on validator output drift against SDK v3.4.1 | NON-BLOCKING in .github/workflows/parity-gate.yml (continue-on-error: true on the comparison step). Runs every push/PR; surfaces in workflow summary; does not fail the build. |
Perf budget (check_perf_budget.py) |
Runtime regression guard | BLOCKING — perf-budget violations still fail CI. |
| Self-parity (validator output vs our last green output) | Catches our own regressions, keyed on our output rather than SDK's | DEFERRED to Spec 013 (specs/013-validator-output-sovereign-gates.md). |
| Word/PowerPoint/Excel roundtrip oracle | Sovereign for app-survival claims | DEFERRED to Spec 011 + Spec 013. |
The strict-no-drift measurement policy (max 0 mismatch growth, 0 new families, 0pp match-rate drop) is unchanged — the comparison still uses those thresholds when deciding what to surface in the summary. The change is what happens when those thresholds are exceeded: the build no longer fails. SDK divergence is now a trend signal, not a gate.
When the SDK reference is bumped (separate spec), the advisory snapshot must be re-extracted so the trend stays meaningful.
Open XML SDK v3.4.1.data/corpus/sdk_seed/manifest.jsondata/corpus/parity_baseline/v3.4.1/parity_snapshot.jsondata/corpus/parity_baseline/v3.4.1/perf_budget.jsondata/corpus/parity_baseline/v3.4.1/waivers.jsonCurrent snapshots are produced by scripts/corpus/run_parity_snapshot.py and include:
duration_secondsvalidation_runschecks_collectedchecks_totalchecks_matchedchecks_mismatchedchecks_missing_filesmatch_rate_percentstrictinclude_mutation_expectationsskipped_mutation_expectationsmismatch_families[] with normalized tuple fields:iderror_typepartpathdescriptionfamily_key (id|error_type|part|path|description)countExpectations extracted from SDK tests are tagged in the manifest with scenario:
base: validation of an unmodified corpus filemutation: validation after test-time document mutations (add/remove/edit operations)Extractor normalization for parity fidelity:
OpenXmlValidator.Validate(package) assertions are mapped to file-level expectations.MaxNumberOfErrors tests) are tagged as mutation.Default parity snapshots run against base expectations only.
scripts/corpus/run_parity_snapshot.py excludes scenario == "mutation" unless --include-mutation-expectations is passed.skipped_mutation_expectations in the snapshot JSON.scripts/corpus/compare_to_baseline.py compares a current snapshot against baseline and enforces:
checks_mismatched delta)Family drift is keyed by family_key when present (falls back to description for older snapshots).
Default measurement policy is strict no-drift (advisory; see "Gate Roles" above for the build-failure semantics):
--max-mismatch-growth 0--max-new-families 0--max-match-rate-drop 0.0--max-missing-files 0--require-same-strictchecks_total dropThese thresholds determine what the comparator surfaces as drift; they no longer determine whether the build passes. Per Spec 012, the SDK comparison is non-blocking.
Workflow: .github/workflows/parity-gate.yml
compare_to_baseline.py (advisory; see "Gate Roles").check_perf_budget.py (blocking).Corpus source for gate:
data/corpus/sdk_seed/files/ if present.PARITY_CORPUS_ARCHIVE_URL.Archive requirement:
.tar.zst that extracts a top-level files/ directory.Performance budget is defined in data/corpus/parity_baseline/v3.4.1/perf_budget.json.
scripts/corpus/check_perf_budget.py validates:
max_duration_seconds)max_seconds_per_check)max_seconds_per_validation)min_checks_total)Default policy is fail-on-regression against these limits in PR CI.
Waivers are declared in data/corpus/parity_baseline/v3.4.1/waivers.json.
Required fields per waiver:
kind: one ofnew_mismatch_familymismatch_growthmatch_rate_dropmissing_filescheck_total_dropstrict_modeowner: accountable owner/teamreason: short rationaleexpires: ISO date YYYY-MM-DDtarget: required for new_mismatch_family (family description)Rules:
Example:
{
"waivers": [
{
"kind": "new_mismatch_family",
"target": "file_not_found",
"owner": "openxml-audit-maintainers",
"reason": "Temporary corpus mirror outage",
"expires": "2026-04-15"
}
]
}