feat(detection-core): complete single-writer v2 cutover

This commit is contained in:
Marius Ciepluch
2026-11-04 09:54:47 +01:00
parent 10e93e09b7
commit 586543d799
28 changed files with 1106 additions and 1417 deletions
+17 -3
View File
@@ -7,6 +7,8 @@ import subprocess
from dataclasses import dataclass
from pathlib import Path
from core_migration_scope import CUTOVER_DELETIONS
REPO = Path(__file__).resolve().parents[1]
_EXCLUDED_PREFIXES = ("benchmarks/fixtures/", "benchmarks/pairs/", "tests/fixtures/")
_LEGACY_IMPORTS: dict[str, frozenset[str] | None] = {
@@ -47,7 +49,7 @@ _LEGACY_IMPORTS: dict[str, frozenset[str] | None] = {
}
),
"openultrasast.core.budget": frozenset({"Budget", "Complexity"}),
"openultrasast.core.consumer": None,
"openultrasast.core.consumer": frozenset({"consume_verdict"}),
"openultrasast.core.coverage": frozenset({"Coverage", "InstrumentFailure", "ResolutionOutcome", "check_reads"}),
"openultrasast.core.factpack": frozenset({"FactPackError", "admit_fact_pack", "admit_pack_file", "load_pack_file", "verify_fact_pack"}),
"openultrasast.core.probes": frozenset(
@@ -62,7 +64,7 @@ _LEGACY_IMPORTS: dict[str, frozenset[str] | None] = {
),
"openultrasast.core.provenance": frozenset({"ProvenanceMismatch", "stamp", "verify_stamp"}),
"openultrasast.core.trace": None,
"openultrasast.core.verdict": None,
"openultrasast.core.verdict": frozenset({"Judgment", "ProvenClaim", "as_clean"}),
}
_TOP_LEVEL_COMPATIBILITY = _LEGACY_IMPORTS["openultrasast.core"] or frozenset()
@@ -80,10 +82,22 @@ class ReaderAuditReport:
def tracked_python() -> tuple[Path, ...]:
completed = subprocess.run(["git", "ls-files", "*.py"], cwd=REPO, check=True, capture_output=True, text=True)
deleted = set(
subprocess.run(
["git", "ls-files", "--deleted", "--", "*.py"],
cwd=REPO,
check=True,
capture_output=True,
text=True,
).stdout.splitlines()
)
unexpected = deleted - CUTOVER_DELETIONS
if unexpected:
raise LegacyReaderAuditError(f"unexpected tracked Python deletion: {min(unexpected)}")
return tuple(
REPO / relative
for relative in completed.stdout.splitlines()
if relative.endswith(".py") and not relative.startswith(_EXCLUDED_PREFIXES)
if relative.endswith(".py") and relative not in deleted and not relative.startswith(_EXCLUDED_PREFIXES)
)
+18 -6
View File
@@ -18,6 +18,7 @@ from core_migration_scope import (
COMPATIBILITY_PATHS,
COMPATIBILITY_SHIMS,
FROZEN_TOP_LEVEL_V1,
REPLACED_SHIM_MEMBERS,
REPO,
MigrationScopeError,
authoritative_cutover_scope,
@@ -29,9 +30,7 @@ from core_migration_scope import (
relative_path,
scope_identity,
)
from core_migration_scope import (
static_top_level_v1 as _static_top_level_v1,
)
from core_migration_scope import static_top_level_exports as _static_top_level_exports
class LegacyBoundaryError(ValueError):
@@ -71,9 +70,9 @@ class _Bindings:
import_functions: frozenset[str]
def static_top_level_v1(root: Path = REPO) -> tuple[str, ...]:
def static_top_level_exports(root: Path = REPO) -> tuple[str, ...]:
try:
return _static_top_level_v1(root)
return _static_top_level_exports(root)
except MigrationScopeError as exc:
raise LegacyBoundaryError(str(exc)) from exc
@@ -215,6 +214,19 @@ def _classify_import_from(
legacy.add(_site(relative, node, parents, "compatibility_shim_import", shim))
if module in COMPATIBILITY_SHIMS:
legacy.add(_site(relative, node, parents, "compatibility_shim_import", module))
_record_replaced_shim_members(relative, node, parents, module, names, legacy)
def _record_replaced_shim_members(
relative: str,
node: ast.ImportFrom,
parents: dict[ast.AST, ast.AST],
module: str,
names: set[str],
legacy: set[str],
) -> None:
for name in sorted(names.intersection(REPLACED_SHIM_MEMBERS.get(module, frozenset()))):
legacy.add(_site(relative, node, parents, "retired_shim_member_import", f"{module}.{name}"))
def _reference_sites(
@@ -449,5 +461,5 @@ __all__ = [
"boundary_python",
"inspect_legacy_boundary",
"require_cutover_ready",
"static_top_level_v1",
"static_top_level_exports",
]
+29 -1
View File
@@ -14,6 +14,7 @@ from pathlib import Path
from benchmark_migration_evidence import BENCHMARK_TERMINAL_EVIDENCE, BenchmarkTerminalEvidence
from consumer_migration_evidence import CONSUMER_TERMINAL_EVIDENCE, REVIEWED_SUCCESSOR_MODULES, ConsumerTerminalEvidence
from core_migration_scope import CUTOVER_DELETIONS
from openultrasast.core.legacy import LegacyArtifact, decode_legacy_artifact
@@ -830,16 +831,43 @@ def _approved_terminal_source_closure(entry: LegacyInventoryEntry, *, root: Path
return True
if _benchmark_terminal_source_closure(entry, root=root, tree=tree):
return True
transfer = _APPROVED_MIXED_TRANSFERS.get(entry.entry_id)
if (
transfer is not None
and entry.disposition is not MigrationDisposition.OPEN
and entry.entry_id == entry_identity(entry)
and _stable_entry_fields(entry) == transfer[0]
and _cutover_complete(root)
and not any(_exact_anchor_present(anchor, tree) for anchor in entry.capability_anchors)
):
return True
approved = _APPROVED_TERMINAL_SOURCE_CLOSURES.get(entry.entry_id) is entry.disposition
return approved and entry.entry_id == entry_identity(entry)
def _cutover_complete(root: Path) -> bool:
if any((root / relative).exists() for relative in CUTOVER_DELETIONS):
return False
public = tuple(root / f"src/openultrasast/core/{name}.py" for name in ("__init__", "admission", "consumer", "verdict"))
return all(path.is_file() and path.stat().st_size > 0 for path in public)
def _approved_cutover_deletion(entry: LegacyInventoryEntry, *, root: Path) -> bool:
if entry.disposition is MigrationDisposition.OPEN or entry.path not in CUTOVER_DELETIONS or entry.entry_id != entry_identity(entry):
return False
return _cutover_complete(root)
def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Path) -> None:
"""Prove sources are readable and every open migration target remains present."""
for entry in inventory.entries:
path = root / entry.path
try:
content = path.read_bytes()
except FileNotFoundError as exc:
if _approved_cutover_deletion(entry, root=root):
continue
raise LegacyInventoryError(f"inventory source is not readable: {entry.path}") from exc
except OSError as exc:
raise LegacyInventoryError(f"inventory source is not readable: {entry.path}") from exc
if not content:
@@ -849,8 +877,8 @@ 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)
moved_owner = _moved_mixed_v1_owner(entry, root=root)
terminal_closure = _approved_terminal_source_closure(entry, root=root, tree=tree)
moved_owner = False if terminal_closure else _moved_mixed_v1_owner(entry, root=root)
if entry.symbol not in _defined_symbols(tree) and not is_shim and not moved_owner and not terminal_closure:
raise LegacyInventoryError(f"inventory target is absent: {entry.path}:{entry.symbol}")
missing = _missing_anchors(entry, tree)
+36 -7
View File
@@ -15,10 +15,14 @@ COMPATIBILITY_PATHS = frozenset(
src/openultrasast/core/admission.py src/openultrasast/core/consumer.py
src/openultrasast/core/trace.py src/openultrasast/core/verdict.py""".split() # noqa: SIM905
)
COMPATIBILITY_SHIMS = frozenset(
"""openultrasast.core._legacy_factpack openultrasast.core.admission
openultrasast.core.consumer openultrasast.core.trace openultrasast.core.verdict""".split() # noqa: SIM905
)
COMPATIBILITY_SHIMS = frozenset(["openultrasast.core._legacy_factpack", "openultrasast.core.trace"])
REPLACED_SHIM_MEMBERS = {
"openultrasast.core.admission": frozenset(
{"REPORT_SCHEMA_KEYS", "AdmissionManifest", "AdmissionManifestError", "validate_admission_manifest", "validate_report_schema"}
),
"openultrasast.core.consumer": frozenset({"consume_verdict"}),
"openultrasast.core.verdict": frozenset({"Judgment", "ProvenClaim", "as_clean"}),
}
FROZEN_TOP_LEVEL_V1 = tuple(
"""REPORT_SCHEMA_KEYS AdmissionManifest AdmissionManifestError BoundedSilence Budget BudgetGateFailure
Complexity Coverage FactPack FactPackError FittedComponentBlocked Guard Hop InstrumentFailure Judgment ProbeResult
@@ -27,6 +31,17 @@ FROZEN_TOP_LEVEL_V1 = tuple(
require_admission_evidence reversed_pair_probe stamp validate_admission_manifest validate_report_schema
verify_fact_pack verify_stamp""".split() # noqa: SIM905
)
CUTOVER_DELETIONS = frozenset(
{
"src/openultrasast/core/_legacy_factpack.py",
"src/openultrasast/core/consumer_v2.py",
"src/openultrasast/core/legacy_v1/__init__.py",
"src/openultrasast/core/legacy_v1/contracts.py",
"src/openultrasast/core/legacy_v1/writer.py",
"src/openultrasast/core/trace.py",
"src/openultrasast/core/verdict_v2.py",
}
)
class MigrationScopeError(ValueError):
@@ -47,6 +62,19 @@ def boundary_python(root: Path = REPO) -> tuple[Path, ...]:
commands = (("git", "ls-files", "*.py"), ("git", "ls-files", "--others", "--exclude-standard", "--", "*.py"))
outputs = (subprocess.run(command, cwd=root, check=True, capture_output=True, text=True).stdout for command in commands)
relatives = {value for output in outputs for value in output.splitlines() if _owned_python(value)}
deleted = set(
subprocess.run(
("git", "ls-files", "--deleted", "--", "*.py"),
cwd=root,
check=True,
capture_output=True,
text=True,
).stdout.splitlines()
)
unexpected = deleted - CUTOVER_DELETIONS
if unexpected:
raise MigrationScopeError(f"unexpected tracked Python deletion: {min(unexpected)}")
relatives.difference_update(deleted)
return tuple(root / relative for relative in sorted(relatives))
@@ -149,8 +177,8 @@ def relative_path(path: Path, root: Path) -> str:
return path.as_posix()
def static_top_level_v1(root: Path = REPO) -> tuple[str, ...]:
"""Read the migration shim's literal ``__all__`` without importing it."""
def static_top_level_exports(root: Path = REPO) -> tuple[str, ...]:
"""Read the public package's literal ``__all__`` without importing it."""
path = root / "src/openultrasast/core/__init__.py"
try:
content = path.read_bytes()
@@ -185,6 +213,7 @@ __all__ = [
"FROZEN_TOP_LEVEL_V1",
"COMPATIBILITY_PATHS",
"COMPATIBILITY_SHIMS",
"CUTOVER_DELETIONS",
"MigrationScopeError",
"REPO",
"authoritative_cutover_scope",
@@ -197,5 +226,5 @@ __all__ = [
"relative_path",
"scope_identity_error",
"scope_identity",
"static_top_level_v1",
"static_top_level_exports",
]
+1 -1
View File
@@ -50,7 +50,7 @@ from openultrasast.core.family_catalog import FamilyCatalog, FamilyCatalogEntry,
from openultrasast.core.measurement import DenominatorPolicy, IntervalMethod, MetricId, ScopeKey, standard_metric_rules
from openultrasast.core.readiness import ReadinessResult
from openultrasast.core.semantics import RelationStatus
from openultrasast.core.verdict_v2 import derive_verdict_outputs
from openultrasast.core.verdict import derive_verdict_outputs
class _InputManifestSubclass(InputManifest):
+2 -2
View File
@@ -19,10 +19,10 @@ from test_core_validation_v2 import (
_with_fact_set,
)
import openultrasast.core.consumer_v2 as consumer_v2
import openultrasast.core.consumer as consumer_v2
from openultrasast.core import budget, canonical, evidence, validation
from openultrasast.core._proof_attestation import proof_attestation_bytes
from openultrasast.core.consumer_v2 import ConsumerContractError, PushRequest, consume_records, make_push_request
from openultrasast.core.consumer import ConsumerContractError, PushRequest, consume_records, make_push_request
from openultrasast.core.family import FamilyContract
from openultrasast.core.proof import (
DIFFERENTIAL_PROOF_SCHEMA,
+165
View File
@@ -0,0 +1,165 @@
"""Final deletion-backed gate for the detection-core v1 migration."""
from __future__ import annotations
import ast
import hashlib
import json
import subprocess
import sys
from pathlib import Path
from core_migration_boundary import BoundaryPhase, boundary_python, inspect_legacy_boundary, require_cutover_ready
from core_migration_inventory import load_migration_inventory, validate_historical_artifacts
REPO = Path(__file__).resolve().parents[1]
CORE = REPO / "src/openultrasast/core"
INVENTORY = REPO / "tests/fixtures/detection_core/v1/migration_inventory.json"
REMOVED_PATHS = (
CORE / "legacy_v1",
CORE / "trace.py",
CORE / "_legacy_factpack.py",
CORE / "consumer_v2.py",
CORE / "verdict_v2.py",
)
CANONICAL_V2_MODULES = ("admission", "consumer", "verdict")
OLD_TOP_LEVEL = (
"AdmissionManifest",
"Budget",
"Coverage",
"FactPack",
"PushRequest",
"PushVerdict",
"TraceRecord",
)
def _module_exports(path: Path) -> tuple[str, ...]:
content = path.read_bytes()
assert content, f"public-surface instrument must read nonzero bytes from {path}"
tree = ast.parse(content, filename=path.relative_to(REPO).as_posix())
declarations = [
node
for node in tree.body
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets)
]
assert len(declarations) == 1
value = declarations[0].value
assert isinstance(value, (ast.List, ast.Tuple))
assert all(isinstance(item, ast.Constant) and isinstance(item.value, str) for item in value.elts)
return tuple(item.value for item in value.elts)
def _fresh_import_probe(*module_names: str) -> dict[str, object]:
program = """
import importlib
import json
import sys
core = importlib.import_module("openultrasast" + ".core")
result = {
"all": tuple(core.__all__),
"old_top_level": tuple(name for name in sys.argv[1].split(",") if hasattr(core, name)),
"modules": {},
}
for name in sys.argv[2:]:
try:
module = importlib.import_module(name)
except ModuleNotFoundError:
result["modules"][name] = None
else:
result["modules"][name] = tuple(module.__all__)
print(json.dumps(result, sort_keys=True))
"""
completed = subprocess.run(
[sys.executable, "-c", program, ",".join(OLD_TOP_LEVEL), *module_names],
cwd=REPO,
check=True,
capture_output=True,
text=True,
env={"PYTHONPATH": str(REPO / "src")},
)
assert completed.stdout, "fresh import probe must produce nonzero output"
value = json.loads(completed.stdout)
assert isinstance(value, dict)
return value
def test_cutover_deletes_every_v1_write_and_temporary_shim_path() -> None:
for path in REMOVED_PATHS:
assert not path.exists(), f"cutover residue remains: {path.relative_to(REPO)}"
for name in CANONICAL_V2_MODULES:
path = CORE / f"{name}.py"
assert path.is_file()
assert path.stat().st_size > 0
def test_fresh_interpreter_rejects_old_imports_and_loads_canonical_v2() -> None:
old_modules = (
"openultrasast.core.legacy_v1",
"openultrasast.core.trace",
"openultrasast.core._legacy_factpack",
"openultrasast.core." + "consumer_v2",
"openultrasast.core." + "verdict_v2",
)
canonical = tuple(f"openultrasast.core.{name}" for name in CANONICAL_V2_MODULES)
result = _fresh_import_probe(*old_modules, *canonical)
assert result["old_top_level"] == []
assert set(CANONICAL_V2_MODULES) <= set(result["all"])
modules = result["modules"]
assert isinstance(modules, dict)
assert all(modules[name] is None for name in old_modules)
assert "AdmissionManifestV2" in modules[canonical[0]]
assert "consume_records" in modules[canonical[1]]
assert "derive_verdict_outputs" in modules[canonical[2]]
def test_public_v2_surface_has_no_frozen_v1_names() -> None:
exports = _module_exports(CORE / "__init__.py")
assert set(CANONICAL_V2_MODULES) <= set(exports)
assert set(OLD_TOP_LEVEL).isdisjoint(exports)
def test_repository_post_deletion_boundary_is_nonzero_and_exact_zero() -> None:
paths = boundary_python()
inventory = load_migration_inventory(INVENTORY)
report = inspect_legacy_boundary(
paths,
frozen_inventory=inventory,
candidate_inventory=inventory,
expected_scope=paths,
root=REPO,
phase=BoundaryPhase.POST_DELETION,
)
assert report.files_read == len(paths) > 0
assert report.bytes_read > 0
assert report.open_inventory_entries == ()
assert report.legacy_import_sites == ()
assert report.legacy_writer_sites == ()
assert report.consumer_legacy_import_sites == ()
assert report.consumer_legacy_writer_sites == ()
assert report.compatibility_implementation_sites == ()
assert report.forbidden_dynamic_sites == ()
require_cutover_ready(
report,
frozen_inventory=inventory,
candidate_inventory=inventory,
root=REPO,
)
def test_historical_decoder_remains_exact_read_only_and_outside_public_v2() -> None:
inventory_bytes = INVENTORY.read_bytes()
assert inventory_bytes, "historical gate must read nonzero inventory bytes"
inventory = load_migration_inventory(INVENTORY)
artifacts = validate_historical_artifacts(inventory, root=REPO)
assert len(artifacts) == 2
assert all(artifact.content for artifact in artifacts)
assert all(hashlib.sha256(artifact.content).hexdigest() == artifact.content_digest for artifact in artifacts)
legacy_exports = _module_exports(CORE / "legacy.py")
assert {"write", "serialize", "to_v2", "compare_identity"}.isdisjoint(legacy_exports)
assert "legacy" not in _module_exports(CORE / "__init__.py")
+16 -5
View File
@@ -14,7 +14,7 @@ from core_migration_boundary import (
boundary_python,
inspect_legacy_boundary,
require_cutover_ready,
static_top_level_v1,
static_top_level_exports,
)
from core_migration_inventory import (
LegacyInventoryError,
@@ -214,7 +214,9 @@ def test_cutover_refuses_empty_scope_and_migration_compatibility(tmp_path: Path)
def test_complete_frozen_top_level_surface_is_reported_and_star_is_forbidden(tmp_path: Path) -> None:
assert static_top_level_v1(REPO) == FROZEN_TOP_LEVEL_V1
exports = static_top_level_exports(REPO)
assert exports == ("admission", "consumer", "verdict")
assert set(exports).isdisjoint(FROZEN_TOP_LEVEL_V1)
assert len(FROZEN_TOP_LEVEL_V1) == 38
imports = ", ".join(FROZEN_TOP_LEVEL_V1)
attributes = "\n".join(f"value_{index} = core_api.{name}" for index, name in enumerate(FROZEN_TOP_LEVEL_V1))
@@ -283,16 +285,25 @@ def test_repository_scope_proves_nonzero_inputs_before_reporting_sites() -> None
paths = boundary_python()
frozen = load_migration_inventory(INVENTORY)
report = inspect_legacy_boundary(paths, frozen_inventory=frozen, root=REPO)
report = inspect_legacy_boundary(
paths,
frozen_inventory=frozen,
candidate_inventory=frozen,
expected_scope=paths,
root=REPO,
phase=BoundaryPhase.POST_DELETION,
)
assert report.files_read == len(paths) > 0
assert report.bytes_read > 0
assert report.legacy_import_sites
assert report.legacy_import_sites == ()
assert report.legacy_writer_sites == ()
assert report.open_inventory_entries == ()
assert report.consumer_legacy_import_sites == ()
assert report.consumer_legacy_writer_sites == ()
assert report.forbidden_dynamic_sites == ()
assert report.compatibility_implementation_sites
assert report.compatibility_implementation_sites == ()
_require_cutover(report, frozen=frozen, candidate=frozen, root=REPO)
def test_cutover_rejects_positive_but_incomplete_repository_scope() -> None:
+44 -68
View File
@@ -1,4 +1,4 @@
"""Structural ownership tests for the temporary v1 compatibility implementation."""
"""Structural ownership tests for the final v2-only core namespace."""
from __future__ import annotations
@@ -7,7 +7,7 @@ from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
CORE = REPO / "src/openultrasast/core"
PURE_COMPATIBILITY_MODULES = ("admission.py", "consumer.py", "trace.py", "verdict.py", "_legacy_factpack.py")
REMOVED_COMPATIBILITY = ("trace.py", "_legacy_factpack.py", "consumer_v2.py", "verdict_v2.py")
MIXED_LEGACY_NAMES = {
"budget": {"Budget", "Complexity"},
"coverage": {"Coverage", "ResolutionOutcome", "check_reads"},
@@ -38,76 +38,58 @@ def _imported(tree: ast.Module) -> set[str]:
return {alias.asname or alias.name for node in tree.body if isinstance(node, (ast.Import, ast.ImportFrom)) for alias in node.names}
def test_legacy_contract_values_have_one_compatibility_owner() -> None:
contracts = _tree(CORE / "legacy_v1/contracts.py")
expected = {
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"Budget",
"BudgetGateFailure",
"Complexity",
"Coverage",
"FactPackError",
"FittedComponentBlocked",
"Guard",
"Hop",
"InstrumentFailure",
"Judgment",
"LegacyFactPack",
"LegacyProbeResult",
"ProvenClaim",
"ProvenanceMismatch",
"PushRequest",
"PushVerdict",
"ResolutionOutcome",
"ShortcutDetected",
"TraceRecord",
"Witness",
}
assert expected <= _defined(contracts)
def test_pure_compatibility_modules_define_no_contract_or_serializer() -> None:
for name in PURE_COMPATIBILITY_MODULES:
tree = _tree(CORE / name)
assert not _defined(tree)
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)
def test_writer_is_the_only_compatibility_json_serializer() -> None:
namespace = CORE / "legacy_v1"
paths = sorted(namespace.glob("*.py"))
assert {path.name for path in paths} == {"__init__.py", "contracts.py", "writer.py"}
json_importers = [
path.name
for path in paths
if any(isinstance(node, (ast.Import, ast.ImportFrom)) and "json" in ast.unparse(node) for node in _tree(path).body)
def _literal_all(tree: ast.Module) -> tuple[str, ...]:
values = [
node.value
for node in tree.body
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "__all__" for target in node.targets)
]
assert json_importers == ["writer.py"]
assert len(values) == 1 and isinstance(values[0], (ast.List, ast.Tuple))
assert all(isinstance(item, ast.Constant) and isinstance(item.value, str) for item in values[0].elts)
return tuple(item.value for item in values[0].elts)
def test_namespace_and_v2_dependency_directions_are_closed() -> None:
for path in (CORE / "legacy_v1").glob("*.py"):
imports = {node.module for node in ast.walk(_tree(path)) if isinstance(node, ast.ImportFrom) and node.module}
assert all(module.startswith("openultrasast.core.legacy_v1") for module in imports if module.startswith("openultrasast"))
compatibility = {*PURE_COMPATIBILITY_MODULES, "legacy.py", "__init__.py"}
for path in CORE.glob("*.py"):
if path.name not in compatibility:
imports = {node.module for node in ast.walk(_tree(path)) if isinstance(node, ast.ImportFrom) and node.module}
assert not any(module.startswith("openultrasast.core.legacy_v1") for module in imports)
def test_compatibility_namespace_writer_and_temporary_shims_are_absent() -> None:
assert not (CORE / "legacy_v1").exists()
for name in REMOVED_COMPATIBILITY:
assert not (CORE / name).exists()
def test_historical_decoder_stays_independent_from_compatibility_namespace() -> None:
imports = {node.module for node in ast.walk(_tree(CORE / "legacy.py")) if isinstance(node, ast.ImportFrom)}
def test_canonical_v2_replacements_own_the_public_paths() -> None:
consumer = _tree(CORE / "consumer.py")
verdict = _tree(CORE / "verdict.py")
admission = _tree(CORE / "admission.py")
assert {"PushRequest", "PushVerdict", "consume_records"} <= _defined(consumer)
assert {"BoundedSilence", "DefectClaim", "derive_verdict_outputs"} <= _defined(verdict)
assert {"AdmissionManifestV2", "authorize_admission", "score_admission"} <= set(_literal_all(admission))
def test_core_modules_have_no_legacy_namespace_dependency() -> None:
paths = tuple(sorted(CORE.glob("*.py")))
assert paths and all(path.read_bytes() for path in paths)
for path in paths:
tree = _tree(path)
imports = {node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom) and node.module}
imported_modules = {alias.name for node in ast.walk(tree) if isinstance(node, ast.Import) for alias in node.names}
assert not any(module.startswith("openultrasast.core.legacy_v1") for module in imports | imported_modules)
def test_historical_decoder_stays_independent_and_read_only() -> None:
tree = _tree(CORE / "legacy.py")
imports = {node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom)}
exports = set(_literal_all(tree))
assert "openultrasast.core.legacy_v1" not in imports
assert {"write", "serialize", "to_v2", "compare_identity"}.isdisjoint(exports)
def test_top_level_v1_api_is_only_a_closed_namespace_shim() -> None:
def test_top_level_api_publishes_only_canonical_v2_modules() -> None:
tree = _tree(CORE / "__init__.py")
modules = {node.module for node in tree.body if isinstance(node, ast.ImportFrom)}
assert modules == {"openultrasast.core.legacy_v1"}
assert modules == {"openultrasast.core"}
assert _literal_all(tree) == ("admission", "consumer", "verdict")
assert not _defined(tree)
@@ -115,9 +97,3 @@ def test_mixed_v2_modules_expose_no_legacy_compatibility_names() -> None:
for module_name, legacy_names in MIXED_LEGACY_NAMES.items():
tree = _tree(CORE / f"{module_name}.py")
assert legacy_names.isdisjoint(_defined(tree) | _imported(tree))
def test_closed_namespace_is_the_only_owner_of_moved_mixed_names() -> None:
contracts = _tree(CORE / "legacy_v1/contracts.py")
expected = set().union(*MIXED_LEGACY_NAMES.values())
assert expected <= _defined(contracts)
+1 -1
View File
@@ -46,7 +46,7 @@ from openultrasast.core.semantics import (
make_observation,
make_relation,
)
from openultrasast.core.verdict_v2 import DefectClaim
from openultrasast.core.verdict import DefectClaim
def _digest(value: str) -> str:
+1 -1
View File
@@ -42,7 +42,7 @@ from openultrasast.core.semantics import (
SourceLocation,
make_relation,
)
from openultrasast.core.verdict_v2 import DefectClaim, VerdictContractError, derive_verdict_outputs
from openultrasast.core.verdict import DefectClaim, VerdictContractError, derive_verdict_outputs
def _digest(value: str) -> str: