diff --git a/README.md b/README.md index 744f24c..f615e75 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Open Institutional Compiler -- **Status:** BOUNDED_REFERENCE_IMPLEMENTATION — scoped independent Gate F repository validation passed; merge pending Gate G and owner authorization +- **Status:** BOUNDED_REFERENCE_IMPLEMENTATION — scoped independent Gate G validation passed for candidate `a2b5053771ce510fb35ce09f3e99f545c21ac20e` and merged to `main` as `c4a325c551ce8904dfcc5b9fe81b05109726a334` - **Bootstrap date:** 2026-07-29 - **Governing design:** TDD-OIC-001 v1.1 @@ -44,8 +44,19 @@ that exact candidate only. It does not establish semantic correctness, model accuracy, institutional validity, legal effect, provider qualification, rights resolution, ontology execution, production compilation, runtime authorization, institutional-IR closure, enterprise readiness, or benchmark superiority. It -also does not establish legal validity or production readiness. Merge remains -pending Gate G and owner authorization. +also does not establish legal validity or production readiness. It is not +validation of any later commit. +Independent Gate G validation passed for candidate +`a2b5053771ce510fb35ce09f3e99f545c21ac20e` (tree +`b8e31ec4786a2fd1aca976a6ff047deeee63ef15`): canonical Linux execution reported +1720 passed, 0 failed, 0 errors, 1 declared skip, 93.5% coverage, and the same +byte-identical offline demo digest. Pull request 40 merged that exact tree into +`main` as merge commit `c4a325c551ce8904dfcc5b9fe81b05109726a334` (first parent +`9ad37fc80d8f34318c6212ed702de5eab3551cf5`, second parent +`a2b5053771ce510fb35ce09f3e99f545c21ac20e`), approved by `inventor1975` and +merged at 2026-09-06T08:14:03Z. That result carries the same twelve exclusions +and the same ceilings, is scoped to that exact candidate and tree, and is not +validation of any later commit. See [`CAPABILITY_MATRIX.json`](docs/capabilities/CAPABILITY_MATRIX.json) for exact provenance and ceilings. NVIDIA is NOT_QUALIFIED; Canada redistribution is UNRESOLVED; Ontology 007R1 is unexecuted and execution-unauthorized. No model accuracy or legal validity is claimed. diff --git a/STATUS.md b/STATUS.md index 7fe1e4a..227172b 100644 --- a/STATUS.md +++ b/STATUS.md @@ -2,12 +2,13 @@ ## Current status -**BOUNDED_REFERENCE_IMPLEMENTATION — SCOPED INDEPENDENT GATE F REPOSITORY VALIDATION PASSED** +**BOUNDED_REFERENCE_IMPLEMENTATION — SCOPED INDEPENDENT GATE G VALIDATION PASSED AND MERGED TO MAIN** OIC-SEMANTIC-PROMOTION-001 admits only the 58-path maximum recorded in `docs/capabilities/CAPABILITY_MATRIX.json`, from main `9ad37fc80d8f34318c6212ed702de5eab3551cf5`. It implements an offline synthetic reference path, not production compilation, runtime authorization, or canonical meaning. + Independent Gate F repository validation passed for exact candidate `c0108a7a80585d6f5732407d4904ba815073ecd2`, tree `1d12b17aad7977c939090909171183be166cfd50`. Canonical Linux execution reported @@ -15,8 +16,23 @@ Independent Gate F repository validation passed for exact candidate byte-identical offline demo runs with SHA-256 `0f9d01bb0dfc488505e027ac7bd8aecf869578e379b5a977cd9d642f2101a39a`. The result establishes reproducibility, boundary integrity, the specified fail-closed -properties, packaging, and named adversarial checks for that candidate only. -Merge remains pending Gate G and owner authorization; no merge is authorized. +properties, packaging, and named adversarial checks for that candidate only. It +is not validation of any later commit. + +Independent Gate G validation passed for exact candidate +`a2b5053771ce510fb35ce09f3e99f545c21ac20e`, tree +`b8e31ec4786a2fd1aca976a6ff047deeee63ef15`. Canonical Linux execution reported +1720 passed, 0 failed, 0 errors, 1 declared skip +(`tests/contract/test_canada_acquisition_preflight.py:574`), 93.5% coverage, and +two byte-identical offline demo runs with SHA-256 +`0f9d01bb0dfc488505e027ac7bd8aecf869578e379b5a977cd9d642f2101a39a`. Pull request +40 merged that exact tree into `main` as merge commit +`c4a325c551ce8904dfcc5b9fe81b05109726a334`, first parent +`9ad37fc80d8f34318c6212ed702de5eab3551cf5`, second parent +`a2b5053771ce510fb35ce09f3e99f545c21ac20e`, approved by `inventor1975` at +2026-09-06T08:12:51Z and merged at 2026-09-06T08:14:03Z. The merged tree is +byte-identical to the validated tree. Each result above is scoped to the exact +commit and tree that produced it and to nothing later. NVIDIA: NOT_QUALIFIED and excluded from the demo. Canada redistribution: UNRESOLVED. Ontology 007R1: unexecuted and execution-unauthorized. Institutional-IR closure: diff --git a/docs/capabilities/CAPABILITY_MATRIX.json b/docs/capabilities/CAPABILITY_MATRIX.json index bd98e45..84830c0 100644 --- a/docs/capabilities/CAPABILITY_MATRIX.json +++ b/docs/capabilities/CAPABILITY_MATRIX.json @@ -305,6 +305,49 @@ "benchmark superiority" ] }, + "gate_g_promotion_evidence": { + "status": "GATE_G_PASS", + "work_order": "OIC-INDEPENDENT-GATE-G-001", + "candidate_commit": "a2b5053771ce510fb35ce09f3e99f545c21ac20e", + "candidate_tree": "b8e31ec4786a2fd1aca976a6ff047deeee63ef15", + "canonical_linux": { + "passed": 1720, + "failed": 0, + "errors": 0, + "skipped": 1, + "declared_skip": "tests/contract/test_canada_acquisition_preflight.py:574", + "coverage_percent": 93.5 + }, + "demo_sha256": "0f9d01bb0dfc488505e027ac7bd8aecf869578e379b5a977cd9d642f2101a39a", + "promotion": { + "pull_request": 40, + "pull_request_url": "https://github.com/veraxis-protocol/Institutional-Compiler/pull/40", + "base_before_merge": "9ad37fc80d8f34318c6212ed702de5eab3551cf5", + "merge_commit": "c4a325c551ce8904dfcc5b9fe81b05109726a334", + "merge_first_parent": "9ad37fc80d8f34318c6212ed702de5eab3551cf5", + "merge_second_parent": "a2b5053771ce510fb35ce09f3e99f545c21ac20e", + "merge_tree": "b8e31ec4786a2fd1aca976a6ff047deeee63ef15", + "merge_method": "merge_commit", + "approved_by": "inventor1975", + "approved_at": "2026-09-06T08:12:51Z", + "merged_at": "2026-09-06T08:14:03Z" + }, + "scope": "Scoped independent Gate G acceptance of this exact candidate: identity and boundary, claims audit, adversarial fail-closed challenges, canonical Linux execution, deterministic offline demo, packaging, and repository integrity. Pull request 40 merged this exact tree into main without modification.", + "exclusions": [ + "semantic correctness", + "model accuracy", + "institutional validity", + "legal effect", + "provider qualification", + "rights resolution", + "ontology execution", + "production compilation", + "runtime authorization", + "institutional-IR closure", + "enterprise readiness", + "benchmark superiority" + ] + }, "capabilities": [ { "name": "grounded_candidate_extraction", diff --git a/scripts/verify_code_start_gate.py b/scripts/verify_code_start_gate.py index 9683547..de64069 100644 --- a/scripts/verify_code_start_gate.py +++ b/scripts/verify_code_start_gate.py @@ -94,6 +94,65 @@ def validate_independent_validation_evidence(evidence: object) -> None: _require(evidence == INDEPENDENT_VALIDATION_EVIDENCE, "independent validation evidence forged") +GATE_G_PROMOTION_EVIDENCE = { + "status": "GATE_G_PASS", + "work_order": "OIC-INDEPENDENT-GATE-G-001", + "candidate_commit": "a2b5053771ce510fb35ce09f3e99f545c21ac20e", + "candidate_tree": "b8e31ec4786a2fd1aca976a6ff047deeee63ef15", + "canonical_linux": { + "passed": 1720, + "failed": 0, + "errors": 0, + "skipped": 1, + "declared_skip": "tests/contract/test_canada_acquisition_preflight.py:574", + "coverage_percent": 93.5, + }, + "demo_sha256": "0f9d01bb0dfc488505e027ac7bd8aecf869578e379b5a977cd9d642f2101a39a", + "promotion": { + "pull_request": 40, + "pull_request_url": "https://github.com/veraxis-protocol/Institutional-Compiler/pull/40", + "base_before_merge": "9ad37fc80d8f34318c6212ed702de5eab3551cf5", + "merge_commit": "c4a325c551ce8904dfcc5b9fe81b05109726a334", + "merge_first_parent": "9ad37fc80d8f34318c6212ed702de5eab3551cf5", + "merge_second_parent": "a2b5053771ce510fb35ce09f3e99f545c21ac20e", + "merge_tree": "b8e31ec4786a2fd1aca976a6ff047deeee63ef15", + "merge_method": "merge_commit", + "approved_by": "inventor1975", + "approved_at": "2026-09-06T08:12:51Z", + "merged_at": "2026-09-06T08:14:03Z", + }, + "scope": ( + "Scoped independent Gate G acceptance of this exact candidate: identity and " + "boundary, claims audit, adversarial fail-closed challenges, canonical Linux " + "execution, deterministic offline demo, packaging, and repository integrity. " + "Pull request 40 merged this exact tree into main without modification." + ), + "exclusions": [ + "semantic correctness", + "model accuracy", + "institutional validity", + "legal effect", + "provider qualification", + "rights resolution", + "ontology execution", + "production compilation", + "runtime authorization", + "institutional-IR closure", + "enterprise readiness", + "benchmark superiority", + ], +} + + +def validate_gate_g_promotion_evidence(evidence: object) -> None: + """Accept only the exact owner-adjudicated Gate G promotion evidence record. + + The merge second parent must be the Gate G candidate and the merge tree must be + the validated tree, so a forged, mutated or removed promotion record fails closed. + """ + _require(evidence == GATE_G_PROMOTION_EVIDENCE, "gate g promotion evidence forged") + + ADMITTED_SRC_OIC_PATHS = frozenset( { "src/oic/__init__.py", @@ -273,6 +332,7 @@ def validate_bounded_record(root: Path) -> None: "source_provenance", "ceilings", "independent_validation_evidence", + "gate_g_promotion_evidence", "capabilities", }, "capability matrix fields expanded", @@ -313,6 +373,7 @@ def validate_bounded_record(root: Path) -> None: _require(record.get("production_semantic_gate") == "BLOCKED", "production gate expanded") _require(record.get("ceilings") == CEILINGS, "evidence ceiling expanded") validate_independent_validation_evidence(record.get("independent_validation_evidence")) + validate_gate_g_promotion_evidence(record.get("gate_g_promotion_evidence")) provenance = record.get("source_provenance") _require( hashlib.sha256( diff --git a/tests/contract/test_claims_discipline.py b/tests/contract/test_claims_discipline.py index 9803022..b70ff2b 100644 --- a/tests/contract/test_claims_discipline.py +++ b/tests/contract/test_claims_discipline.py @@ -230,6 +230,57 @@ def test_static_claims_documents_are_unchanged_by_this_work_order(repo_root: Pat assert observed == committed, relpath +#: Pre-merge wording that the completed PR #40 promotion makes false. A front-door +#: document that reintroduces any of these is stale and must fail closed. +STALE_PRE_MERGE_PHRASES = ( + "merge remains pending gate g and owner authorization", + "merge pending gate g and owner authorization", + "no merge is authorized", + "gate g and owner merge authorization remain pending", +) + + +def _assert_promotion_claims(normalized: str, promotion: object) -> None: + """Require the shipped text to carry the exact Gate G promotion anchors. + + Every anchor is read from the live capability matrix rather than hard-coded here, + so changing an anchor in the matrix without changing the shipped document fails + closed, and changing the document without the matrix fails closed too. + """ + assert isinstance(promotion, dict), "missing gate_g_promotion_evidence" + assert promotion.get("status") == "GATE_G_PASS" + assert promotion.get("work_order") == "OIC-INDEPENDENT-GATE-G-001" + detail = promotion.get("promotion") + assert isinstance(detail, dict), "missing promotion detail" + + for key in ("candidate_commit", "candidate_tree"): + value = promotion.get(key) + assert isinstance(value, str) and value in normalized, ( + f"missing Gate G promotion anchor: {key}" + ) + for key in ("merge_commit", "merge_first_parent", "merge_second_parent"): + value = detail.get(key) + assert isinstance(value, str) and value in normalized, ( + f"missing Gate G promotion anchor: {key}" + ) + + pull_request = detail.get("pull_request") + assert isinstance(pull_request, int) + assert f"pull request {pull_request}" in normalized, "missing pull request reference" + approver = detail.get("approved_by") + assert isinstance(approver, str) and approver.lower() in normalized + merged_at = detail.get("merged_at") + assert isinstance(merged_at, str) and merged_at.lower() in normalized + + assert "independent gate g validation passed" in normalized, ( + "missing independent Gate G validation statement" + ) + assert promotion.get("exclusions") == list(GATE_F_EXCLUSIONS) + + for phrase in STALE_PRE_MERGE_PHRASES: + assert phrase not in normalized, f"stale pre-merge assertion reintroduced: {phrase}" + + def _assert_status_claims(text: str, capability_matrix: dict[str, object]) -> None: """Require the active bounded state and ceilings; reject affirmative escalation.""" normalized = " ".join(text.lower().replace("*", "").split()) @@ -237,6 +288,7 @@ def _assert_status_claims(text: str, capability_matrix: dict[str, object]) -> No gate = capability_matrix["production_semantic_gate"] ceilings = capability_matrix["ceilings"] evidence = capability_matrix.get("independent_validation_evidence") + promotion = capability_matrix.get("gate_g_promotion_evidence") assert isinstance(state, str) and state.lower() in normalized assert isinstance(gate, str) and f"production semantic gate: {gate.lower()}" in normalized assert isinstance(ceilings, dict) @@ -265,13 +317,14 @@ def _assert_status_claims(text: str, capability_matrix: dict[str, object]) -> No "missing STATUS.md independent Gate F validation statement" ) assert "1714 passed, 0 failed, 0 errors, 1 declared skip, 93.5% coverage" in normalized - assert "merge remains pending gate g and owner authorization" in normalized assert "does not establish semantic correctness" in normalized assert evidence.get("exclusions") == list(GATE_F_EXCLUSIONS) for exclusion in GATE_F_EXCLUSIONS: assert exclusion.lower() in normalized + _assert_promotion_claims(normalized, promotion) else: assert evidence is None + assert promotion is None assert "pending independent validation" in normalized for phrase in FORBIDDEN_ABSOLUTELY: assert phrase not in normalized, f"forbidden STATUS.md claim: {phrase}" @@ -331,11 +384,36 @@ def test_status_reports_active_bounded_state_and_ceilings(repo_root: Path) -> No with pytest.raises(AssertionError, match="independently validated"): _assert_status_claims(broad_claim, matrix) + stale_claim = status + "\nMerge remains pending Gate G and owner authorization.\n" + with pytest.raises(AssertionError, match="stale pre-merge assertion reintroduced"): + _assert_status_claims(stale_claim, matrix) + + drifted_matrix = json.loads(json.dumps(matrix)) + drifted_matrix["gate_g_promotion_evidence"]["promotion"]["merge_commit"] = "0" * 40 + with pytest.raises(AssertionError, match="missing Gate G promotion anchor: merge_commit"): + _assert_status_claims(status, drifted_matrix) + + removed_promotion = json.loads(json.dumps(matrix)) + del removed_promotion["gate_g_promotion_evidence"] + with pytest.raises(AssertionError, match="missing gate_g_promotion_evidence"): + _assert_status_claims(status, removed_promotion) + + gate_g_marker = "independent gate g validation passed" + dropped_gate_g = re.sub( + re.escape(gate_g_marker), "gate g marker removed", status, flags=re.IGNORECASE + ) + assert dropped_gate_g != status + with pytest.raises( + AssertionError, match=re.escape("missing independent Gate G validation statement") + ): + _assert_status_claims(dropped_gate_g, matrix) + pending_matrix = json.loads(json.dumps(matrix)) pending_matrix["ceilings"]["independent_validation"] = False del pending_matrix["independent_validation_evidence"] + del pending_matrix["gate_g_promotion_evidence"] pending_status = status.replace( - "SCOPED INDEPENDENT GATE F REPOSITORY VALIDATION PASSED", + "SCOPED INDEPENDENT GATE G VALIDATION PASSED AND MERGED TO MAIN", "PENDING INDEPENDENT VALIDATION", ) _assert_status_claims(pending_status, pending_matrix) @@ -354,6 +432,113 @@ def test_gate_f_exclusions_are_exact_and_present_in_both_front_doors(repo_root: ) +def _normalize_readme_claims(text: str) -> str: + """Remove presentation syntax while preserving the words that carry a claim.""" + return " ".join(re.sub(r"[^a-z0-9]+", " ", text.casefold()).split()) + + +def _assert_readme_independent_validation_is_scoped( + text: str, gate_f: dict[str, object], gate_g: dict[str, object] +) -> None: + normalized = _normalize_readme_claims(text) + subject = ( + r"(?:this\s+)?(?:current\s+repository(?:\s+head)?|repository\s+head|" + r"current\s+(?:head|commit|revision|release)|release|main|implementation)" + ) + validation = r"(?:independently\s+validated|independent\s+validation)" + bridge = r"(?:\s+[a-z0-9]+){0,5}\s+" + unscoped = re.search( + rf"\b(?:{subject}{bridge}{validation}|{validation}{bridge}{subject})\b", + normalized, + ) + assert unscoped is None, f"unscoped README independent-validation claim: {unscoped.group(0)!r}" + + for label, marker, evidence in ( + ( + "Gate F", + "independent gate f repository validation passed for candidate", + gate_f, + ), + ("Gate G", "independent gate g validation passed for candidate", gate_g), + ): + candidate_commit = evidence["candidate_commit"] + candidate_tree = evidence["candidate_tree"] + assert isinstance(candidate_commit, str) + assert isinstance(candidate_tree, str) + scoped_claim = f"{marker} {candidate_commit} tree {candidate_tree}".lower() + assert scoped_claim in normalized, ( + f"README.md {label} independent-validation statement is not bound " + "to its exact candidate commit and tree" + ) + + +def test_readme_numeric_claims_are_bound_to_their_source_evidence(repo_root: Path) -> None: + """Close GG001-M01: every README validation claim must name its source candidate. + + The Gate F and Gate G numeric results are only meaningful next to the exact commit + and tree that produced them. This control reads the anchors from the live matrix, + so stripping the citation from README.md while leaving the numbers in place fails + closed, and so does changing an anchor in the matrix alone. + """ + matrix = json.loads( + (repo_root / "docs/capabilities/CAPABILITY_MATRIX.json").read_text(encoding="utf-8") + ) + readme = (repo_root / "README.md").read_text(encoding="utf-8") + normalized = " ".join(readme.lower().replace("*", "").split()) + + gate_f = matrix["independent_validation_evidence"] + gate_g = matrix["gate_g_promotion_evidence"] + + required_anchors = { + "gate_f_candidate_commit": gate_f["candidate_commit"], + "gate_f_candidate_tree": gate_f["candidate_tree"], + "gate_g_candidate_commit": gate_g["candidate_commit"], + "gate_g_candidate_tree": gate_g["candidate_tree"], + "merge_commit": gate_g["promotion"]["merge_commit"], + "merge_first_parent": gate_g["promotion"]["merge_first_parent"], + "merge_second_parent": gate_g["promotion"]["merge_second_parent"], + } + for label, anchor in required_anchors.items(): + assert isinstance(anchor, str) and anchor.lower() in normalized, ( + f"README.md numeric claim is unscoped: missing {label}" + ) + + # A numeric result may not appear without the commit that produced it. + gate_f_passed = gate_f["canonical_linux"]["passed"] + gate_g_passed = gate_g["canonical_linux"]["passed"] + for passed, anchor_label in ( + (gate_f_passed, "gate_f_candidate_commit"), + (gate_g_passed, "gate_g_candidate_commit"), + ): + assert f"{passed} passed" in normalized, f"missing README result for {anchor_label}" + + _assert_readme_independent_validation_is_scoped(readme, gate_f, gate_g) + + unscoped_claims = ( + "This current repository head is independently validated.", + "**THIS CURRENT**\nrepository HEAD -- is independently validated!!!", + "The current head is independently validated.", + "This release has independent validation.", + "*This Implementation* is independently validated.", + "Main is independently validated.", + ) + for claim in unscoped_claims: + mutated = f"{readme}\n{claim}\n" + assert _normalize_readme_claims(claim) in _normalize_readme_claims(mutated) + with pytest.raises(AssertionError, match="unscoped README independent-validation claim"): + _assert_readme_independent_validation_is_scoped(mutated, gate_f, gate_g) + + scoped_claims = ( + "Independent Gate F repository validation passed for candidate " + f"{gate_f['candidate_commit']} (tree {gate_f['candidate_tree']}).", + "Independent Gate G validation passed for candidate " + f"{gate_g['candidate_commit']} (tree {gate_g['candidate_tree']}).", + ) + normalized_claims = _normalize_readme_claims(readme) + for claim in scoped_claims: + assert _normalize_readme_claims(claim) in normalized_claims + + @pytest.mark.parametrize("mutation", ("delete", "substitute", "reorder", "add")) def test_matrix_gate_f_exclusion_mutations_fail_closed(repo_root: Path, mutation: str) -> None: import importlib.util diff --git a/tests/contract/test_semantic_promotion.py b/tests/contract/test_semantic_promotion.py index 2058082..e2c1794 100644 --- a/tests/contract/test_semantic_promotion.py +++ b/tests/contract/test_semantic_promotion.py @@ -196,3 +196,62 @@ def test_forged_independent_validation_evidence_is_refused( path.write_text(json.dumps(matrix), encoding="utf-8") with pytest.raises(module.GateEvidenceError, match="evidence forged"): module.validate_bounded_record(gate_tree) + + +def test_gate_g_promotion_evidence_removal_is_refused(repo_root: Path, gate_tree: Path) -> None: + module = gate(repo_root) + path = gate_tree / "docs/capabilities/CAPABILITY_MATRIX.json" + matrix = json.loads(path.read_bytes()) + del matrix["gate_g_promotion_evidence"] + path.write_text(json.dumps(matrix), encoding="utf-8") + with pytest.raises(module.GateEvidenceError): + module.validate_bounded_record(gate_tree) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("candidate_commit", "0" * 40), + ("candidate_tree", "0" * 40), + ("status", "GATE_G_FAIL"), + ("work_order", "OIC-INDEPENDENT-GATE-G-999"), + ], +) +def test_forged_gate_g_promotion_evidence_is_refused( + repo_root: Path, gate_tree: Path, field: str, value: str +) -> None: + module = gate(repo_root) + path = gate_tree / "docs/capabilities/CAPABILITY_MATRIX.json" + matrix = json.loads(path.read_bytes()) + matrix["gate_g_promotion_evidence"][field] = value + path.write_text(json.dumps(matrix), encoding="utf-8") + with pytest.raises(module.GateEvidenceError, match="gate g promotion evidence forged"): + module.validate_bounded_record(gate_tree) + + +@pytest.mark.parametrize( + "field", + ["merge_commit", "merge_first_parent", "merge_second_parent", "merge_tree", "approved_by"], +) +def test_forged_gate_g_merge_topology_is_refused( + repo_root: Path, gate_tree: Path, field: str +) -> None: + """A forged merge parent, tree, commit or approver must fail closed.""" + module = gate(repo_root) + path = gate_tree / "docs/capabilities/CAPABILITY_MATRIX.json" + matrix = json.loads(path.read_bytes()) + matrix["gate_g_promotion_evidence"]["promotion"][field] = "forged" + path.write_text(json.dumps(matrix), encoding="utf-8") + with pytest.raises(module.GateEvidenceError, match="gate g promotion evidence forged"): + module.validate_bounded_record(gate_tree) + + +def test_gate_g_promotion_evidence_matches_the_gate_g_candidate(repo_root: Path) -> None: + """The recorded merge must carry the Gate G candidate as its second parent.""" + module = gate(repo_root) + evidence = module.GATE_G_PROMOTION_EVIDENCE + promotion = evidence["promotion"] + assert promotion["merge_second_parent"] == evidence["candidate_commit"] + assert promotion["merge_tree"] == evidence["candidate_tree"] + assert promotion["merge_first_parent"] == promotion["base_before_merge"] + assert evidence["exclusions"] == module.INDEPENDENT_VALIDATION_EVIDENCE["exclusions"]