feat(detection-core): bind evidence packets to neutral decisions

This commit is contained in:
Marius Ciepluch
2026-10-31 14:18:51 +01:00
parent 62c87b39ab
commit 93aade237e
3 changed files with 764 additions and 1 deletions
+1 -1
View File
@@ -64,7 +64,7 @@
- Refuse protected-population influence, redirected artifact identities, wrong purposes, and incomplete receipt history. - Refuse protected-population influence, redirected artifact identities, wrong purposes, and incomplete receipt history.
- Done when matching subjects cannot mask a wrong influenced artifact and contaminated populations are deterministically refused. - Done when matching subjects cannot mask a wrong influenced artifact and contaminated populations are deterministically refused.
- _Requirements: 13.1, 13.3, 13.4_ - _Requirements: 13.1, 13.3, 13.4_
- [ ] 2.5 Bind immutable evidence packets to neutral decisions - [x] 2.5 Bind immutable evidence packets to neutral decisions
- Preserve family identity, security object, analyzed scope, ordered semantics, observations, family fields, completeness, and provenance. - Preserve family identity, security object, analyzed scope, ordered semantics, observations, family fields, completeness, and provenance.
- Keep family decision codes separate from violation, satisfied, and indeterminate outcomes. - Keep family decision codes separate from violation, satisfied, and indeterminate outcomes.
- Require bounded scope for satisfaction, named reasons for indeterminate outcomes, and a valid deterministic, model-corroborated, or execution-backed basis. - Require bounded scope for satisfaction, named reasons for indeterminate outcomes, and a valid deterministic, model-corroborated, or execution-backed basis.
+414
View File
@@ -0,0 +1,414 @@
"""Immutable family-neutral evidence packets and neutral decisions."""
from __future__ import annotations
import re
from dataclasses import dataclass
from enum import Enum
from openultrasast.core import canonical
from openultrasast.core.provenance import DecisionProvenance, ModelCallIdentity, ProvenanceStamp
from openultrasast.core.semantics import (
EvidenceProvenance,
FieldValue,
Observation,
RelationStatus,
SemanticNode,
SemanticRelation,
SourceLocation,
)
EVIDENCE_PACKET_SCHEMA = "openultrasast.detection-core.evidence-packet.v2"
EVIDENCE_RECORD_SCHEMA = "openultrasast.detection-core.evidence-record.v2"
_DIGEST = re.compile(r"[0-9a-f]{64}")
class EvidenceValidationError(ValueError):
"""An evidence packet or decision violates the neutral core contract."""
class NeutralOutcome(Enum):
VIOLATION = "violation"
SATISFIED = "satisfied"
INDETERMINATE = "indeterminate"
class EvidenceBasis(Enum):
DETERMINISTIC = "deterministic"
MODEL_CORROBORATED = "model_corroborated"
EXECUTION_BACKED = "execution_backed"
def _require_text(value: object, field: str) -> None:
if not isinstance(value, str) or not value:
raise EvidenceValidationError(f"{field} must be nonempty text")
def _require_digest(value: object, field: str) -> None:
if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
raise EvidenceValidationError(f"{field} must be a full lowercase SHA-256 digest")
def _require_text_tuple(value: object, field: str) -> None:
if not isinstance(value, tuple) or not all(isinstance(item, str) and item for item in value):
raise EvidenceValidationError(f"{field} must be a tuple of nonempty text")
try:
canonical.require_unique_identities(value, kind=field)
except canonical.DuplicateIdentityError as exc:
raise EvidenceValidationError(str(exc)) from exc
@dataclass(frozen=True)
class SourceRegion:
path: str
start_line: int
end_line: int
def __post_init__(self) -> None:
_require_text(self.path, "path")
for value, field in ((self.start_line, "start_line"), (self.end_line, "end_line")):
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
raise EvidenceValidationError(f"{field} must be a positive line number")
if self.end_line < self.start_line:
raise EvidenceValidationError("end_line must not precede start_line")
@dataclass(frozen=True)
class AnalyzedScope:
base_revision: str
head_revision: str
change_digest: str
regions: tuple[SourceRegion, ...]
def __post_init__(self) -> None:
_require_text(self.base_revision, "base_revision")
_require_text(self.head_revision, "head_revision")
_require_digest(self.change_digest, "change_digest")
if not isinstance(self.regions, tuple) or not self.regions:
raise EvidenceValidationError("regions must be a nonempty tuple")
if not all(isinstance(region, SourceRegion) for region in self.regions):
raise EvidenceValidationError("every region must be a SourceRegion")
identities = tuple(f"{region.path}:{region.start_line}:{region.end_line}" for region in self.regions)
try:
canonical.require_unique_identities(identities, kind="region")
except canonical.DuplicateIdentityError as exc:
raise EvidenceValidationError(str(exc)) from exc
def _location_value(location: SourceLocation) -> canonical.JsonValue:
return {"path": location.path, "line": location.line, "column": location.column, "symbol": location.symbol}
def _node_value(node: SemanticNode) -> canonical.JsonValue:
return {"node_id": node.node_id, "role": node.role, "location": _location_value(node.location)}
def _content_ref_value(reference: canonical.ContentRef) -> canonical.JsonValue:
return {
"artifact_id": reference.artifact_id,
"content_digest": reference.content_digest,
"media_type": reference.media_type,
"byte_length": reference.byte_length,
"retention_policy_digest": reference.retention_policy_digest,
"storage_receipt_digest": reference.storage_receipt_digest,
}
def _semantic_provenance_value(value: EvidenceProvenance) -> canonical.JsonValue:
fact_match: canonical.JsonValue = None if value.fact_match is None else _content_ref_value(value.fact_match)
return {
"kind": value.kind.value,
"source_content_digest": value.source_content_digest,
"semantic_pack_digest": value.semantic_pack_digest,
"semantic_fact_id": value.semantic_fact_id,
"fact_match": fact_match,
}
def _field_value(value: FieldValue) -> canonical.JsonValue:
return {"name": value.name, "kind": value.kind.value, "value": value.value}
def _relation_value(value: SemanticRelation) -> canonical.JsonValue:
return {
"relation_id": value.relation_id,
"kind": value.kind.value,
"role": value.role,
"source": _node_value(value.source),
"target": _node_value(value.target),
"location": _location_value(value.location),
"status": value.status.value,
"conditions": value.conditions,
"reasons": value.reasons,
"provenance": _semantic_provenance_value(value.provenance),
}
def _observation_value(value: Observation) -> canonical.JsonValue:
return {
"observation_id": value.observation_id,
"role": value.role,
"location": _location_value(value.location),
"fields": tuple(_field_value(field) for field in value.fields),
"status": value.status.value,
"provenance": _semantic_provenance_value(value.provenance),
}
def _scope_value(value: AnalyzedScope) -> canonical.JsonValue:
return {
"base_revision": value.base_revision,
"head_revision": value.head_revision,
"change_digest": value.change_digest,
"regions": tuple({"path": region.path, "start_line": region.start_line, "end_line": region.end_line} for region in value.regions),
}
def _stamp_value(value: ProvenanceStamp) -> canonical.JsonValue:
return {
"analyzer_revision": value.analyzer_revision,
"family_contract_digest": value.family_contract_digest,
"fact_set_digest": value.fact_set_digest,
"acquisition_record_digest": value.acquisition_record_digest,
"configuration_digest": value.configuration_digest,
"population_digest": value.population_digest,
"change_input_digest": value.change_input_digest,
}
@dataclass(frozen=True)
class EvidencePacket:
schema_version: str
family_id: str
family_contract_digest: str
security_object_id: str
ecosystem: str
scope: AnalyzedScope
transitions: tuple[SemanticRelation, ...]
observations: tuple[Observation, ...]
family_fields: tuple[FieldValue, ...]
completeness_reasons: tuple[str, ...]
provenance: ProvenanceStamp
def __post_init__(self) -> None:
_validate_packet(self)
def _validate_packet(packet: EvidencePacket) -> None:
if packet.schema_version != EVIDENCE_PACKET_SCHEMA:
raise EvidenceValidationError(f"unknown evidence packet schema: {packet.schema_version!r}")
for value, field in (
(packet.family_id, "family_id"),
(packet.security_object_id, "security_object_id"),
(packet.ecosystem, "ecosystem"),
):
_require_text(value, field)
_require_digest(packet.family_contract_digest, "family_contract_digest")
if not isinstance(packet.scope, AnalyzedScope):
raise EvidenceValidationError("scope must be an AnalyzedScope")
_validate_packet_members(packet)
_validate_completeness(packet)
if not isinstance(packet.provenance, ProvenanceStamp):
raise EvidenceValidationError("provenance must be a ProvenanceStamp")
if packet.provenance.family_contract_digest != packet.family_contract_digest:
raise EvidenceValidationError("packet family contract digest does not match provenance")
def _validate_packet_members(packet: EvidencePacket) -> None:
members = (
(packet.transitions, SemanticRelation, "relation", "relation_id"),
(packet.observations, Observation, "observation", "observation_id"),
(packet.family_fields, FieldValue, "family field", "name"),
)
for values, expected_type, kind, identity_field in members:
if not isinstance(values, tuple) or not all(isinstance(value, expected_type) for value in values):
raise EvidenceValidationError(f"{kind} values must be a tuple of {expected_type.__name__}")
try:
canonical.require_unique_identities(tuple(getattr(value, identity_field) for value in values), kind=kind)
except canonical.DuplicateIdentityError as exc:
raise EvidenceValidationError(str(exc)) from exc
def _validate_completeness(packet: EvidencePacket) -> None:
_require_text_tuple(packet.completeness_reasons, "completeness reason")
relations_incomplete = any(item.status is not RelationStatus.COMPLETE for item in packet.transitions)
observations_incomplete = any(item.status is not RelationStatus.COMPLETE for item in packet.observations)
if (relations_incomplete or observations_incomplete) and not packet.completeness_reasons:
raise EvidenceValidationError("non-complete semantics require named completeness reasons")
relation_reasons = {reason for relation in packet.transitions for reason in relation.reasons}
if not relation_reasons.issubset(packet.completeness_reasons):
raise EvidenceValidationError("every non-complete relation reason must be named in packet completeness")
def _packet_value(packet: EvidencePacket) -> canonical.JsonValue:
return {
"schema_version": packet.schema_version,
"family_id": packet.family_id,
"family_contract_digest": packet.family_contract_digest,
"security_object_id": packet.security_object_id,
"ecosystem": packet.ecosystem,
"scope": _scope_value(packet.scope),
"transitions": tuple(_relation_value(item) for item in packet.transitions),
"observations": tuple(_observation_value(item) for item in packet.observations),
"family_fields": tuple(_field_value(item) for item in packet.family_fields),
"completeness_reasons": packet.completeness_reasons,
"provenance": _stamp_value(packet.provenance),
}
def derive_packet_digest(packet: EvidencePacket) -> str:
if not isinstance(packet, EvidencePacket):
raise EvidenceValidationError("packet must be an EvidencePacket")
return canonical.content_digest(_packet_value(packet))
def _model_identity_value(value: ModelCallIdentity) -> canonical.JsonValue:
return {
"provider": value.provider,
"model": value.model,
"program_digest": value.program_digest,
"prompt_digest": value.prompt_digest,
"configuration_digest": value.configuration_digest,
"adapter_revision": value.adapter_revision,
}
def _decision_provenance_value(value: DecisionProvenance) -> canonical.JsonValue:
model: canonical.JsonValue = None if value.model_identity is None else _model_identity_value(value.model_identity)
return {
"model_identity": model,
"model_result_digest": value.model_result_digest,
"model_declaration_digest": value.model_declaration_digest,
"proof_record_digest": value.proof_record_digest,
"proof_producer_digest": value.proof_producer_digest,
"lineage_manifest_digest": value.lineage_manifest_digest,
}
@dataclass(frozen=True)
class Decision:
family_code: str
outcome: NeutralOutcome
basis: EvidenceBasis
bounded_scope: AnalyzedScope
reasons: tuple[str, ...]
packet_digest: str
provenance: DecisionProvenance
def __post_init__(self) -> None:
_require_text(self.family_code, "family_code")
if not isinstance(self.outcome, NeutralOutcome):
raise EvidenceValidationError("outcome must be a NeutralOutcome")
if not isinstance(self.basis, EvidenceBasis):
raise EvidenceValidationError("basis must be an EvidenceBasis")
if not isinstance(self.bounded_scope, AnalyzedScope):
raise EvidenceValidationError("bounded_scope must be an AnalyzedScope")
_require_text_tuple(self.reasons, "decision reason")
_require_digest(self.packet_digest, "packet_digest")
if not isinstance(self.provenance, DecisionProvenance):
raise EvidenceValidationError("provenance must be DecisionProvenance")
_validate_basis(self.basis, self.provenance)
def _validate_basis(basis: EvidenceBasis, value: DecisionProvenance) -> None:
model = (value.model_identity, value.model_result_digest, value.model_declaration_digest, value.lineage_manifest_digest)
proof = (value.proof_record_digest, value.proof_producer_digest)
if basis is EvidenceBasis.DETERMINISTIC and any(item is not None for item in (*model, *proof)):
raise EvidenceValidationError("deterministic basis forbids model, proof, and lineage provenance")
if basis is EvidenceBasis.MODEL_CORROBORATED and (any(item is None for item in model) or any(item is not None for item in proof)):
raise EvidenceValidationError("model-corroborated basis requires complete model provenance and forbids proof provenance")
if basis is EvidenceBasis.EXECUTION_BACKED and (any(item is None for item in proof) or any(item is not None for item in model)):
raise EvidenceValidationError("execution-backed basis requires complete proof provenance and forbids model provenance")
def _decision_value(decision: Decision) -> canonical.JsonValue:
return {
"family_code": decision.family_code,
"outcome": decision.outcome.value,
"basis": decision.basis.value,
"bounded_scope": _scope_value(decision.bounded_scope),
"reasons": decision.reasons,
"packet_digest": decision.packet_digest,
"provenance": _decision_provenance_value(decision.provenance),
}
def _scope_within(bounded: AnalyzedScope, analyzed: AnalyzedScope) -> bool:
if (bounded.base_revision, bounded.head_revision, bounded.change_digest) != (
analyzed.base_revision,
analyzed.head_revision,
analyzed.change_digest,
):
return False
return all(region in analyzed.regions for region in bounded.regions)
@dataclass(frozen=True)
class EvidenceRecord:
packet: EvidencePacket
decision: Decision
record_id: str
def __post_init__(self) -> None:
if not isinstance(self.packet, EvidencePacket) or not isinstance(self.decision, Decision):
raise EvidenceValidationError("record requires an EvidencePacket and Decision")
_validate_record_pair(self.packet, self.decision)
expected = derive_record_id(self.packet, self.decision)
if not self.record_id:
object.__setattr__(self, "record_id", expected)
elif self.record_id != expected:
raise EvidenceValidationError("record_id does not match canonical record fields")
def _validate_record_pair(packet: EvidencePacket, decision: Decision) -> None:
if decision.packet_digest != derive_packet_digest(packet):
raise EvidenceValidationError("decision packet digest does not match packet")
if not _scope_within(decision.bounded_scope, packet.scope):
raise EvidenceValidationError("decision bounded scope is outside analyzed packet scope")
noncomplete = any(item.status is not RelationStatus.COMPLETE for item in packet.transitions) or any(
item.status is not RelationStatus.COMPLETE for item in packet.observations
)
if decision.outcome is not NeutralOutcome.INDETERMINATE and (packet.completeness_reasons or noncomplete):
raise EvidenceValidationError("non-complete semantics or completeness reasons forbid a determinate outcome")
if decision.outcome is NeutralOutcome.INDETERMINATE:
if not decision.reasons:
raise EvidenceValidationError("indeterminate outcome requires a named reason")
if not set(packet.completeness_reasons).issubset(decision.reasons):
raise EvidenceValidationError("indeterminate reasons must include every packet completeness reason")
elif decision.reasons:
raise EvidenceValidationError("determinate outcomes must not carry indeterminate reasons")
def _record_value(packet: EvidencePacket, decision: Decision) -> canonical.JsonValue:
return {
"schema_version": EVIDENCE_RECORD_SCHEMA,
"packet": _packet_value(packet),
"decision": _decision_value(decision),
}
def derive_record_id(packet: EvidencePacket, decision: Decision) -> str:
return canonical.content_digest(_record_value(packet, decision))
def evidence_record_bytes(record: EvidenceRecord) -> bytes:
if not isinstance(record, EvidenceRecord):
raise EvidenceValidationError("record must be an EvidenceRecord")
return canonical.canonical_bytes(_record_value(record.packet, record.decision))
__all__ = [
"AnalyzedScope",
"Decision",
"EVIDENCE_PACKET_SCHEMA",
"EVIDENCE_RECORD_SCHEMA",
"EvidenceBasis",
"EvidencePacket",
"EvidenceRecord",
"EvidenceValidationError",
"NeutralOutcome",
"SourceRegion",
"derive_packet_digest",
"derive_record_id",
"evidence_record_bytes",
]
+349
View File
@@ -0,0 +1,349 @@
"""Immutable evidence packet and neutral decision contracts."""
from __future__ import annotations
import json
from dataclasses import fields, replace
from enum import Enum
from typing import cast
import pytest
import openultrasast.core.canonical as canonical
import openultrasast.core.evidence as evidence
import openultrasast.core.provenance as provenance
import openultrasast.core.semantics as semantics
def _digest(value: str) -> str:
return canonical.content_digest(value)
def _location(line: int = 7) -> semantics.SourceLocation:
return semantics.SourceLocation("src/app.py", line, 2, "handle")
def _product_provenance() -> semantics.EvidenceProvenance:
return semantics.EvidenceProvenance(
semantics.ProvenanceKind.PRODUCT_CODE,
_digest("source"),
None,
None,
None,
)
def _relation(status: semantics.RelationStatus = semantics.RelationStatus.COMPLETE) -> semantics.SemanticRelation:
source = semantics.SemanticNode("request", "request", _location())
target = semantics.SemanticNode("resource", "resource", _location(8))
reasons = () if status is semantics.RelationStatus.COMPLETE else (f"relation:{status.value}",)
return semantics.make_relation(
kind=semantics.RelationKind.CONSTRAINT_EFFECT,
role="authorization_constraint",
source=source,
target=target,
location=_location(9),
status=status,
conditions=("authenticated",),
reasons=reasons,
provenance=_product_provenance(),
)
def _observation(status: semantics.RelationStatus = semantics.RelationStatus.COMPLETE) -> semantics.Observation:
return semantics.make_observation(
role="security_object",
location=_location(10),
fields=(semantics.FieldValue("kind", semantics.FieldKind.TEXT, "resource"),),
status=status,
provenance=_product_provenance(),
)
def _scope(*regions: evidence.SourceRegion) -> evidence.AnalyzedScope:
selected = regions or (evidence.SourceRegion("src/app.py", 1, 20),)
return evidence.AnalyzedScope("base", "head", _digest("change"), selected)
def _stamp() -> provenance.ProvenanceStamp:
return provenance.ProvenanceStamp(
*(
_digest(name)
for name in (
"analyzer",
"family-contract",
"facts",
"acquisition",
"configuration",
"population",
"change-input",
)
)
)
def _packet(
*,
status: semantics.RelationStatus = semantics.RelationStatus.COMPLETE,
completeness: tuple[str, ...] | None = None,
transitions: tuple[semantics.SemanticRelation, ...] | None = None,
) -> evidence.EvidencePacket:
relation = _relation(status)
reasons = () if status is semantics.RelationStatus.COMPLETE else relation.reasons
return evidence.EvidencePacket(
schema_version=evidence.EVIDENCE_PACKET_SCHEMA,
family_id="access-control",
family_contract_digest=_digest("family-contract"),
security_object_id="resource:42",
ecosystem="python",
scope=_scope(),
transitions=(relation,) if transitions is None else transitions,
observations=(_observation(),),
family_fields=(semantics.FieldValue("operation", semantics.FieldKind.TEXT, "read"),),
completeness_reasons=reasons if completeness is None else completeness,
provenance=_stamp(),
)
def _empty_decision_provenance() -> provenance.DecisionProvenance:
return provenance.DecisionProvenance(None, None, None, None, None, None)
def _decision(
packet: evidence.EvidencePacket,
*,
outcome: evidence.NeutralOutcome = evidence.NeutralOutcome.VIOLATION,
basis: evidence.EvidenceBasis = evidence.EvidenceBasis.DETERMINISTIC,
reasons: tuple[str, ...] = (),
bounded_scope: evidence.AnalyzedScope | None = None,
decision_provenance: provenance.DecisionProvenance | None = None,
) -> evidence.Decision:
return evidence.Decision(
family_code="unauthorized_access",
outcome=outcome,
basis=basis,
bounded_scope=packet.scope if bounded_scope is None else bounded_scope,
reasons=reasons,
packet_digest=evidence.derive_packet_digest(packet),
provenance=_empty_decision_provenance() if decision_provenance is None else decision_provenance,
)
def _record(
*,
packet: evidence.EvidencePacket | None = None,
outcome: evidence.NeutralOutcome = evidence.NeutralOutcome.VIOLATION,
basis: evidence.EvidenceBasis = evidence.EvidenceBasis.DETERMINISTIC,
reasons: tuple[str, ...] = (),
bounded_scope: evidence.AnalyzedScope | None = None,
decision_provenance: provenance.DecisionProvenance | None = None,
) -> evidence.EvidenceRecord:
selected = _packet() if packet is None else packet
decision = _decision(
selected,
outcome=outcome,
basis=basis,
reasons=reasons,
bounded_scope=bounded_scope,
decision_provenance=decision_provenance,
)
return evidence.EvidenceRecord(selected, decision, "")
@pytest.mark.parametrize("outcome", [evidence.NeutralOutcome.VIOLATION, evidence.NeutralOutcome.SATISFIED])
def test_determinate_records_round_trip_canonically(outcome: evidence.NeutralOutcome) -> None:
record = _record(outcome=outcome)
content = evidence.evidence_record_bytes(record)
parsed = json.loads(content)
assert len(content) > 0
assert json.dumps(parsed, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":")).encode() == content
assert record.record_id == canonical.bytes_digest(content)
assert parsed["decision"]["family_code"] == "unauthorized_access"
assert parsed["decision"]["outcome"] == outcome.value
assert parsed["packet"]["family_id"] == "access-control"
def test_incomplete_record_round_trips_as_named_indeterminate() -> None:
packet = _packet(status=semantics.RelationStatus.INCOMPLETE)
record = _record(
packet=packet,
outcome=evidence.NeutralOutcome.INDETERMINATE,
reasons=packet.completeness_reasons,
)
parsed = json.loads(evidence.evidence_record_bytes(record))
assert parsed["decision"]["outcome"] == "indeterminate"
assert parsed["decision"]["reasons"] == ["relation:incomplete"]
def test_packet_preserves_ordered_semantics_and_family_fields() -> None:
first = _relation()
second = replace(first, location=_location(11), relation_id="")
forward = _packet(transitions=(first, second))
reverse = _packet(transitions=(second, first))
assert forward.transitions == (first, second)
assert evidence.derive_packet_digest(forward) != evidence.derive_packet_digest(reverse)
assert forward.family_fields[0].name == "operation"
@pytest.mark.parametrize("status", [semantics.RelationStatus.INCOMPLETE, semantics.RelationStatus.UNRESOLVED])
@pytest.mark.parametrize("outcome", [evidence.NeutralOutcome.VIOLATION, evidence.NeutralOutcome.SATISFIED])
def test_noncomplete_semantics_cannot_produce_determinate_outcome(
status: semantics.RelationStatus,
outcome: evidence.NeutralOutcome,
) -> None:
packet = _packet(status=status)
with pytest.raises(evidence.EvidenceValidationError, match="non-complete|completeness"):
_record(packet=packet, outcome=outcome)
def test_packet_requires_every_noncomplete_reason_to_be_named() -> None:
with pytest.raises(evidence.EvidenceValidationError, match="completeness"):
_packet(status=semantics.RelationStatus.INCOMPLETE, completeness=())
def test_noncomplete_observation_also_forces_indeterminate_outcome() -> None:
packet = replace(
_packet(),
observations=(_observation(semantics.RelationStatus.UNRESOLVED),),
completeness_reasons=("observation:unresolved",),
)
with pytest.raises(evidence.EvidenceValidationError, match="non-complete"):
_record(packet=packet, outcome=evidence.NeutralOutcome.VIOLATION)
def test_indeterminate_requires_reasons_and_carries_every_packet_reason() -> None:
packet = _packet(status=semantics.RelationStatus.INCOMPLETE)
with pytest.raises(evidence.EvidenceValidationError, match="reason"):
_record(packet=packet, outcome=evidence.NeutralOutcome.INDETERMINATE)
with pytest.raises(evidence.EvidenceValidationError, match="completeness"):
_record(packet=packet, outcome=evidence.NeutralOutcome.INDETERMINATE, reasons=("different",))
def test_satisfaction_requires_nonempty_bounded_scope_within_packet_scope() -> None:
packet = _packet()
with pytest.raises(evidence.EvidenceValidationError, match="bounded scope"):
_record(
packet=packet,
outcome=evidence.NeutralOutcome.SATISFIED,
bounded_scope=_scope(evidence.SourceRegion("src/other.py", 1, 2)),
)
outside = evidence.AnalyzedScope("base", "head", _digest("other-change"), packet.scope.regions)
with pytest.raises(evidence.EvidenceValidationError, match="bounded scope"):
_record(packet=packet, outcome=evidence.NeutralOutcome.SATISFIED, bounded_scope=outside)
def test_satisfaction_accepts_an_exact_nonempty_subset_scope() -> None:
packet = replace(
_packet(),
scope=_scope(evidence.SourceRegion("src/app.py", 1, 10), evidence.SourceRegion("src/app.py", 20, 30)),
)
bounded = _scope(evidence.SourceRegion("src/app.py", 1, 10))
record = _record(packet=packet, outcome=evidence.NeutralOutcome.SATISFIED, bounded_scope=bounded)
assert record.decision.bounded_scope == bounded
def _model_provenance() -> provenance.DecisionProvenance:
identity = provenance.ModelCallIdentity(
provider="provider",
model="model",
program_digest=_digest("program"),
prompt_digest=_digest("prompt"),
configuration_digest=_digest("model-configuration"),
adapter_revision="adapter-v1",
)
return provenance.DecisionProvenance(
identity,
_digest("model-result"),
_digest("model-declaration"),
None,
None,
_digest("lineage"),
)
def _proof_provenance() -> provenance.DecisionProvenance:
return provenance.DecisionProvenance(None, None, None, _digest("proof"), _digest("producer"), None)
@pytest.mark.parametrize(
("basis", "decision_provenance"),
[
(evidence.EvidenceBasis.DETERMINISTIC, _empty_decision_provenance()),
(evidence.EvidenceBasis.MODEL_CORROBORATED, _model_provenance()),
(evidence.EvidenceBasis.EXECUTION_BACKED, _proof_provenance()),
],
)
def test_every_closed_evidence_basis_accepts_only_its_complete_provenance(
basis: evidence.EvidenceBasis,
decision_provenance: provenance.DecisionProvenance,
) -> None:
record = _record(basis=basis, decision_provenance=decision_provenance)
assert record.decision.basis is basis
@pytest.mark.parametrize(
("basis", "decision_provenance"),
[
(evidence.EvidenceBasis.DETERMINISTIC, _model_provenance()),
(evidence.EvidenceBasis.DETERMINISTIC, _proof_provenance()),
(evidence.EvidenceBasis.MODEL_CORROBORATED, _empty_decision_provenance()),
(evidence.EvidenceBasis.MODEL_CORROBORATED, replace(_model_provenance(), model_result_digest=None)),
(evidence.EvidenceBasis.EXECUTION_BACKED, _empty_decision_provenance()),
(evidence.EvidenceBasis.EXECUTION_BACKED, replace(_proof_provenance(), proof_producer_digest=None)),
(evidence.EvidenceBasis.EXECUTION_BACKED, _model_provenance()),
],
)
def test_basis_provenance_is_a_closed_union(
basis: evidence.EvidenceBasis,
decision_provenance: provenance.DecisionProvenance,
) -> None:
with pytest.raises(evidence.EvidenceValidationError, match="basis|provenance"):
_record(basis=basis, decision_provenance=decision_provenance)
def test_packet_has_no_model_or_proof_fields() -> None:
names = {item.name for item in fields(evidence.EvidencePacket)}
assert not any("model" in name or "proof" in name for name in names)
assert {"model_identity", "proof_record_digest"} <= {item.name for item in fields(provenance.DecisionProvenance)}
def test_decision_is_bound_to_exact_packet_digest_and_record_identity() -> None:
record = _record()
with pytest.raises(evidence.EvidenceValidationError, match="packet digest"):
replace(record, decision=replace(record.decision, packet_digest=_digest("other")), record_id="")
with pytest.raises(evidence.EvidenceValidationError, match="record_id"):
replace(record, record_id="0" * 64)
def test_packet_rejects_duplicate_logical_identities() -> None:
relation = _relation()
with pytest.raises(evidence.EvidenceValidationError, match="duplicate"):
_packet(transitions=(relation, relation))
packet = _packet()
with pytest.raises(evidence.EvidenceValidationError, match="duplicate"):
replace(packet, family_fields=(packet.family_fields[0], packet.family_fields[0]))
def test_scope_and_text_contracts_are_runtime_checked() -> None:
with pytest.raises(evidence.EvidenceValidationError, match="line"):
evidence.SourceRegion("src/app.py", 9, 8)
with pytest.raises(evidence.EvidenceValidationError, match="region"):
evidence.AnalyzedScope("base", "head", _digest("change"), ())
packet = _packet()
with pytest.raises(evidence.EvidenceValidationError, match="family_id"):
replace(packet, family_id=cast(str, 7))
def test_unknown_runtime_enums_and_schema_are_refused() -> None:
packet = _packet()
with pytest.raises(evidence.EvidenceValidationError, match="schema"):
replace(packet, schema_version="unknown")
class ForeignOutcome(Enum):
VIOLATION = "violation"
with pytest.raises(evidence.EvidenceValidationError, match="NeutralOutcome"):
replace(_decision(packet), outcome=cast(evidence.NeutralOutcome, ForeignOutcome.VIOLATION))