mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 01:48:17 +00:00
feat(detection-core): isolate legacy compatibility shims
This commit is contained in:
@@ -74,6 +74,85 @@ class LegacyMigrationInventory:
|
||||
entries: tuple[LegacyInventoryEntry, ...]
|
||||
|
||||
|
||||
_APPROVED_MIXED_TRANSFERS: dict[
|
||||
str,
|
||||
tuple[tuple[str, str, str, str, tuple[str, ...], str], str, tuple[str, ...]],
|
||||
] = {
|
||||
"9eb4cba855044fe025daa7a523458c4fba0b12758ca604c894849f824f55ccb4": (
|
||||
(
|
||||
"src/openultrasast/core/factpack.py",
|
||||
"<module>",
|
||||
"reader",
|
||||
"v1-contract",
|
||||
("import_from:openultrasast.core._legacy_factpack:FactPackError",),
|
||||
"core",
|
||||
),
|
||||
"openultrasast.core.legacy_v1.contracts",
|
||||
("FactPackError",),
|
||||
),
|
||||
"1b90ea5474dff89121dc1317a86892bb0d68f2c64117923c5979cf4bb0cd8b76": (
|
||||
(
|
||||
"src/openultrasast/core/factpack.py",
|
||||
"<module>",
|
||||
"reader",
|
||||
"v1-contract",
|
||||
("import_from:openultrasast.core._legacy_factpack:admit_fact_pack",),
|
||||
"core",
|
||||
),
|
||||
"openultrasast.core.legacy_v1.contracts",
|
||||
("admit_fact_pack",),
|
||||
),
|
||||
"005f1fee3a1a659a65a45d8134bc71f061e314bdc28ba95516e870997c2648cd": (
|
||||
(
|
||||
"src/openultrasast/core/factpack.py",
|
||||
"<module>",
|
||||
"reader",
|
||||
"v1-contract",
|
||||
("import_from:openultrasast.core._legacy_factpack:admit_pack_file",),
|
||||
"core",
|
||||
),
|
||||
"openultrasast.core.legacy_v1.contracts",
|
||||
("admit_pack_file",),
|
||||
),
|
||||
"4c846ba1d97e5a5b4bbf8f1e99e10c40da10906ca93d68596cb987738438582a": (
|
||||
(
|
||||
"src/openultrasast/core/factpack.py",
|
||||
"<module>",
|
||||
"reader",
|
||||
"v1-contract",
|
||||
("import_from:openultrasast.core._legacy_factpack:load_pack_file",),
|
||||
"core",
|
||||
),
|
||||
"openultrasast.core.legacy_v1.contracts",
|
||||
("load_pack_file",),
|
||||
),
|
||||
"23e6ccf25c185ca231d73ab02570b14a9cddae27f7506e95d517e241bb693ae4": (
|
||||
(
|
||||
"src/openultrasast/core/factpack.py",
|
||||
"<module>",
|
||||
"reader",
|
||||
"v1-contract",
|
||||
("import_from:openultrasast.core._legacy_factpack:verify_fact_pack",),
|
||||
"core",
|
||||
),
|
||||
"openultrasast.core.legacy_v1.contracts",
|
||||
("verify_fact_pack",),
|
||||
),
|
||||
"c8aaf430009a592a8ed4361f38023d5faa12a6da6186fc0dcea9c7c522da85ad": (
|
||||
(
|
||||
"src/openultrasast/core/probes.py",
|
||||
"reversed_pair_probe",
|
||||
"writer",
|
||||
"v1-probe",
|
||||
("definition:reversed_pair_probe", "name:LegacyProbeResult"),
|
||||
"core",
|
||||
),
|
||||
"openultrasast.core.legacy_v1.contracts",
|
||||
("reversed_pair_probe", "LegacyProbeResult"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _object(value: object, fields: frozenset[str], name: str) -> dict[str, object]:
|
||||
if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
|
||||
raise LegacyInventoryError(f"{name} must be an object")
|
||||
@@ -311,6 +390,53 @@ def _explicit_legacy_shim(tree: ast.Module, symbol: str) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _stable_entry_fields(entry: LegacyInventoryEntry) -> tuple[str, str, str, str, tuple[str, ...], str]:
|
||||
return (
|
||||
entry.path,
|
||||
entry.symbol,
|
||||
entry.dependency_kind.value,
|
||||
entry.artifact_family,
|
||||
entry.capability_anchors,
|
||||
entry.owner_boundary,
|
||||
)
|
||||
|
||||
|
||||
def _read_owner_module(path: Path, description: str) -> ast.Module:
|
||||
try:
|
||||
content = path.read_bytes()
|
||||
except OSError as exc:
|
||||
raise LegacyInventoryError(f"{description} is not readable") from exc
|
||||
if not content:
|
||||
raise LegacyInventoryError(f"{description} must contain nonzero bytes")
|
||||
try:
|
||||
return ast.parse(content, filename=path.as_posix())
|
||||
except (SyntaxError, UnicodeDecodeError) as exc:
|
||||
raise LegacyInventoryError(f"{description} is not parseable") from exc
|
||||
|
||||
|
||||
def _moved_mixed_v1_owner(entry: LegacyInventoryEntry, *, root: Path) -> bool:
|
||||
transfer = _APPROVED_MIXED_TRANSFERS.get(entry.entry_id)
|
||||
if transfer is None or entry.entry_id != entry_identity(entry):
|
||||
return False
|
||||
expected_fields, destination_module, destination_symbols = transfer
|
||||
if _stable_entry_fields(entry) != expected_fields:
|
||||
return False
|
||||
facade_path = root / "src/openultrasast/core/legacy_v1/__init__.py"
|
||||
facade = _read_owner_module(facade_path, "compatibility facade")
|
||||
facade_imports = {
|
||||
(node.module, alias.name, alias.asname or alias.name)
|
||||
for node in facade.body
|
||||
if isinstance(node, ast.ImportFrom)
|
||||
for alias in node.names
|
||||
}
|
||||
if not all((destination_module, symbol, symbol) in facade_imports for symbol in destination_symbols):
|
||||
return False
|
||||
destination_path = root / "src" / Path(*destination_module.split("."))
|
||||
destination = _read_owner_module(destination_path.with_suffix(".py"), "compatibility destination")
|
||||
owned = {node.name for node in destination.body if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef))}
|
||||
return set(destination_symbols) <= owned
|
||||
|
||||
|
||||
def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Path) -> None:
|
||||
"""Prove every registered migration target is readable, nonzero, and present."""
|
||||
for entry in inventory.entries:
|
||||
@@ -326,10 +452,11 @@ def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Pat
|
||||
except (SyntaxError, UnicodeDecodeError) as exc:
|
||||
raise LegacyInventoryError(f"inventory source is not parseable: {entry.path}") from exc
|
||||
is_shim = _explicit_legacy_shim(tree, entry.symbol)
|
||||
if entry.symbol not in _defined_symbols(tree) and not is_shim:
|
||||
moved_owner = _moved_mixed_v1_owner(entry, root=root)
|
||||
if entry.symbol not in _defined_symbols(tree) and not is_shim and not moved_owner:
|
||||
raise LegacyInventoryError(f"inventory target is absent: {entry.path}:{entry.symbol}")
|
||||
missing = _missing_anchors(entry, tree)
|
||||
if missing and not is_shim:
|
||||
if missing and not is_shim and not moved_owner:
|
||||
raise LegacyInventoryError(f"inventory capability is absent: {entry.path}:{missing}")
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from openultrasast.core.budget import ( # noqa: E402
|
||||
from openultrasast.core.legacy_v1 import ( # noqa: E402
|
||||
Budget,
|
||||
BudgetGateFailure,
|
||||
Complexity,
|
||||
|
||||
@@ -12,6 +12,10 @@ from detection_core_fixtures import InMemoryContentResolver, stored_artifact
|
||||
|
||||
from openultrasast.core import budget as budget_v2
|
||||
from openultrasast.core import canonical, evidence, provenance, semantics
|
||||
from openultrasast.core.legacy_v1 import Budget as LegacyBudget
|
||||
from openultrasast.core.legacy_v1 import Complexity as LegacyComplexity
|
||||
from openultrasast.core.legacy_v1 import check_budget as legacy_check_budget
|
||||
from openultrasast.core.legacy_v1 import derive_budget as legacy_derive_budget
|
||||
|
||||
|
||||
def _digest(value: str) -> str:
|
||||
@@ -342,7 +346,7 @@ def test_scope_revalidates_post_construction_tampering() -> None:
|
||||
|
||||
|
||||
def test_legacy_budget_api_remains_available() -> None:
|
||||
samples = ((budget_v2.Complexity(1, 1, 10), 1.0), (budget_v2.Complexity(2, 2, 20), 2.0))
|
||||
legacy = budget_v2.derive_budget(samples, workload_class="legacy", percentile=0.5, hardware="runner")
|
||||
assert isinstance(legacy, budget_v2.Budget)
|
||||
budget_v2.check_budget(change_id="change", seconds=1.0, budget=legacy)
|
||||
samples = ((LegacyComplexity(1, 1, 10), 1.0), (LegacyComplexity(2, 2, 20), 2.0))
|
||||
legacy = legacy_derive_budget(samples, workload_class="legacy", percentile=0.5, hardware="runner")
|
||||
assert isinstance(legacy, LegacyBudget)
|
||||
legacy_check_budget(change_id="change", seconds=1.0, budget=legacy)
|
||||
|
||||
@@ -5,10 +5,22 @@ from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from openultrasast.core.coverage import Coverage, InstrumentFailure, check_reads # noqa: E402
|
||||
from openultrasast.core.provenance import ProvenanceMismatch, stamp, verify_stamp # noqa: E402
|
||||
from openultrasast.core.trace import Guard, Hop, TraceRecord, Witness # noqa: E402
|
||||
from openultrasast.core.verdict import BoundedSilence, Judgment, ProvenClaim, as_clean # noqa: E402
|
||||
from openultrasast.core.legacy_v1 import ( # noqa: E402
|
||||
BoundedSilence,
|
||||
Coverage,
|
||||
Guard,
|
||||
Hop,
|
||||
InstrumentFailure,
|
||||
Judgment,
|
||||
ProvenanceMismatch,
|
||||
ProvenClaim,
|
||||
TraceRecord,
|
||||
Witness,
|
||||
as_clean,
|
||||
check_reads,
|
||||
stamp,
|
||||
verify_stamp,
|
||||
)
|
||||
|
||||
|
||||
def _trace() -> TraceRecord:
|
||||
|
||||
@@ -17,6 +17,7 @@ from openultrasast.core.coverage import (
|
||||
diagnose_first_zero,
|
||||
validate_instrument,
|
||||
)
|
||||
from openultrasast.core.legacy_v1 import InstrumentFailure as LegacyInstrumentFailure
|
||||
from openultrasast.core.semantics import RelationStatus
|
||||
|
||||
DIGEST_A = "a" * 64
|
||||
@@ -74,6 +75,15 @@ def test_input_manifest_and_receipts_validate_exactly() -> None:
|
||||
validate_instrument(_manifest(), _receipts())
|
||||
|
||||
|
||||
def test_v2_instrument_failure_is_distinct_from_legacy_and_owns_input_refusal() -> None:
|
||||
from openultrasast.core.coverage import InstrumentFailure
|
||||
|
||||
assert InstrumentFailure is not LegacyInstrumentFailure
|
||||
receipt = InputReadReceipt("src/app.py", "src/app.py", False, 12, DIGEST_A)
|
||||
with pytest.raises(InstrumentFailure, match="not opened"):
|
||||
validate_instrument(_manifest(), (receipt, _receipts()[1]))
|
||||
|
||||
|
||||
def test_zero_named_counts_are_allowed_for_measurements_but_not_unresolved_coverage() -> None:
|
||||
with pytest.raises(ValueError, match="unresolved reason count must be positive"):
|
||||
_funnel(
|
||||
@@ -84,6 +94,10 @@ def test_zero_named_counts_are_allowed_for_measurements_but_not_unresolved_cover
|
||||
)
|
||||
|
||||
|
||||
def test_legacy_instrument_failure_is_available_only_through_compatibility_boundary() -> None:
|
||||
assert LegacyInstrumentFailure.__module__ == "openultrasast.core.legacy_v1.contracts"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("opened", "byte_count", "digest", "match"),
|
||||
[
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from openultrasast.core.consumer import PushRequest, consume_verdict # noqa: E402
|
||||
from openultrasast.core.factpack import FactPackError, admit_fact_pack, verify_fact_pack # noqa: E402
|
||||
from openultrasast.core.legacy_v1 import FactPackError, admit_fact_pack, verify_fact_pack # noqa: E402
|
||||
from openultrasast.core.trace import TraceRecord # noqa: E402
|
||||
from openultrasast.core.verdict import BoundedSilence, Judgment, ProvenClaim # noqa: E402
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from openultrasast.core.factpack import FactPackError, admit_pack_file, verify_fact_pack # noqa: E402
|
||||
from openultrasast.core.legacy_v1 import FactPackError, admit_pack_file, verify_fact_pack # noqa: E402
|
||||
from openultrasast.mapping.installed_fact_authority import ( # noqa: E402
|
||||
InstalledAuthorityWireError,
|
||||
decode_fact_pack_bytes,
|
||||
|
||||
@@ -14,6 +14,20 @@ PURE_COMPATIBILITY_MODULES = (
|
||||
"verdict.py",
|
||||
"_legacy_factpack.py",
|
||||
)
|
||||
MIXED_LEGACY_NAMES = {
|
||||
"budget": {"Budget", "Complexity"},
|
||||
"coverage": {"Coverage", "ResolutionOutcome", "check_reads"},
|
||||
"factpack": {"FactPackError", "admit_fact_pack", "admit_pack_file", "load_pack_file", "verify_fact_pack"},
|
||||
"probes": {
|
||||
"FittedComponentBlocked",
|
||||
"LegacyProbeResult",
|
||||
"ShortcutDetected",
|
||||
"assert_no_shortcut",
|
||||
"require_admission_evidence",
|
||||
"reversed_pair_probe",
|
||||
},
|
||||
"provenance": {"ProvenanceMismatch", "stamp", "verify_stamp"},
|
||||
}
|
||||
|
||||
|
||||
def _trace() -> object:
|
||||
@@ -40,14 +54,24 @@ def test_legacy_contract_values_have_one_compatibility_owner() -> None:
|
||||
"AdmissionManifest",
|
||||
"AdmissionManifestError",
|
||||
"BoundedSilence",
|
||||
"Budget",
|
||||
"BudgetGateFailure",
|
||||
"Complexity",
|
||||
"Coverage",
|
||||
"FactPackError",
|
||||
"FittedComponentBlocked",
|
||||
"Guard",
|
||||
"Hop",
|
||||
"InstrumentFailure",
|
||||
"Judgment",
|
||||
"LegacyFactPack",
|
||||
"LegacyProbeResult",
|
||||
"ProvenClaim",
|
||||
"ProvenanceMismatch",
|
||||
"PushRequest",
|
||||
"PushVerdict",
|
||||
"ResolutionOutcome",
|
||||
"ShortcutDetected",
|
||||
"TraceRecord",
|
||||
"Witness",
|
||||
}
|
||||
@@ -61,6 +85,7 @@ def test_pure_compatibility_modules_define_no_contract_or_serializer() -> None:
|
||||
assert content, f"ownership instrument must read nonzero bytes from {name}"
|
||||
tree = ast.parse(content, filename=path.as_posix())
|
||||
assert not [node.name for node in tree.body if isinstance(node, ast.ClassDef)]
|
||||
assert not [node.name for node in tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
|
||||
assert not any(isinstance(node, (ast.Import, ast.ImportFrom)) and "json" in ast.unparse(node) for node in tree.body)
|
||||
assert any(isinstance(node, ast.ImportFrom) and node.module == "openultrasast.core.legacy_v1.contracts" for node in tree.body)
|
||||
|
||||
@@ -69,6 +94,7 @@ def test_writer_is_the_only_compatibility_json_serializer() -> None:
|
||||
namespace = REPO / "src/openultrasast/core/legacy_v1"
|
||||
paths = sorted(namespace.glob("*.py"))
|
||||
assert paths and all(path.read_bytes() for path in paths)
|
||||
assert {path.name for path in paths} == {"__init__.py", "contracts.py", "writer.py"}
|
||||
json_importers = []
|
||||
for path in paths:
|
||||
tree = ast.parse(path.read_bytes(), filename=path.as_posix())
|
||||
@@ -109,3 +135,44 @@ def test_historical_decoder_stays_independent_from_compatibility_namespace() ->
|
||||
tree = ast.parse(content, filename=path.as_posix())
|
||||
imports = {node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom)}
|
||||
assert "openultrasast.core.legacy_v1" not in imports
|
||||
|
||||
|
||||
def test_top_level_v1_api_imports_only_from_closed_namespace() -> None:
|
||||
import openultrasast.core as public_core
|
||||
from openultrasast.core import legacy_v1
|
||||
|
||||
path = REPO / "src/openultrasast/core/__init__.py"
|
||||
content = path.read_bytes()
|
||||
assert content
|
||||
tree = ast.parse(content, filename=path.as_posix())
|
||||
legacy_imports = {
|
||||
node.module
|
||||
for node in tree.body
|
||||
if isinstance(node, ast.ImportFrom) and any((alias.asname or alias.name) in public_core.__all__ for alias in node.names)
|
||||
}
|
||||
assert legacy_imports == {"openultrasast.core.legacy_v1"}
|
||||
assert all(getattr(public_core, name) is getattr(legacy_v1, name) for name in public_core.__all__)
|
||||
|
||||
|
||||
def test_mixed_v2_modules_expose_no_legacy_compatibility_names() -> None:
|
||||
import importlib
|
||||
|
||||
for module_name, legacy_names in MIXED_LEGACY_NAMES.items():
|
||||
module = importlib.import_module(f"openultrasast.core.{module_name}")
|
||||
assert legacy_names.isdisjoint(vars(module)), f"{module_name} still exposes {legacy_names & vars(module).keys()}"
|
||||
|
||||
|
||||
def test_closed_namespace_is_the_only_owner_of_moved_mixed_names() -> None:
|
||||
from openultrasast.core import legacy_v1
|
||||
|
||||
expected = set().union(*MIXED_LEGACY_NAMES.values())
|
||||
assert all(hasattr(legacy_v1, name) for name in expected)
|
||||
operation_names = {
|
||||
"assert_no_shortcut",
|
||||
"check_reads",
|
||||
"require_admission_evidence",
|
||||
"reversed_pair_probe",
|
||||
"stamp",
|
||||
"verify_stamp",
|
||||
}
|
||||
assert {getattr(legacy_v1, name).__module__ for name in operation_names} == {"openultrasast.core.legacy_v1.contracts"}
|
||||
|
||||
@@ -21,6 +21,15 @@ from core_migration_inventory import (
|
||||
)
|
||||
|
||||
INVENTORY = REPO / "tests/fixtures/detection_core/v1/migration_inventory.json"
|
||||
MIXED_V2_MODULES = frozenset(
|
||||
{
|
||||
"src/openultrasast/core/budget.py",
|
||||
"src/openultrasast/core/coverage.py",
|
||||
"src/openultrasast/core/factpack.py",
|
||||
"src/openultrasast/core/probes.py",
|
||||
"src/openultrasast/core/provenance.py",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -51,22 +60,18 @@ def test_reader_audit_covers_direct_legacy_submodule_bindings(
|
||||
|
||||
def test_reader_audit_covers_real_direct_legacy_submodule_reads() -> None:
|
||||
expected = {
|
||||
("tests/test_core_budget_v2.py", "module_attr:openultrasast.core.budget:Budget"),
|
||||
("tests/test_core_budget_v2.py", "module_attr:openultrasast.core.budget:Complexity"),
|
||||
("tests/test_core_probes.py", "module_attr:openultrasast.core.probes:FittedComponentBlocked"),
|
||||
("tests/test_core_probes.py", "module_attr:openultrasast.core.probes:LegacyProbeResult"),
|
||||
("tests/test_core_probes.py", "module_attr:openultrasast.core.probes:ShortcutDetected"),
|
||||
("tests/test_core_probes.py", "module_attr:openultrasast.core.probes:assert_no_shortcut"),
|
||||
("tests/test_core_probes.py", "module_attr:openultrasast.core.probes:require_admission_evidence"),
|
||||
("tests/test_core_probes.py", "module_attr:openultrasast.core.probes:reversed_pair_probe"),
|
||||
("tests/test_core_contract.py", "module_attr:openultrasast.core.verdict:__all__"),
|
||||
}
|
||||
paths = tuple(REPO / path for path in sorted({path for path, _ in expected}))
|
||||
paths = tuple(REPO / path for path in ("tests/test_core_budget_v2.py", "tests/test_core_contract.py", "tests/test_core_probes.py"))
|
||||
|
||||
report = audit_legacy_readers(paths)
|
||||
|
||||
assert report.files_read == 3 and report.bytes_read > 0
|
||||
assert expected <= set(report.capabilities)
|
||||
assert not any(
|
||||
capability.startswith(("module_attr:openultrasast.core.budget:", "module_attr:openultrasast.core.probes:"))
|
||||
for _path, capability in report.capabilities
|
||||
)
|
||||
|
||||
|
||||
def test_frozen_inventory_resolves_nonzero_sources_and_targets() -> None:
|
||||
@@ -185,6 +190,9 @@ def test_independent_reader_audit_matches_split_inventory_capabilities() -> None
|
||||
for path, _anchor in open_registered - current:
|
||||
source = (REPO / path).read_bytes()
|
||||
assert source, f"migration shim instrument must read nonzero bytes from {path}"
|
||||
if path in MIXED_V2_MODULES:
|
||||
assert b"openultrasast.core.legacy_v1" not in source
|
||||
continue
|
||||
assert b"openultrasast.core.legacy_v1" in source, f"open reader disappeared without an explicit compatibility shim: {path}"
|
||||
|
||||
|
||||
@@ -293,6 +301,55 @@ def test_inventory_refuses_unreadable_zero_and_unknown_targets(tmp_path: Path) -
|
||||
validate_inventory_sources(replace(frozen, entries=(unknown,)), root=tmp_path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mutation", ("symbol", "anchor"))
|
||||
def test_mixed_owner_transfer_refuses_rekeyed_inventory_entries(mutation: str) -> None:
|
||||
frozen = load_migration_inventory(INVENTORY)
|
||||
original = next(
|
||||
entry for entry in frozen.entries if entry.entry_id == "9eb4cba855044fe025daa7a523458c4fba0b12758ca604c894849f824f55ccb4"
|
||||
)
|
||||
if mutation == "symbol":
|
||||
changed = replace(original, symbol="InstrumentFailure", capability_anchors=("definition:InstrumentFailure",))
|
||||
else:
|
||||
changed = replace(
|
||||
original,
|
||||
capability_anchors=("import_from:openultrasast.core._legacy_factpack:InstrumentFailure",),
|
||||
)
|
||||
changed = replace(changed, entry_id=entry_identity(changed))
|
||||
|
||||
with pytest.raises(LegacyInventoryError, match="target|capability"):
|
||||
validate_inventory_sources(replace(frozen, entries=(changed,)), root=REPO)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("facade_source", "owner_source"),
|
||||
(
|
||||
(
|
||||
"from openultrasast.core.legacy_v1.writer import FactPackError\n",
|
||||
"class FactPackError(ValueError):\n pass\n",
|
||||
),
|
||||
(
|
||||
"from openultrasast.core.legacy_v1.contracts import FactPackError\n",
|
||||
"class AnotherContract:\n pass\n",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_mixed_owner_transfer_refuses_wrong_or_missing_destination(tmp_path: Path, facade_source: str, owner_source: str) -> None:
|
||||
frozen = load_migration_inventory(INVENTORY)
|
||||
original = next(
|
||||
entry for entry in frozen.entries if entry.entry_id == "9eb4cba855044fe025daa7a523458c4fba0b12758ca604c894849f824f55ccb4"
|
||||
)
|
||||
mixed = tmp_path / original.path
|
||||
mixed.parent.mkdir(parents=True)
|
||||
mixed.write_text("pass\n", encoding="utf-8")
|
||||
namespace = tmp_path / "src/openultrasast/core/legacy_v1"
|
||||
namespace.mkdir(parents=True)
|
||||
(namespace / "__init__.py").write_text(facade_source, encoding="utf-8")
|
||||
(namespace / "contracts.py").write_text(owner_source, encoding="utf-8")
|
||||
|
||||
with pytest.raises(LegacyInventoryError, match="capability"):
|
||||
validate_inventory_sources(replace(frozen, entries=(original,)), root=tmp_path)
|
||||
|
||||
|
||||
def test_inventory_refuses_changed_artifact_bytes(tmp_path: Path) -> None:
|
||||
frozen = load_migration_inventory(INVENTORY)
|
||||
artifact = frozen.historical_artifacts[0]
|
||||
|
||||
@@ -28,6 +28,14 @@ from openultrasast.core.family import (
|
||||
derive_security_object_id,
|
||||
family_contract_digest,
|
||||
)
|
||||
from openultrasast.core.legacy_v1 import (
|
||||
FittedComponentBlocked,
|
||||
LegacyProbeResult,
|
||||
ShortcutDetected,
|
||||
assert_no_shortcut,
|
||||
require_admission_evidence,
|
||||
reversed_pair_probe,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -393,15 +401,15 @@ def test_legacy_reversed_pair_probe_remains_compatible() -> None:
|
||||
def shape_score(change: Mapping[str, Any]) -> float:
|
||||
return float(change["lines"])
|
||||
|
||||
result = probes.reversed_pair_probe(shape_score, forward, reversed_change)
|
||||
result = reversed_pair_probe(shape_score, forward, reversed_change)
|
||||
assert result.separated is True
|
||||
with pytest.raises(probes.ShortcutDetected):
|
||||
probes.assert_no_shortcut(shape_score, forward, reversed_change)
|
||||
with pytest.raises(ShortcutDetected):
|
||||
assert_no_shortcut(shape_score, forward, reversed_change)
|
||||
|
||||
assert public_core.ProbeResult is probes.LegacyProbeResult
|
||||
assert public_core.ProbeResult is LegacyProbeResult
|
||||
|
||||
|
||||
def test_legacy_admission_guard_remains_compatible() -> None:
|
||||
probes.require_admission_evidence(holdout_passed=True, admission_decision="GO")
|
||||
with pytest.raises(probes.FittedComponentBlocked):
|
||||
probes.require_admission_evidence(holdout_passed=False, admission_decision="GO")
|
||||
require_admission_evidence(holdout_passed=True, admission_decision="GO")
|
||||
with pytest.raises(FittedComponentBlocked):
|
||||
require_admission_evidence(holdout_passed=False, admission_decision="GO")
|
||||
|
||||
Reference in New Issue
Block a user