101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""Tests for immutable classification and repair policy enforcement."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from swe_data_processing.policy import (
|
|
PolicyViolation,
|
|
enforce_classification_policy,
|
|
enforce_repair_policy,
|
|
)
|
|
|
|
|
|
def _classification() -> dict:
|
|
dimensions = {
|
|
"trajectory_integrity": "PASS",
|
|
"tool_integrity": "PASS",
|
|
"patch_presence": "PASS",
|
|
"patch_trajectory_consistency": "PASS",
|
|
"instruction_compliance": "PASS",
|
|
"verification_consistency": "PASS",
|
|
"final_claim_alignment": "PASS",
|
|
"patch_hygiene": "PASS",
|
|
"issue_patch_alignment": "PASS",
|
|
}
|
|
return {
|
|
"source_outcome_class": "POSITIVE_CANDIDATE",
|
|
"qc_decision": "ACCEPT_SILVER_POSITIVE",
|
|
"training_use": "SFT_FULL",
|
|
"qc_passed": True,
|
|
"hard_fail_codes": [],
|
|
"verification": {"status": "PASS_RELIABLE"},
|
|
"dimensions": dimensions,
|
|
}
|
|
|
|
|
|
def test_valid_silver_positive_passes() -> None:
|
|
enforce_classification_policy({"resolved": 1}, _classification())
|
|
|
|
|
|
def test_resolved_zero_cannot_be_promoted() -> None:
|
|
result = _classification()
|
|
result["source_outcome_class"] = "EXPLICIT_NEGATIVE"
|
|
with pytest.raises(PolicyViolation, match="resolved=0"):
|
|
enforce_classification_policy({"resolved": 0}, result)
|
|
|
|
|
|
def test_unknown_cannot_be_full_sft() -> None:
|
|
result = _classification()
|
|
result["source_outcome_class"] = "UNVERIFIED"
|
|
result["qc_decision"] = "HOLD_UNVERIFIED"
|
|
result["qc_passed"] = False
|
|
with pytest.raises(PolicyViolation, match="resolved=-1"):
|
|
enforce_classification_policy({"resolved": -1}, result)
|
|
|
|
|
|
def _repair_result() -> dict:
|
|
"""Return a minimal non-mutating repair result for policy tests."""
|
|
|
|
return {
|
|
"repair_decision": "NO_CHANGE",
|
|
"maximum_training_use": "HOLD",
|
|
"invariants": {
|
|
"resolved_unchanged": True,
|
|
"tool_outputs_unchanged": True,
|
|
"model_patch_unchanged": True,
|
|
"reference_patch_unchanged": True,
|
|
"no_synthetic_execution_result": True,
|
|
},
|
|
"operations": [],
|
|
"requires_second_review": True,
|
|
}
|
|
|
|
|
|
def test_nonmutating_repair_decision_rejects_operations() -> None:
|
|
"""A hold decision cannot smuggle in an operation the applier will ignore."""
|
|
|
|
result = _repair_result()
|
|
result["repair_decision"] = "REQUIRES_EXECUTION"
|
|
result["operations"] = [
|
|
{
|
|
"op": "REWRITE_FINAL_SUMMARY",
|
|
"target_turns": [3],
|
|
"preconditions": ["Turn 3 is an assistant summary"],
|
|
"replacement": "Honest summary",
|
|
"reason": "Align the claim with evidence",
|
|
}
|
|
]
|
|
with pytest.raises(PolicyViolation, match="must not contain"):
|
|
enforce_repair_policy(result)
|
|
|
|
|
|
def test_step_example_requires_operations() -> None:
|
|
"""Step-only salvage must include a concrete correction and truncation."""
|
|
|
|
result = _repair_result()
|
|
result["repair_decision"] = "CREATE_STEP_EXAMPLE"
|
|
result["maximum_training_use"] = "SFT_STEP_ONLY"
|
|
with pytest.raises(PolicyViolation, match="requires at least one"):
|
|
enforce_repair_policy(result)
|