feat(detection-core): isolate legacy v1 ownership

This commit is contained in:
Marius Ciepluch
2026-11-02 11:23:18 +01:00
parent e86fb07856
commit 856312c36e
12 changed files with 573 additions and 364 deletions
+1 -1
View File
@@ -354,7 +354,7 @@
- _Depends: 8.9, 9.5_ - _Depends: 8.9, 9.5_
- [ ] 10. Complete the single-writer v2 migration - [ ] 10. Complete the single-writer v2 migration
- [ ] 10.1 Isolate every live v1 contract and writer behind the closed compatibility namespace - [x] 10.1 Isolate every live v1 contract and writer behind the closed compatibility namespace
- Move all authorization-shaped v1 values into one compatibility contract owner and all live v1 serialization into its sole writer facade. - Move all authorization-shaped v1 values into one compatibility contract owner and all live v1 serialization into its sole writer facade.
- Limit the compatibility namespace to the canonical primitive needed for byte stability and prevent every v2 module from importing it. - Limit the compatibility namespace to the canonical primitive needed for byte stability and prevent every v2 module from importing it.
- Preserve the frozen v1 wire representation while eliminating independent contract or serializer implementations elsewhere. - Preserve the frozen v1 wire representation while eliminating independent contract or serializer implementations elsewhere.
+17 -57
View File
@@ -1,59 +1,19 @@
"""Compatibility facade for the pre-v2 file-backed fact-pack API.""" """Temporary explicit import shim for the pre-v2 fact-pack contract."""
from __future__ import annotations from openultrasast.core.legacy_v1.contracts import (
FactPackError,
LegacyFactPack,
admit_fact_pack,
admit_pack_file,
load_pack_file,
verify_fact_pack,
)
import hashlib __all__ = [
import json "FactPackError",
from collections.abc import Mapping, Sequence "LegacyFactPack",
from dataclasses import dataclass "admit_fact_pack",
from pathlib import Path "admit_pack_file",
from typing import Any "load_pack_file",
"verify_fact_pack",
]
class FactPackError(ValueError):
"""A legacy fact pack is unreviewed or inconsistent."""
@dataclass(frozen=True)
class LegacyFactPack:
family: str
facts: tuple[dict[str, Any], ...]
review_digest: str
fact_set_digest: str
def _digest(payload: object) -> str:
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(encoded).hexdigest()[:12]
def admit_fact_pack(family: str, facts: Sequence[Mapping[str, Any]], *, review_digest: str) -> LegacyFactPack:
if not review_digest:
raise FactPackError("unreviewed fact pack refused: no review digest")
normalized = tuple(dict(fact) for fact in facts)
return LegacyFactPack(family, normalized, review_digest, _digest([family, normalized]))
def verify_fact_pack(pack: LegacyFactPack, facts: Sequence[Mapping[str, Any]]) -> None:
normalized = tuple(dict(fact) for fact in facts)
if _digest([pack.family, normalized]) != pack.fact_set_digest:
raise FactPackError(f"fact content does not match the stamped digest of pack {pack.family!r}")
def load_pack_file(path: str | Path) -> LegacyFactPack:
payload = json.loads(Path(path).read_text(encoding="utf-8"))
review = payload.get("review", {})
note = review.get("note", "")
if not note or review.get("digest") != hashlib.sha256(note.encode()).hexdigest():
raise FactPackError(f"pack {path} lacks a verifiable review attestation")
facts = payload.get("facts")
if not isinstance(facts, list) or not all(isinstance(fact, dict) for fact in facts):
raise FactPackError(f"pack {path}: facts must be a list of objects")
return admit_fact_pack(str(payload.get("family", "")), facts, review_digest=review["digest"])
def admit_pack_file(path: str | Path, *, review_digest: str) -> LegacyFactPack:
pack = load_pack_file(path)
if review_digest != pack.review_digest:
raise FactPackError(f"pack {path}: supplied review digest does not match the embedded attestation")
return pack
+7 -131
View File
@@ -1,137 +1,13 @@
"""Admission contract for the detection core (Req 7, 10, 12). """Temporary explicit import shim for the pre-v2 admission contract."""
Family-agnostic generalization of the prover's consume-only manifest validation: from openultrasast.core.legacy_v1.contracts import (
only manifests produced and reviewed by the benchmark workflow are consumed, never REPORT_SCHEMA_KEYS,
produced, mutated or re-labeled here. Refusal order is fixed and deterministic. AdmissionManifest,
Reports must carry the claimed families so numbers are never read across families AdmissionManifestError,
the arm does not cover. validate_admission_manifest,
""" validate_report_schema,
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from openultrasast.tools.fact_init import FORBIDDEN_LABEL_MARKERS
class AdmissionManifestError(ValueError):
"""Raised when an admission manifest is refused; the message names the reason."""
@dataclass(frozen=True)
class AdmissionManifest:
"""A validated, immutable admission manifest with its claimed families."""
produced_by: str
reviewed: bool
label_isolated: bool
spent: bool
population_role: str
analyzer_revision: str
fact_set_digest: str
acquisition_record_digest: str
configuration_digest: str
population_digest: str
labels: tuple[str, ...]
families: tuple[str, ...] = ()
_REQUIRED_FIELDS = (
"produced_by",
"reviewed",
"label_isolated",
"spent",
"population_role",
"analyzer_revision",
"fact_set_digest",
"acquisition_record_digest",
"configuration_digest",
"population_digest",
"labels",
) )
_BENCHMARK_PRODUCER = "valid-detection-benchmark"
# The fixed report schema every admission report must carry (Req 7.3, 10.2, 12.1).
REPORT_SCHEMA_KEYS = frozenset(
{
"manifest",
"families",
"pairs",
"pair_correctness",
"fix_recall",
"verified_location_recall",
"flagged_ordinary_per_100",
"precision",
"indeterminate_rate",
"coverage",
"runtime",
"instrument_failures",
"failed_gates",
"decision",
}
)
def validate_admission_manifest(payload: dict[str, object], *, expected_acquisition_digest: str | None = None) -> AdmissionManifest:
"""Validate an externally produced admission manifest and return it, or refuse.
Refusal reasons, in check order: malformed, producer, unreviewed,
label-isolated, label-exposure, acquisition-output-changed, spent.
"""
if not all(field in payload for field in _REQUIRED_FIELDS):
raise AdmissionManifestError("malformed: missing required manifest fields")
if payload["produced_by"] != _BENCHMARK_PRODUCER:
raise AdmissionManifestError(f"producer: only {_BENCHMARK_PRODUCER} manifests are consumed")
if payload["reviewed"] is not True:
raise AdmissionManifestError("unreviewed: manifest lacks external review")
if payload["label_isolated"] is not True:
raise AdmissionManifestError("label-isolated: isolation attestation missing")
raw_labels = payload["labels"]
if not isinstance(raw_labels, (tuple, list)) or any(marker in str(label) for label in raw_labels for marker in FORBIDDEN_LABEL_MARKERS):
raise AdmissionManifestError("label-exposure: manifest carries admission labels")
if expected_acquisition_digest is not None and payload["acquisition_record_digest"] != expected_acquisition_digest:
raise AdmissionManifestError("acquisition-output-changed: digest mismatch")
if payload["spent"] is True:
raise AdmissionManifestError("spent: population already consumed")
raw_families = payload.get("families", ())
if not isinstance(raw_families, (tuple, list)):
raise AdmissionManifestError("malformed: families must be a sequence")
return AdmissionManifest(
produced_by=str(payload["produced_by"]),
reviewed=True,
label_isolated=True,
spent=False,
population_role=str(payload["population_role"]),
analyzer_revision=str(payload["analyzer_revision"]),
fact_set_digest=str(payload["fact_set_digest"]),
acquisition_record_digest=str(payload["acquisition_record_digest"]),
configuration_digest=str(payload["configuration_digest"]),
population_digest=str(payload["population_digest"]),
labels=tuple(str(label) for label in raw_labels),
families=tuple(str(family) for family in raw_families),
)
def validate_report_schema(report: dict[str, Any]) -> None:
"""Refuse an admission report missing any fixed-schema key (Req 12.1)."""
missing = sorted(REPORT_SCHEMA_KEYS - set(report))
if missing:
raise AdmissionManifestError(f"report schema: missing keys {missing}")
for rate_key in (
"pair_correctness",
"fix_recall",
"verified_location_recall",
"flagged_ordinary_per_100",
"precision",
"indeterminate_rate",
):
rate = report[rate_key]
if not {"n", "count", "rate", "wilson95"} <= set(rate):
raise AdmissionManifestError(f"report schema: {rate_key} lacks n/count/rate/wilson95")
__all__ = [ __all__ = [
"REPORT_SCHEMA_KEYS", "REPORT_SCHEMA_KEYS",
"AdmissionManifest", "AdmissionManifest",
+2 -52
View File
@@ -1,55 +1,5 @@
"""Consumer contracts for the detection core (Req 9, 11). """Temporary explicit import shim for the pre-v2 consumer contract."""
The pre-push transaction consumes traces and verdicts through this interface within
its (measured) budget; the proof layer attaches demonstrated evidence to a verdict.
Every claim must carry a witness; every silence must carry a coverage reason.
"""
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass
from openultrasast.core.trace import TraceRecord
from openultrasast.core.verdict import BoundedSilence, ProvenClaim
@dataclass(frozen=True)
class PushRequest:
"""One push transaction handed to the core."""
base: str
head: str
budget_class: str
@dataclass(frozen=True)
class PushVerdict:
"""What the push consumer receives: proven claims plus bounded silence."""
claims: tuple[ProvenClaim, ...]
silence: tuple[BoundedSilence, ...]
digests: dict[str, str]
def consume_verdict(
request: PushRequest,
record: TraceRecord,
claims: Sequence[ProvenClaim],
silence: Sequence[BoundedSilence],
) -> PushVerdict:
"""Validate and wrap one trace's verdict for the push consumer (Req 5, 11.1).
A claim without a witness or a silence without a reason is refused: silence is
never a clean bill of health.
"""
for claim in claims:
if not claim.witness or not claim.path or claim.line <= 0:
raise ValueError(f"proven claim lacks witness or exact location: {claim}")
for entry in silence:
if not entry.reason:
raise ValueError(f"bounded silence lacks a coverage reason: {entry}")
return PushVerdict(claims=tuple(claims), silence=tuple(silence), digests=dict(record.digests))
from openultrasast.core.legacy_v1.contracts import PushRequest, PushVerdict, consume_verdict
__all__ = ["PushRequest", "PushVerdict", "consume_verdict"] __all__ = ["PushRequest", "PushVerdict", "consume_verdict"]
@@ -0,0 +1,51 @@
"""Explicit temporary exports for the closed detection-core v1 namespace."""
from openultrasast.core.legacy_v1.contracts import (
REPORT_SCHEMA_KEYS,
AdmissionManifest,
AdmissionManifestError,
BoundedSilence,
FactPackError,
Guard,
Hop,
Judgment,
LegacyFactPack,
ProvenClaim,
PushRequest,
PushVerdict,
TraceRecord,
Witness,
admit_fact_pack,
admit_pack_file,
as_clean,
consume_verdict,
load_pack_file,
validate_admission_manifest,
validate_report_schema,
verify_fact_pack,
)
__all__ = [
"REPORT_SCHEMA_KEYS",
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"FactPackError",
"Guard",
"Hop",
"Judgment",
"LegacyFactPack",
"ProvenClaim",
"PushRequest",
"PushVerdict",
"TraceRecord",
"Witness",
"admit_fact_pack",
"admit_pack_file",
"as_clean",
"consume_verdict",
"load_pack_file",
"validate_admission_manifest",
"validate_report_schema",
"verify_fact_pack",
]
@@ -0,0 +1,305 @@
"""Closed temporary owner of detection-core v1 contracts and behavior."""
from __future__ import annotations
import enum
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
class AdmissionManifestError(ValueError):
"""A legacy admission manifest was refused."""
class FactPackError(ValueError):
"""A legacy fact pack is unreviewed or inconsistent."""
class Judgment(enum.Enum):
UNGUARDED = "unguarded"
DISCHARGED = "discharged"
INDETERMINATE = "indeterminate"
@dataclass(frozen=True)
class ProvenClaim:
trace_id: str
witness: str
path: str
line: int
family: str
@dataclass(frozen=True)
class BoundedSilence:
path: str
reason: str
coverage: dict[str, Any]
@dataclass(frozen=True)
class Hop:
function: str
call_line: int | None
complete: bool
@dataclass(frozen=True)
class Witness:
kind: str
provenance: str
relation: str
@dataclass(frozen=True)
class Guard:
kind: str
effect: str
@dataclass(frozen=True)
class TraceRecord:
entry: str
hops: tuple[Hop, ...]
operation_kind: str
resource: str
authority: str
witnesses: tuple[Witness, ...] = ()
guards: tuple[Guard, ...] = ()
completeness_reasons: tuple[str, ...] = ()
judgment: Judgment = Judgment.INDETERMINATE
digests: dict[str, str] = field(default_factory=dict)
def record(self) -> dict[str, Any]:
from openultrasast.core.legacy_v1 import writer
return writer.trace_record(self)
def canonical(self) -> bytes:
from openultrasast.core.legacy_v1 import writer
return writer.trace_bytes(self)
@dataclass(frozen=True)
class PushRequest:
base: str
head: str
budget_class: str
@dataclass(frozen=True)
class PushVerdict:
claims: tuple[ProvenClaim, ...]
silence: tuple[BoundedSilence, ...]
digests: dict[str, str]
@dataclass(frozen=True)
class AdmissionManifest:
produced_by: str
reviewed: bool
label_isolated: bool
spent: bool
population_role: str
analyzer_revision: str
fact_set_digest: str
acquisition_record_digest: str
configuration_digest: str
population_digest: str
labels: tuple[str, ...]
families: tuple[str, ...] = ()
@dataclass(frozen=True)
class LegacyFactPack:
family: str
facts: tuple[dict[str, Any], ...]
review_digest: str
fact_set_digest: str
REPORT_SCHEMA_KEYS = frozenset(
{
"manifest",
"families",
"pairs",
"pair_correctness",
"fix_recall",
"verified_location_recall",
"flagged_ordinary_per_100",
"precision",
"indeterminate_rate",
"coverage",
"runtime",
"instrument_failures",
"failed_gates",
"decision",
}
)
_REQUIRED_MANIFEST_FIELDS = (
"produced_by",
"reviewed",
"label_isolated",
"spent",
"population_role",
"analyzer_revision",
"fact_set_digest",
"acquisition_record_digest",
"configuration_digest",
"population_digest",
"labels",
)
_BENCHMARK_PRODUCER = "valid-detection-benchmark"
_FORBIDDEN_LABEL_MARKERS = ("vulnerability", "fix-location", "ordinary-change")
def as_clean(judgment: Judgment) -> Judgment:
if judgment is Judgment.INDETERMINATE:
raise ValueError("indeterminate never converts to a clean judgment")
return judgment
def consume_verdict(
request: PushRequest,
record: TraceRecord,
claims: Sequence[ProvenClaim],
silence: Sequence[BoundedSilence],
) -> PushVerdict:
del request
for claim in claims:
if not claim.witness or not claim.path or claim.line <= 0:
raise ValueError(f"proven claim lacks witness or exact location: {claim}")
for entry in silence:
if not entry.reason:
raise ValueError(f"bounded silence lacks a coverage reason: {entry}")
return PushVerdict(tuple(claims), tuple(silence), dict(record.digests))
def validate_admission_manifest(
payload: dict[str, object],
*,
expected_acquisition_digest: str | None = None,
) -> AdmissionManifest:
if not all(field in payload for field in _REQUIRED_MANIFEST_FIELDS):
raise AdmissionManifestError("malformed: missing required manifest fields")
if payload["produced_by"] != _BENCHMARK_PRODUCER:
raise AdmissionManifestError(f"producer: only {_BENCHMARK_PRODUCER} manifests are consumed")
if payload["reviewed"] is not True:
raise AdmissionManifestError("unreviewed: manifest lacks external review")
if payload["label_isolated"] is not True:
raise AdmissionManifestError("label-isolated: isolation attestation missing")
raw_labels = payload["labels"]
if not isinstance(raw_labels, (tuple, list)):
raise AdmissionManifestError("label-exposure: manifest carries admission labels")
if any(marker in str(label) for label in raw_labels for marker in _FORBIDDEN_LABEL_MARKERS):
raise AdmissionManifestError("label-exposure: manifest carries admission labels")
if expected_acquisition_digest is not None and payload["acquisition_record_digest"] != expected_acquisition_digest:
raise AdmissionManifestError("acquisition-output-changed: digest mismatch")
if payload["spent"] is True:
raise AdmissionManifestError("spent: population already consumed")
raw_families = payload.get("families", ())
if not isinstance(raw_families, (tuple, list)):
raise AdmissionManifestError("malformed: families must be a sequence")
return _manifest(payload, raw_labels, raw_families)
def _manifest(payload: dict[str, object], labels: Sequence[object], families: Sequence[object]) -> AdmissionManifest:
return AdmissionManifest(
produced_by=str(payload["produced_by"]),
reviewed=True,
label_isolated=True,
spent=False,
population_role=str(payload["population_role"]),
analyzer_revision=str(payload["analyzer_revision"]),
fact_set_digest=str(payload["fact_set_digest"]),
acquisition_record_digest=str(payload["acquisition_record_digest"]),
configuration_digest=str(payload["configuration_digest"]),
population_digest=str(payload["population_digest"]),
labels=tuple(str(label) for label in labels),
families=tuple(str(family) for family in families),
)
def validate_report_schema(report: dict[str, Any]) -> None:
missing = sorted(REPORT_SCHEMA_KEYS - set(report))
if missing:
raise AdmissionManifestError(f"report schema: missing keys {missing}")
for rate_key in (
"pair_correctness",
"fix_recall",
"verified_location_recall",
"flagged_ordinary_per_100",
"precision",
"indeterminate_rate",
):
if not {"n", "count", "rate", "wilson95"} <= set(report[rate_key]):
raise AdmissionManifestError(f"report schema: {rate_key} lacks n/count/rate/wilson95")
def admit_fact_pack(family: str, facts: Sequence[Mapping[str, Any]], *, review_digest: str) -> LegacyFactPack:
from openultrasast.core.legacy_v1 import writer
if not review_digest:
raise FactPackError("unreviewed fact pack refused: no review digest")
normalized = tuple(dict(fact) for fact in facts)
return LegacyFactPack(family, normalized, review_digest, writer.fact_pack_digest(family, normalized))
def verify_fact_pack(pack: LegacyFactPack, facts: Sequence[Mapping[str, Any]]) -> None:
from openultrasast.core.legacy_v1 import writer
normalized = tuple(dict(fact) for fact in facts)
if writer.fact_pack_digest(pack.family, normalized) != pack.fact_set_digest:
raise FactPackError(f"fact content does not match the stamped digest of pack {pack.family!r}")
def load_pack_file(path: str | Path) -> LegacyFactPack:
from openultrasast.core.legacy_v1 import writer
payload = writer.read_json(Path(path))
review = payload.get("review", {})
if not isinstance(review, dict):
raise FactPackError(f"pack {path} lacks a verifiable review attestation")
note = review.get("note", "")
if not isinstance(note, str) or not note or review.get("digest") != writer.text_digest(note):
raise FactPackError(f"pack {path} lacks a verifiable review attestation")
facts = payload.get("facts")
if not isinstance(facts, list) or not all(isinstance(fact, dict) for fact in facts):
raise FactPackError(f"pack {path}: facts must be a list of objects")
return admit_fact_pack(str(payload.get("family", "")), facts, review_digest=str(review["digest"]))
def admit_pack_file(path: str | Path, *, review_digest: str) -> LegacyFactPack:
pack = load_pack_file(path)
if review_digest != pack.review_digest:
raise FactPackError(f"pack {path}: supplied review digest does not match the embedded attestation")
return pack
__all__ = [
"REPORT_SCHEMA_KEYS",
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"FactPackError",
"Guard",
"Hop",
"Judgment",
"LegacyFactPack",
"ProvenClaim",
"PushRequest",
"PushVerdict",
"TraceRecord",
"Witness",
"admit_fact_pack",
"admit_pack_file",
"as_clean",
"consume_verdict",
"load_pack_file",
"validate_admission_manifest",
"validate_report_schema",
"verify_fact_pack",
]
@@ -0,0 +1,41 @@
"""The sole temporary detection-core v1 JSON serialization capability."""
from __future__ import annotations
import hashlib
import json
from collections.abc import Mapping, Sequence
from dataclasses import asdict
from pathlib import Path
from typing import Any, cast
from openultrasast.core.legacy_v1 import contracts
def trace_record(value: contracts.TraceRecord) -> dict[str, Any]:
payload: dict[str, Any] = asdict(value)
payload["judgment"] = value.judgment.value
return cast(dict[str, Any], json.loads(json.dumps(payload, sort_keys=True)))
def trace_bytes(value: contracts.TraceRecord) -> bytes:
return json.dumps(trace_record(value), sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
def fact_pack_digest(family: str, facts: Sequence[Mapping[str, Any]]) -> str:
encoded = json.dumps([family, tuple(dict(fact) for fact in facts)], sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(encoded).hexdigest()[:12]
def text_digest(value: str) -> str:
return hashlib.sha256(value.encode()).hexdigest()
def read_json(path: Path) -> dict[str, Any]:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise contracts.FactPackError(f"pack {path}: root must be an object")
return value
__all__ = ["fact_pack_digest", "read_json", "text_digest", "trace_bytes", "trace_record"]
+3 -72
View File
@@ -1,74 +1,5 @@
"""Canonical trace schema for the detection core (Req 1). """Temporary explicit import shim for the pre-v2 trace contract."""
One family-agnostic trace record per analyzed change, serialized deterministically: from openultrasast.core.legacy_v1.contracts import Guard, Hop, TraceRecord, Witness
identical inputs produce byte-identical canonical output. The access-control
prover's trace is the first implementation of this contract.
"""
from __future__ import annotations __all__ = ["Guard", "Hop", "TraceRecord", "Witness"]
import json
from dataclasses import asdict, dataclass, field
from typing import Any
from openultrasast.core.verdict import Judgment
def _canonical_bytes(payload: dict[str, Any]) -> bytes:
return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
@dataclass(frozen=True)
class Hop:
"""One ordered hop with its call-target completeness."""
function: str
call_line: int | None
complete: bool
@dataclass(frozen=True)
class Witness:
"""Evidence for or against a guard, with provenance and match relation."""
kind: str
provenance: str
relation: str
@dataclass(frozen=True)
class Guard:
"""A guard effect observed on a path (kind plus effect)."""
kind: str
effect: str
@dataclass(frozen=True)
class TraceRecord:
"""The canonical security trace: entry, hops, operation, witnesses, guards, judgment, digests."""
entry: str
hops: tuple[Hop, ...]
operation_kind: str
resource: str
authority: str
witnesses: tuple[Witness, ...] = ()
guards: tuple[Guard, ...] = ()
completeness_reasons: tuple[str, ...] = ()
judgment: Judgment = Judgment.INDETERMINATE
digests: dict[str, str] = field(default_factory=dict)
def record(self) -> dict[str, Any]:
"""JSON-ready dict with sorted keys; enums encode as their value strings."""
payload: dict[str, Any] = asdict(self)
payload["judgment"] = self.judgment.value
record: dict[str, Any] = json.loads(json.dumps(payload, sort_keys=True))
return record
def canonical(self) -> bytes:
"""Deterministic byte serialization (Req 1.2)."""
return _canonical_bytes(self.record())
__all__ = ["Hop", "TraceRecord", "Witness"]
+2 -48
View File
@@ -1,51 +1,5 @@
"""Verdict vocabulary and verdict contract for the detection core (Req 2, 5, 9). """Temporary explicit import shim for the pre-v2 verdict contract."""
Every analyzed operation is judged as exactly one of unguarded, discharged, or
indeterminate. A verdict is either a proven claim (witness attached, exact
location) or a bounded silence (coverage reason attached). Indeterminate never
converts to clean, and models may only judge residuals zero-shot over a trace.
"""
from __future__ import annotations
import enum
from dataclasses import dataclass
from typing import Any
class Judgment(enum.Enum):
"""The three-way judgment vocabulary; value strings are the wire format."""
UNGUARDED = "unguarded"
DISCHARGED = "discharged"
INDETERMINATE = "indeterminate"
@dataclass(frozen=True)
class ProvenClaim:
"""A defect claim carrying its witness and exact location (Req 5.1)."""
trace_id: str
witness: str
path: str
line: int
family: str
@dataclass(frozen=True)
class BoundedSilence:
"""An undecided site with its coverage reason; never a clean result (Req 5.2)."""
path: str
reason: str
coverage: dict[str, Any]
def as_clean(judgment: Judgment) -> Judgment:
"""Return a clean judgment, refusing to convert indeterminate (Req 2.1)."""
if judgment is Judgment.INDETERMINATE:
raise ValueError("indeterminate never converts to a clean judgment")
return judgment
from openultrasast.core.legacy_v1.contracts import BoundedSilence, Judgment, ProvenClaim, as_clean
__all__ = ["BoundedSilence", "Judgment", "ProvenClaim", "as_clean"] __all__ = ["BoundedSilence", "Judgment", "ProvenClaim", "as_clean"]
+21 -2
View File
@@ -197,6 +197,11 @@ def load_migration_inventory(path: str | Path) -> LegacyMigrationInventory:
def _defined_symbols(tree: ast.Module) -> set[str]: def _defined_symbols(tree: ast.Module) -> set[str]:
symbols = {"<module>"} symbols = {"<module>"}
for node in tree.body:
if isinstance(node, ast.ImportFrom):
symbols.update(alias.asname or alias.name for alias in node.names)
elif isinstance(node, ast.Import):
symbols.update(alias.asname or alias.name.split(".", 1)[0] for alias in node.names)
def visit(nodes: list[ast.stmt], parents: tuple[str, ...] = ()) -> None: def visit(nodes: list[ast.stmt], parents: tuple[str, ...] = ()) -> None:
for node in nodes: for node in nodes:
@@ -271,6 +276,9 @@ def _capability_index(tree: ast.Module) -> dict[str, set[str]]:
constants = {node.value for node in ast.walk(tree) if isinstance(node, ast.Constant) and isinstance(node.value, str)} constants = {node.value for node in ast.walk(tree) if isinstance(node, ast.Constant) and isinstance(node.value, str)}
names = {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)} names = {node.id for node in ast.walk(tree) if isinstance(node, ast.Name)}
names.update(node.attr for node in ast.walk(tree) if isinstance(node, ast.Attribute)) names.update(node.attr for node in ast.walk(tree) if isinstance(node, ast.Attribute))
names.update(
alias.asname or alias.name for node in ast.walk(tree) if isinstance(node, (ast.Import, ast.ImportFrom)) for alias in node.names
)
return { return {
"call": {_call_leaf(node) for node in ast.walk(tree) if isinstance(node, ast.Call)}, "call": {_call_leaf(node) for node in ast.walk(tree) if isinstance(node, ast.Call)},
"constant": constants, "constant": constants,
@@ -293,6 +301,16 @@ def _missing_anchors(entry: LegacyInventoryEntry, tree: ast.Module) -> tuple[str
return tuple(missing) return tuple(missing)
def _explicit_legacy_shim(tree: ast.Module, symbol: str) -> bool:
root_symbol = symbol.split(".", 1)[0]
return any(
isinstance(node, ast.ImportFrom)
and (node.module or "").startswith("openultrasast.core.legacy_v1")
and (symbol == "<module>" or any((alias.asname or alias.name) == root_symbol for alias in node.names))
for node in tree.body
)
def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Path) -> None: def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Path) -> None:
"""Prove every registered migration target is readable, nonzero, and present.""" """Prove every registered migration target is readable, nonzero, and present."""
for entry in inventory.entries: for entry in inventory.entries:
@@ -307,10 +325,11 @@ def validate_inventory_sources(inventory: LegacyMigrationInventory, *, root: Pat
tree = ast.parse(content, filename=entry.path) tree = ast.parse(content, filename=entry.path)
except (SyntaxError, UnicodeDecodeError) as exc: except (SyntaxError, UnicodeDecodeError) as exc:
raise LegacyInventoryError(f"inventory source is not parseable: {entry.path}") from exc raise LegacyInventoryError(f"inventory source is not parseable: {entry.path}") from exc
if entry.symbol not in _defined_symbols(tree): is_shim = _explicit_legacy_shim(tree, entry.symbol)
if entry.symbol not in _defined_symbols(tree) and not is_shim:
raise LegacyInventoryError(f"inventory target is absent: {entry.path}:{entry.symbol}") raise LegacyInventoryError(f"inventory target is absent: {entry.path}:{entry.symbol}")
missing = _missing_anchors(entry, tree) missing = _missing_anchors(entry, tree)
if missing: if missing and not is_shim:
raise LegacyInventoryError(f"inventory capability is absent: {entry.path}:{missing}") raise LegacyInventoryError(f"inventory capability is absent: {entry.path}:{missing}")
+111
View File
@@ -0,0 +1,111 @@
"""Task 10.1: isolate the live v1 contract and serialization owners."""
from __future__ import annotations
import ast
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
V1_FIXTURES = REPO / "tests/fixtures/detection_core/v1"
PURE_COMPATIBILITY_MODULES = (
"admission.py",
"consumer.py",
"trace.py",
"verdict.py",
"_legacy_factpack.py",
)
def _trace() -> object:
from openultrasast.core.legacy_v1 import contracts
return contracts.TraceRecord(
entry="src/app.py::handler",
hops=(contracts.Hop(function="handler", call_line=10, complete=True),),
operation_kind="protected_read",
resource="credentials",
authority="admin",
witnesses=(contracts.Witness(kind="guard", provenance="fact:checkPermission", relation="all_paths"),),
guards=(contracts.Guard(kind="permission_check", effect="branches"),),
completeness_reasons=(),
judgment=contracts.Judgment.UNGUARDED,
digests={"input": "abc123"},
)
def test_legacy_contract_values_have_one_compatibility_owner() -> None:
from openultrasast.core.legacy_v1 import contracts
names = {
"AdmissionManifest",
"AdmissionManifestError",
"BoundedSilence",
"FactPackError",
"Guard",
"Hop",
"Judgment",
"LegacyFactPack",
"ProvenClaim",
"PushRequest",
"PushVerdict",
"TraceRecord",
"Witness",
}
assert {getattr(contracts, name).__module__ for name in names} == {"openultrasast.core.legacy_v1.contracts"}
def test_pure_compatibility_modules_define_no_contract_or_serializer() -> None:
for name in PURE_COMPATIBILITY_MODULES:
path = REPO / "src/openultrasast/core" / name
content = path.read_bytes()
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 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 = REPO / "src/openultrasast/core/legacy_v1"
paths = sorted(namespace.glob("*.py"))
assert paths and all(path.read_bytes() for path in paths)
json_importers = []
for path in paths:
tree = ast.parse(path.read_bytes(), filename=path.as_posix())
if any(isinstance(node, (ast.Import, ast.ImportFrom)) and "json" in ast.unparse(node) for node in tree.body):
json_importers.append(path.name)
assert json_importers == ["writer.py"]
def test_namespace_and_v2_dependency_directions_are_closed() -> None:
namespace = REPO / "src/openultrasast/core/legacy_v1"
for path in namespace.glob("*.py"):
tree = ast.parse(path.read_bytes(), filename=path.as_posix())
imports = {node.module for node in ast.walk(tree) 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 (REPO / "src/openultrasast/core").glob("*.py"):
if path.name in compatibility:
continue
content = path.read_bytes()
assert content, f"dependency instrument must read nonzero bytes from {path.name}"
tree = ast.parse(content, filename=path.as_posix())
imports = {node.module for node in ast.walk(tree) if isinstance(node, ast.ImportFrom) and node.module}
assert not any(module.startswith("openultrasast.core.legacy_v1") for module in imports)
def test_legacy_trace_write_remains_byte_identical() -> None:
expected = (V1_FIXTURES / "trace.json").read_bytes()
assert len(expected) == 401
value = _trace()
assert value.canonical() == expected.rstrip(b"\n") # type: ignore[attr-defined]
def test_historical_decoder_stays_independent_from_compatibility_namespace() -> None:
path = REPO / "src/openultrasast/core/legacy.py"
content = path.read_bytes()
assert content
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
+12 -1
View File
@@ -170,11 +170,22 @@ def test_independent_reader_audit_matches_split_inventory_capabilities() -> None
inventory = load_migration_inventory(INVENTORY) inventory = load_migration_inventory(INVENTORY)
reader_entries = tuple(entry for entry in inventory.entries if entry.dependency_kind.value == "reader") reader_entries = tuple(entry for entry in inventory.entries if entry.dependency_kind.value == "reader")
registered = {(entry.path, anchor) for entry in reader_entries for anchor in entry.capability_anchors} registered = {(entry.path, anchor) for entry in reader_entries for anchor in entry.capability_anchors}
current = set(report.capabilities)
open_registered = {
(entry.path, anchor)
for entry in reader_entries
if entry.disposition is MigrationDisposition.OPEN
for anchor in entry.capability_anchors
}
assert report.files_read > 0 and report.bytes_read > 0 assert report.files_read > 0 and report.bytes_read > 0
assert ("tests/test_core_probes.py", "top_level:ProbeResult") in report.capabilities assert ("tests/test_core_probes.py", "top_level:ProbeResult") in report.capabilities
assert all(len(entry.capability_anchors) == 1 for entry in reader_entries) assert all(len(entry.capability_anchors) == 1 for entry in reader_entries)
assert registered == set(report.capabilities) assert current <= registered, "the frozen inventory may never grow to admit a new reader"
for path, _anchor in open_registered - current:
source = (REPO / path).read_bytes()
assert source, f"migration shim instrument must read nonzero bytes from {path}"
assert b"openultrasast.core.legacy_v1" in source, f"open reader disappeared without an explicit compatibility shim: {path}"
def test_inventory_targets_require_exact_capability_anchors(tmp_path: Path) -> None: def test_inventory_targets_require_exact_capability_anchors(tmp_path: Path) -> None: