feat(detection-core): adapt authorization traces

This commit is contained in:
Marius Ciepluch
2026-11-01 12:13:20 +01:00
parent 57802dac7a
commit 292d4b5557
6 changed files with 310 additions and 52 deletions
+4 -3
View File
@@ -17,6 +17,7 @@ from openultrasast.semantic.obligations.abstract_domain import ( # noqa: E402
ResourceRef,
WitnessMatch,
)
from openultrasast.semantic.obligations.core_adapter import to_legacy_trace_record # noqa: E402
from openultrasast.semantic.obligations.trace import AbstractTraceState, Guard, Hop, Trace # noqa: E402
@@ -55,14 +56,14 @@ def _trace() -> Trace:
def test_trace_natively_conforms_to_core_schema() -> None:
record = _trace().to_core_record(entry="src/app.py::handler", digests={"input": "abc123"})
record = to_legacy_trace_record(_trace(), entry="src/app.py::handler", digests={"input": "abc123"})
assert isinstance(record, TraceRecord)
assert record.entry == "src/app.py::handler"
assert record.judgment is Judgment.UNGUARDED
assert record.resource == "credentials"
assert [g.kind for g in record.guards] == ["permission_check", "inner", "after"]
assert record.hops[0].complete is False # conservative default
assert record.canonical() == _trace().to_core_record(entry="src/app.py::handler", digests={"input": "abc123"}).canonical()
assert record.canonical() == to_legacy_trace_record(_trace(), entry="src/app.py::handler", digests={"input": "abc123"}).canonical()
def test_legacy_trace_without_abstract_state_is_indeterminate() -> None:
@@ -82,7 +83,7 @@ def test_legacy_trace_without_abstract_state_is_indeterminate() -> None:
dominating=trace.dominating,
missing=trace.missing,
)
record = legacy.to_core_record(entry="e", digests={})
record = to_legacy_trace_record(legacy, entry="e", digests={})
assert record.judgment is Judgment.INDETERMINATE
assert record.completeness_reasons
+2 -1
View File
@@ -18,6 +18,7 @@ from openultrasast.semantic.obligations.abstract_domain import ( # noqa: E402
ResourceRef,
WitnessMatch,
)
from openultrasast.semantic.obligations.core_adapter import to_legacy_trace_record # noqa: E402
from openultrasast.semantic.obligations.trace import AbstractTraceState, Trace # noqa: E402
REPO = Path(__file__).resolve().parents[1]
@@ -67,7 +68,7 @@ def test_end_to_end_push_through_core_contracts() -> None:
missing="identity_constraint",
abstract_state=state,
)
record = trace.to_core_record(entry="src/app.py::handler", digests={"input": "abc123"})
record = to_legacy_trace_record(trace, entry="src/app.py::handler", digests={"input": "abc123"})
claim = ProvenClaim(trace_id="t1", witness="fact:f", path="src/app.py", line=30, family="access_control")
verdict = consume_verdict(PushRequest(base="b", head="h", budget_class="dev-pool"), record, (claim,), ())
assert verdict.claims[0].witness == "fact:f"
+89
View File
@@ -0,0 +1,89 @@
"""Authorization traces cross into detection-core v2 only through the adapter."""
from __future__ import annotations
import inspect
from dataclasses import replace
from test_core_evidence import _scope, _stamp
from test_core_native import _trace
from test_core_reference_families import _contract, _load
from openultrasast.core import evidence, family, semantics
from openultrasast.semantic.obligations import trace as trace_module
from openultrasast.semantic.obligations.abstract_domain import PairJudgment, ResourceRef
from openultrasast.semantic.obligations.core_adapter import to_evidence_record
def _adapt(trace: trace_module.Trace) -> evidence.EvidenceRecord:
contract = _contract(_load("obligation_family.json"))
stamp = replace(_stamp(), family_contract_digest=family.family_contract_digest(contract))
return to_evidence_record(
trace,
contract=contract,
ecosystem="reference-obligation",
scope=_scope(),
provenance=stamp,
)
def test_direct_complete_authorization_trace_maps_to_v2_violation() -> None:
trace = replace(_trace(), hops=(), operation_line=3)
record = _adapt(trace)
assert record.decision.outcome is evidence.NeutralOutcome.VIOLATION
assert record.decision.family_code == "constraint_exposed"
assert tuple(item.role for item in record.packet.transitions) == ("operation", "constraint")
assert all(item.status is semantics.RelationStatus.COMPLETE for item in record.packet.transitions)
def test_missing_per_hop_completeness_stays_incomplete_and_indeterminate() -> None:
trace = replace(_trace(), operation_line=3)
record = _adapt(trace)
constraint = next(item for item in record.packet.transitions if item.role == "constraint")
assert constraint.status is semantics.RelationStatus.INCOMPLETE
assert record.decision.outcome is evidence.NeutralOutcome.INDETERMINATE
assert record.decision.family_code == "constraint_unknown"
assert "missing_constraint_relation" in record.packet.completeness_reasons
def test_complete_discharged_trace_maps_to_v2_satisfaction() -> None:
trace = replace(_trace(), hops=(), operation_line=3)
assert trace.abstract_state is not None
state = replace(
trace.abstract_state,
path_judgment=replace(trace.abstract_state.path_judgment, judgment=PairJudgment.DISCHARGED, missing=()),
)
record = _adapt(replace(trace, abstract_state=state))
assert record.decision.outcome is evidence.NeutralOutcome.SATISFIED
assert record.decision.family_code == "constraint_enforced"
def test_unknown_resource_degrades_complete_legacy_judgment_to_indeterminate() -> None:
trace = replace(_trace(), hops=(), operation_line=3)
assert trace.abstract_state is not None
state = replace(trace.abstract_state, resource=ResourceRef(kind="unknown"))
record = _adapt(replace(trace, abstract_state=state))
assert record.decision.outcome is evidence.NeutralOutcome.INDETERMINATE
assert record.packet.transitions[1].status is semantics.RelationStatus.INCOMPLETE
def test_unset_abstract_state_never_lifts_legacy_trace_to_a_clean_result() -> None:
record = _adapt(replace(_trace(), hops=(), operation_line=3, abstract_state=None))
assert record.decision.outcome is evidence.NeutralOutcome.INDETERMINATE
assert record.packet.transitions[1].status is semantics.RelationStatus.INCOMPLETE
def test_authorization_trace_domain_has_no_core_import_or_conversion_method() -> None:
source = inspect.getsource(trace_module)
assert "openultrasast.core" not in source
assert not hasattr(trace_module.Trace, "to_core_record")