2026-10-29 20:34:52 +01:00
|
|
|
"""Tasks 11.3 + 11.4: extraction and verified promotion of target knowledge (Reqs 2.9, 2.10).
|
2026-10-29 20:29:15 +01:00
|
|
|
|
|
|
|
|
Extraction reads the legacy recognition strategies and the promoted fact rows and
|
|
|
|
|
returns deterministic candidate records carrying source, version and region
|
2026-10-29 20:34:52 +01:00
|
|
|
evidence. It never modifies promoted outputs. Verification and promotion accept
|
|
|
|
|
only evidence-complete, non-contradicting proposals; rejected proposals leave the
|
|
|
|
|
promoted corpus unchanged.
|
2026-10-29 20:29:15 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import hashlib
|
|
|
|
|
import re
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from openultrasast.tools.fact_init import (
|
2026-10-29 20:49:39 +01:00
|
|
|
AcquisitionRecord,
|
|
|
|
|
AdmissionLabelsExposed,
|
2026-10-29 20:29:15 +01:00
|
|
|
CandidateRecord,
|
2026-10-29 20:34:52 +01:00
|
|
|
PromotionResult,
|
2026-10-29 20:49:39 +01:00
|
|
|
acquire,
|
2026-10-29 20:29:15 +01:00
|
|
|
extract_all,
|
|
|
|
|
extract_fact_rows,
|
|
|
|
|
extract_legacy_strategies,
|
2026-10-29 20:49:39 +01:00
|
|
|
population_is_spent,
|
2026-10-29 20:34:52 +01:00
|
|
|
promote_proposals,
|
|
|
|
|
verify_candidate,
|
2026-10-29 20:29:15 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
REPO_ROOT = Path(__file__).parents[1]
|
|
|
|
|
RULESET_DIR = REPO_ROOT / "src" / "openultrasast" / "ruleset" / "obligations"
|
|
|
|
|
MAPPING_FILE = REPO_ROOT / "src" / "openultrasast" / "mapping" / "legacy.py"
|
|
|
|
|
|
|
|
|
|
_VERSION_PATTERN = re.compile(r"^[0-9a-f]{12}$")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _file_digest(path: Path) -> str:
|
|
|
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()[:12]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _digest_dir(directory: Path) -> dict[str, str]:
|
|
|
|
|
return {path.name: _file_digest(path) for path in sorted(directory.glob("*.toml"))}
|
|
|
|
|
|
|
|
|
|
|
2026-10-29 20:34:52 +01:00
|
|
|
def _proposal(
|
|
|
|
|
name: str = "proposed-read", payload: tuple[tuple[str, str], ...] = (("calls", "read"), ("kind", "protected_read"))
|
|
|
|
|
) -> CandidateRecord:
|
|
|
|
|
return CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name=name,
|
|
|
|
|
source="docs/target.md",
|
|
|
|
|
version="a1b2c3d4e5f6",
|
|
|
|
|
region="docs/target.md:proposed-read",
|
|
|
|
|
payload=payload,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _promoted_corpus() -> tuple[CandidateRecord, ...]:
|
|
|
|
|
return (
|
|
|
|
|
CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name="promoted-read",
|
|
|
|
|
source="src/openultrasast/ruleset/obligations/java.toml",
|
|
|
|
|
version="0123456789ab",
|
|
|
|
|
region="java.toml:operation:promoted-read",
|
|
|
|
|
payload=(("calls", "read"), ("kind", "protected_read")),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_verified_novel_proposal_is_accepted_and_promoted() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
is_valid, reason = verify_candidate(_proposal(), promoted)
|
|
|
|
|
assert is_valid is True
|
|
|
|
|
assert reason is None
|
|
|
|
|
result = promote_proposals((_proposal(),), promoted)
|
|
|
|
|
assert result.accepted == (_proposal(),)
|
|
|
|
|
assert result.rejected == ()
|
|
|
|
|
assert _proposal() in result.promoted_after
|
|
|
|
|
assert promoted[0] in result.promoted_after
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_contradicted_proposal_is_rejected_and_promoted_knowledge_unchanged() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
contradicted = CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name="promoted-read",
|
|
|
|
|
source="docs/target.md",
|
|
|
|
|
version="a1b2c3d4e5f6",
|
|
|
|
|
region="docs/target.md:promoted-read",
|
|
|
|
|
payload=(("calls", "write"),),
|
|
|
|
|
)
|
|
|
|
|
is_valid, reason = verify_candidate(contradicted, promoted)
|
|
|
|
|
assert is_valid is False
|
|
|
|
|
assert reason is not None and reason.startswith("contradicts-promoted")
|
|
|
|
|
result = promote_proposals((contradicted,), promoted)
|
|
|
|
|
assert result.rejected[0][0] == contradicted
|
|
|
|
|
assert result.promoted_after == promoted
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evidence_free_proposal_is_rejected() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
for broken in (
|
|
|
|
|
_proposal().__class__(**{**_proposal().__dict__, "source": ""}),
|
|
|
|
|
CandidateRecord(kind="operation", name="x", source="s", version="short", region="r"),
|
|
|
|
|
CandidateRecord(kind="operation", name="y", source="s", version="a1b2c3d4e5f6", region=""),
|
|
|
|
|
):
|
|
|
|
|
is_valid, reason = verify_candidate(broken, promoted)
|
|
|
|
|
assert is_valid is False
|
|
|
|
|
assert reason is not None and reason.startswith("missing-evidence")
|
|
|
|
|
result = promote_proposals(
|
|
|
|
|
(CandidateRecord(kind="operation", name="z", source="", version="a1b2c3d4e5f6", region="r"),),
|
|
|
|
|
promoted,
|
|
|
|
|
)
|
|
|
|
|
assert result.promoted_after == promoted
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_exact_match_proposal_is_accepted_without_duplicate() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
twin = CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name="promoted-read",
|
|
|
|
|
source="docs/target.md",
|
|
|
|
|
version="a1b2c3d4e5f6",
|
|
|
|
|
region="docs/target.md:promoted-read",
|
|
|
|
|
payload=promoted[0].payload,
|
|
|
|
|
)
|
|
|
|
|
result = promote_proposals((twin,), promoted)
|
|
|
|
|
assert result.accepted == (twin,)
|
|
|
|
|
assert result.rejected == ()
|
|
|
|
|
same_knowledge = [record for record in result.promoted_after if record.kind == twin.kind and record.name == twin.name]
|
|
|
|
|
assert len(same_knowledge) == 1
|
|
|
|
|
assert len(result.promoted_after) == len(promoted)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_rejected_proposals_never_modify_promoted_corpus() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
contradicted = CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name="promoted-read",
|
|
|
|
|
source="docs/target.md",
|
|
|
|
|
version="a1b2c3d4e5f6",
|
|
|
|
|
region="docs/target.md:promoted-read",
|
|
|
|
|
payload=(("calls", "purge"),),
|
|
|
|
|
)
|
|
|
|
|
result = promote_proposals((_proposal(), contradicted), promoted)
|
|
|
|
|
assert result.accepted == (_proposal(),)
|
|
|
|
|
assert len(result.rejected) == 1
|
|
|
|
|
assert contradicted not in result.promoted_after
|
|
|
|
|
assert promoted[0] in result.promoted_after
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_promotion_is_deterministic() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
first = promote_proposals((_proposal(),), promoted)
|
|
|
|
|
second = promote_proposals((_proposal(),), promoted)
|
|
|
|
|
assert first == second
|
|
|
|
|
assert isinstance(first, PromotionResult)
|
|
|
|
|
|
|
|
|
|
|
2026-10-29 20:29:15 +01:00
|
|
|
def test_fact_rows_extraction_yields_operation_and_discharger_records() -> None:
|
|
|
|
|
records = extract_fact_rows(RULESET_DIR)
|
|
|
|
|
kinds = {record.kind for record in records}
|
|
|
|
|
assert "operation" in kinds
|
|
|
|
|
assert "discharger" in kinds
|
|
|
|
|
assert records
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_extraction_includes_java_operation_rows() -> None:
|
|
|
|
|
records = extract_fact_rows(RULESET_DIR)
|
|
|
|
|
java_operations = [record for record in records if record.kind == "operation" and record.source.endswith("java.toml")]
|
|
|
|
|
assert java_operations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_legacy_strategy_extraction_names_recognisers() -> None:
|
|
|
|
|
records = extract_legacy_strategies(MAPPING_FILE)
|
|
|
|
|
names = {record.name for record in records}
|
2026-10-29 20:59:37 +01:00
|
|
|
# _stapler_entries migrated to mapping/stapler.py (task 11.6); legacy keeps the rest.
|
|
|
|
|
assert "_stapler_entries" not in names
|
2026-10-29 20:29:15 +01:00
|
|
|
assert "_jetty_entries" in names
|
|
|
|
|
assert "_python_registered_routes" in names
|
|
|
|
|
for record in records:
|
|
|
|
|
assert record.region.startswith("legacy.py:")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_every_candidate_carries_source_version_and_region() -> None:
|
|
|
|
|
records = extract_all(REPO_ROOT)
|
|
|
|
|
assert records
|
|
|
|
|
for record in records:
|
|
|
|
|
assert record.source
|
|
|
|
|
assert _VERSION_PATTERN.match(record.version)
|
|
|
|
|
assert ":" in record.region
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_version_digest_is_file_derived_and_stable() -> None:
|
|
|
|
|
first = extract_fact_rows(RULESET_DIR)
|
|
|
|
|
second = extract_fact_rows(RULESET_DIR)
|
|
|
|
|
java_rows = [record for record in first if record.source.endswith("java.toml")]
|
|
|
|
|
assert java_rows
|
|
|
|
|
expected = _file_digest(RULESET_DIR / "java.toml")
|
|
|
|
|
assert all(record.version == expected for record in java_rows)
|
|
|
|
|
assert first == second
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_extraction_is_deterministic_and_sorted() -> None:
|
|
|
|
|
first = extract_all(REPO_ROOT)
|
|
|
|
|
second = extract_all(REPO_ROOT)
|
|
|
|
|
assert first == second
|
|
|
|
|
keys = [(record.kind, record.name, record.region) for record in first]
|
|
|
|
|
assert keys == sorted(keys)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_extraction_changes_no_promoted_outputs() -> None:
|
|
|
|
|
from openultrasast.mapping import registry
|
|
|
|
|
|
|
|
|
|
tomls_before = _digest_dir(RULESET_DIR)
|
|
|
|
|
mapping_before = _file_digest(MAPPING_FILE)
|
|
|
|
|
strategies_before = len(registry._STRATEGIES)
|
|
|
|
|
|
|
|
|
|
extract_all(REPO_ROOT)
|
|
|
|
|
|
|
|
|
|
assert _digest_dir(RULESET_DIR) == tomls_before
|
|
|
|
|
assert _file_digest(MAPPING_FILE) == mapping_before
|
|
|
|
|
assert len(registry._STRATEGIES) == strategies_before
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_operation_payload_carries_row_fields() -> None:
|
|
|
|
|
records = extract_fact_rows(RULESET_DIR)
|
|
|
|
|
java_operations = [record for record in records if record.kind == "operation" and record.source.endswith("java.toml")]
|
|
|
|
|
fields = {name for name, _ in java_operations[0].payload}
|
|
|
|
|
assert "calls" in fields
|
|
|
|
|
assert "kind" in fields
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_candidate_record_is_frozen() -> None:
|
|
|
|
|
record = CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name="example",
|
|
|
|
|
source="java.toml",
|
|
|
|
|
version="0" * 12,
|
|
|
|
|
region="java.toml:operations:example",
|
|
|
|
|
payload=(("language", "java"),),
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(Exception): # noqa: B017
|
|
|
|
|
record.name = "other" # type: ignore[misc]
|
2026-10-29 20:49:39 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_label_blind_acquire_records_provenance() -> None:
|
|
|
|
|
record = acquire((_proposal(), _contradicted_proposal()), _promoted_corpus())
|
|
|
|
|
assert isinstance(record, AcquisitionRecord)
|
|
|
|
|
assert _VERSION_PATTERN.fullmatch(record.input_revision)
|
|
|
|
|
assert _VERSION_PATTERN.fullmatch(record.evidence_digest)
|
|
|
|
|
assert _VERSION_PATTERN.fullmatch(record.output_digest)
|
|
|
|
|
assert record.accepted_names == ("proposed-read",)
|
|
|
|
|
assert record.rejected_names == ("promoted-read",)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_admission_labels_are_rejected_before_promotion() -> None:
|
|
|
|
|
promoted = _promoted_corpus()
|
|
|
|
|
with pytest.raises(AdmissionLabelsExposed):
|
|
|
|
|
acquire((_proposal(),), promoted, input_labels=("vulnerability-fix-label",))
|
|
|
|
|
assert promoted == _promoted_corpus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_every_forbidden_marker_raises() -> None:
|
|
|
|
|
for label in ("fix-location:Handler.java:4", "ordinary-change:repo-a", "vulnerability:case-9"):
|
|
|
|
|
with pytest.raises(AdmissionLabelsExposed):
|
|
|
|
|
acquire((_proposal(),), _promoted_corpus(), input_labels=(label,))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_acquire_is_deterministic() -> None:
|
|
|
|
|
first = acquire((_proposal(),), _promoted_corpus())
|
|
|
|
|
second = acquire((_proposal(),), _promoted_corpus())
|
|
|
|
|
assert first == second
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_population_is_spent_detects_output_change() -> None:
|
|
|
|
|
blind = acquire((_proposal(),), _promoted_corpus())
|
|
|
|
|
assert population_is_spent(blind, blind.output_digest) is False
|
|
|
|
|
assert population_is_spent(blind, "0" * 12) is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_acquisition_record_is_frozen() -> None:
|
|
|
|
|
record = acquire((_proposal(),), _promoted_corpus())
|
|
|
|
|
with pytest.raises(Exception): # noqa: B017
|
|
|
|
|
record.output_digest = "0" * 12 # type: ignore[misc]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _contradicted_proposal() -> CandidateRecord:
|
|
|
|
|
return CandidateRecord(
|
|
|
|
|
kind="operation",
|
|
|
|
|
name="promoted-read",
|
|
|
|
|
source="proposals.toml",
|
|
|
|
|
version="a" * 12,
|
|
|
|
|
region="proposals.toml:operation:promoted-read",
|
|
|
|
|
payload=(("calls", "write"), ("kind", "protected_read")),
|
|
|
|
|
)
|