feat(detection-core): validate differential proof attachment

This commit is contained in:
Marius Ciepluch
2026-10-31 17:52:25 +01:00
parent 2a44ad938c
commit d4fcde615b
5 changed files with 895 additions and 1 deletions
+142
View File
@@ -0,0 +1,142 @@
"""Differential-proof application against one exact defect claim."""
from __future__ import annotations
from dataclasses import replace
from openultrasast.core import canonical, provenance
from openultrasast.core._proof_attestation import proof_attestation_bytes, validate_proof_artifacts
from openultrasast.core.evidence import EvidenceBasis, EvidenceRecord, derive_packet_digest
from openultrasast.core.proof import (
DifferentialProofRecord,
ProofAttachment,
ProofOutcome,
ProofTrustPolicy,
ProofValidationError,
ProofVerificationReceipt,
derive_producer_identity_digest,
)
from openultrasast.core.verdict_v2 import DefectClaim
def _validate_claim_binding(claim: DefectClaim, attachment: ProofAttachment) -> None:
try:
claim.__post_init__()
attachment.__post_init__()
attachment.proof.__post_init__()
attachment.proof.producer.__post_init__()
attachment.proof.affected.__post_init__()
attachment.proof.safe.__post_init__()
attachment.proof.control.__post_init__()
attachment.verification.__post_init__()
except (TypeError, ValueError) as exc:
raise ProofValidationError(f"proof attachment identity validation failed: {exc}") from exc
record = claim.record
if record.decision.basis is EvidenceBasis.EXECUTION_BACKED:
raise ProofValidationError("an execution-backed claim cannot replace its proof attachment")
if attachment.family_id != record.packet.family_id or attachment.packet_digest != derive_packet_digest(record.packet):
raise ProofValidationError("proof family or packet does not match the defect claim")
if attachment.location != claim.location:
raise ProofValidationError("proof attachment does not match the exact claim location")
def _validate_change_and_outcome(record: EvidenceRecord, proof: DifferentialProofRecord) -> None:
change = record.packet.provenance.change_input_digest
if proof.affected.input_digest != change or proof.control.affected_change_input_digest != change:
raise ProofValidationError("affected proof input does not match the evidence change input")
if proof.safe.input_digest != proof.control.safe_input_digest:
raise ProofValidationError("safe proof input does not match the reviewed control")
if proof.affected.input_digest == proof.safe.input_digest:
raise ProofValidationError("affected and safe proof inputs must be distinct")
if proof.outcome is not ProofOutcome.DEMONSTRATED or not proof.affected.observed or proof.safe.observed:
raise ProofValidationError("only demonstrated affected-only behavior can upgrade evidence basis")
def _validate_trust(
proof: DifferentialProofRecord,
verification: ProofVerificationReceipt,
policy: ProofTrustPolicy,
) -> str:
try:
policy.__post_init__()
except (TypeError, ValueError) as exc:
raise ProofValidationError(f"proof policy validation failed: {exc}") from exc
producer_digest = derive_producer_identity_digest(proof.producer)
if producer_digest not in policy.reviewed_producer_digests:
raise ProofValidationError("proof producer is not independently reviewed")
reviewer = proof.control.reviewer_identity
if reviewer not in policy.reviewed_control_reviewer_digests:
raise ProofValidationError("control reviewer is not admitted by the proof policy")
producer_ids = (proof.producer.producer_id, canonical.content_digest(proof.producer.producer_id), producer_digest)
if reviewer in producer_ids:
raise ProofValidationError("proof producer cannot supply a self-asserted safe control")
expected = (
producer_digest,
proof.record_digest,
proof.affected.result_digest,
proof.safe.result_digest,
proof.witness.content_digest,
)
actual = (
verification.producer_identity_digest,
verification.proof_record_digest,
verification.affected_result_digest,
verification.safe_result_digest,
verification.witness_content_digest,
)
if actual != expected:
raise ProofValidationError("proof verification identities do not match the proof record")
return producer_digest
def _resolve_artifacts(
proof: DifferentialProofRecord,
verification: ProofVerificationReceipt,
policy: ProofTrustPolicy,
resolver: canonical.ContentResolver,
) -> None:
try:
expected = proof_attestation_bytes(
verification.producer_identity_digest,
verification.proof_record_digest,
verification.affected_result_digest,
verification.safe_result_digest,
verification.witness_content_digest,
)
validate_proof_artifacts(
proof.control.control_manifest,
proof.witness,
verification.attestation,
expected,
resolver,
policy.trusted_store_identities,
policy.maximum_artifact_bytes,
)
except (canonical.ArtifactResolutionError, canonical.ArtifactValidationError, ValueError) as exc:
raise ProofValidationError(f"proof artifact validation failed: {exc}") from exc
def attach_differential_proof(
claim: DefectClaim,
attachment: ProofAttachment,
policy: ProofTrustPolicy,
resolver: canonical.ContentResolver,
) -> DefectClaim:
if not isinstance(claim, DefectClaim) or not isinstance(attachment, ProofAttachment):
raise ProofValidationError("proof application requires a DefectClaim and ProofAttachment")
if not isinstance(policy, ProofTrustPolicy):
raise ProofValidationError("proof application requires a ProofTrustPolicy")
_validate_claim_binding(claim, attachment)
_validate_change_and_outcome(claim.record, attachment.proof)
producer_digest = _validate_trust(attachment.proof, attachment.verification, policy)
_resolve_artifacts(attachment.proof, attachment.verification, policy, resolver)
decision = replace(
claim.record.decision,
basis=EvidenceBasis.EXECUTION_BACKED,
provenance=provenance.DecisionProvenance(None, None, None, attachment.proof.record_digest, producer_digest, None),
)
record = EvidenceRecord(claim.record.packet, decision, "")
return DefectClaim(record, EvidenceBasis.EXECUTION_BACKED, claim.location, claim.change_anchor)
__all__ = ["attach_differential_proof"]
@@ -0,0 +1,56 @@
"""Canonical proof-attestation bytes and bounded artifact resolution."""
from __future__ import annotations
from collections.abc import Collection
from openultrasast.core import canonical
PROOF_ATTESTATION_SCHEMA = "openultrasast.detection-core.proof-attestation.v2"
def proof_attestation_bytes(
producer_identity_digest: str,
proof_record_digest: str,
affected_result_digest: str,
safe_result_digest: str,
witness_content_digest: str,
) -> bytes:
payload: dict[str, canonical.JsonValue] = {
"schema_version": PROOF_ATTESTATION_SCHEMA,
"producer_identity_digest": producer_identity_digest,
"proof_record_digest": proof_record_digest,
"affected_result_digest": affected_result_digest,
"safe_result_digest": safe_result_digest,
"witness_content_digest": witness_content_digest,
}
return canonical.canonical_record_bytes(payload, known_schema_versions=(PROOF_ATTESTATION_SCHEMA,))
def validate_proof_artifacts(
control: canonical.ContentRef,
witness: canonical.ContentRef,
attestation: canonical.ContentRef,
expected_attestation: bytes,
resolver: canonical.ContentResolver,
trusted_stores: Collection[str],
maximum_bytes: int,
) -> None:
for reference in (control, witness):
canonical.resolve_content(
reference,
resolver,
trusted_store_identities=trusted_stores,
maximum_bytes=maximum_bytes,
)
resolved = canonical.resolve_content(
attestation,
resolver,
trusted_store_identities=trusted_stores,
maximum_bytes=maximum_bytes,
)
if resolved != expected_attestation:
raise canonical.ArtifactValidationError("proof attestation does not bind the exact verification identities")
__all__ = ["PROOF_ATTESTATION_SCHEMA", "proof_attestation_bytes", "validate_proof_artifacts"]
+345
View File
@@ -0,0 +1,345 @@
"""Canonical differential-proof validation for execution-backed evidence."""
from __future__ import annotations
import re
from dataclasses import dataclass
from enum import Enum
from openultrasast.core import canonical
from openultrasast.core.semantics import SourceLocation
from openultrasast.core.verdict_v2 import DefectClaim
DIFFERENTIAL_PROOF_SCHEMA = "openultrasast.detection-core.differential-proof.v2"
_DIGEST = re.compile(r"[0-9a-f]{64}")
class ProofValidationError(ValueError):
"""A proof attachment cannot reproduce execution-backed evidence."""
class ProofOutcome(Enum):
DEMONSTRATED = "demonstrated"
INCONCLUSIVE = "inconclusive"
FAILED = "failed"
def _digest(value: object, field: str) -> None:
if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
raise ProofValidationError(f"{field} must be a full lowercase SHA-256 digest")
def _text(value: object, field: str) -> None:
if not isinstance(value, str) or not value:
raise ProofValidationError(f"{field} must be nonempty text")
def _positive(value: object, field: str) -> None:
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
raise ProofValidationError(f"{field} must be a positive integer")
def _ref_value(value: canonical.ContentRef) -> canonical.JsonValue:
return {
"artifact_id": value.artifact_id,
"content_digest": value.content_digest,
"media_type": value.media_type,
"byte_length": value.byte_length,
"retention_policy_digest": value.retention_policy_digest,
"storage_receipt_digest": value.storage_receipt_digest,
}
def _location_value(value: SourceLocation) -> canonical.JsonValue:
return {"path": value.path, "line": value.line, "column": value.column, "symbol": value.symbol}
@dataclass(frozen=True)
class ProofProducerIdentity:
producer_id: str
verifier_revision: str
sandbox_profile_digest: str
oracle_contract_digest: str
review_digest: str
def __post_init__(self) -> None:
_text(self.producer_id, "producer_id")
_text(self.verifier_revision, "verifier_revision")
for value, field in (
(self.sandbox_profile_digest, "sandbox_profile_digest"),
(self.oracle_contract_digest, "oracle_contract_digest"),
(self.review_digest, "review_digest"),
):
_digest(value, field)
def _producer_value(value: ProofProducerIdentity) -> canonical.JsonValue:
return {
"producer_id": value.producer_id,
"verifier_revision": value.verifier_revision,
"sandbox_profile_digest": value.sandbox_profile_digest,
"oracle_contract_digest": value.oracle_contract_digest,
"review_digest": value.review_digest,
}
def derive_producer_identity_digest(value: ProofProducerIdentity) -> str:
value.__post_init__()
return canonical.content_digest(_producer_value(value))
@dataclass(frozen=True)
class ProofSideResult:
input_digest: str
observed: bool
observation_digest: str
run_count: int
result_digest: str
def __post_init__(self) -> None:
_digest(self.input_digest, "proof side input_digest")
if not isinstance(self.observed, bool):
raise ProofValidationError("proof side observed must be boolean")
_digest(self.observation_digest, "proof side observation_digest")
_positive(self.run_count, "proof side run_count")
expected = derive_proof_side_result_digest(self)
if not self.result_digest:
object.__setattr__(self, "result_digest", expected)
elif self.result_digest != expected:
raise ProofValidationError("proof side result digest cannot be re-derived")
def _side_value(value: ProofSideResult) -> dict[str, canonical.JsonValue]:
return {
"input_digest": value.input_digest,
"observed": value.observed,
"observation_digest": value.observation_digest,
"run_count": value.run_count,
}
def derive_proof_side_result_digest(value: ProofSideResult) -> str:
return canonical.content_digest(_side_value(value))
@dataclass(frozen=True)
class ControlBinding:
affected_change_input_digest: str
safe_input_digest: str
control_manifest: canonical.ContentRef
reviewer_identity: str
binding_digest: str
def __post_init__(self) -> None:
_digest(self.affected_change_input_digest, "affected_change_input_digest")
_digest(self.safe_input_digest, "safe_input_digest")
if not isinstance(self.control_manifest, canonical.ContentRef):
raise ProofValidationError("control_manifest must be a ContentRef")
_digest(self.reviewer_identity, "control reviewer_identity")
expected = derive_control_binding_digest(self)
if not self.binding_digest:
object.__setattr__(self, "binding_digest", expected)
elif self.binding_digest != expected:
raise ProofValidationError("control binding digest cannot be re-derived")
def _control_value(value: ControlBinding) -> dict[str, canonical.JsonValue]:
return {
"affected_change_input_digest": value.affected_change_input_digest,
"safe_input_digest": value.safe_input_digest,
"control_manifest": _ref_value(value.control_manifest),
"reviewer_identity": value.reviewer_identity,
}
def derive_control_binding_digest(value: ControlBinding) -> str:
return canonical.content_digest(_control_value(value))
@dataclass(frozen=True)
class DifferentialProofRecord:
schema_version: str
producer: ProofProducerIdentity
affected: ProofSideResult
safe: ProofSideResult
control: ControlBinding
witness: canonical.ContentRef
outcome: ProofOutcome
record_digest: str
def __post_init__(self) -> None:
if self.schema_version != DIFFERENTIAL_PROOF_SCHEMA:
raise ProofValidationError("unknown differential proof schema")
if not isinstance(self.producer, ProofProducerIdentity):
raise ProofValidationError("producer must use ProofProducerIdentity")
if not isinstance(self.affected, ProofSideResult) or not isinstance(self.safe, ProofSideResult):
raise ProofValidationError("proof sides must use ProofSideResult")
if not isinstance(self.control, ControlBinding) or not isinstance(self.witness, canonical.ContentRef):
raise ProofValidationError("proof control and witness have invalid types")
if not isinstance(self.outcome, ProofOutcome):
raise ProofValidationError("proof outcome is invalid")
expected = derive_differential_proof_record_digest(self)
if not self.record_digest:
object.__setattr__(self, "record_digest", expected)
elif self.record_digest != expected:
raise ProofValidationError("differential proof digest cannot be re-derived")
def _proof_value(value: DifferentialProofRecord) -> canonical.JsonValue:
return {
"schema_version": value.schema_version,
"producer": _producer_value(value.producer),
"affected": {**_side_value(value.affected), "result_digest": value.affected.result_digest},
"safe": {**_side_value(value.safe), "result_digest": value.safe.result_digest},
"control": {**_control_value(value.control), "binding_digest": value.control.binding_digest},
"witness": _ref_value(value.witness),
"outcome": value.outcome.value,
}
def derive_differential_proof_record_digest(value: DifferentialProofRecord) -> str:
return canonical.content_digest(_proof_value(value))
@dataclass(frozen=True)
class ProofVerificationReceipt:
producer_identity_digest: str
proof_record_digest: str
affected_result_digest: str
safe_result_digest: str
witness_content_digest: str
attestation: canonical.ContentRef
receipt_digest: str
def __post_init__(self) -> None:
for value, field in (
(self.producer_identity_digest, "producer_identity_digest"),
(self.proof_record_digest, "proof_record_digest"),
(self.affected_result_digest, "affected_result_digest"),
(self.safe_result_digest, "safe_result_digest"),
(self.witness_content_digest, "witness_content_digest"),
):
_digest(value, field)
if not isinstance(self.attestation, canonical.ContentRef):
raise ProofValidationError("attestation must be a ContentRef")
expected = derive_proof_verification_receipt_digest(self)
if not self.receipt_digest:
object.__setattr__(self, "receipt_digest", expected)
elif self.receipt_digest != expected:
raise ProofValidationError("proof verification receipt digest cannot be re-derived")
def _verification_value(value: ProofVerificationReceipt) -> canonical.JsonValue:
return {
"producer_identity_digest": value.producer_identity_digest,
"proof_record_digest": value.proof_record_digest,
"affected_result_digest": value.affected_result_digest,
"safe_result_digest": value.safe_result_digest,
"witness_content_digest": value.witness_content_digest,
"attestation": _ref_value(value.attestation),
}
def derive_proof_verification_receipt_digest(value: ProofVerificationReceipt) -> str:
return canonical.content_digest(_verification_value(value))
@dataclass(frozen=True)
class ProofTrustPolicy:
reviewed_producer_digests: tuple[str, ...]
reviewed_control_reviewer_digests: tuple[str, ...]
trusted_store_identities: tuple[str, ...]
maximum_artifact_bytes: int
policy_digest: str
def __post_init__(self) -> None:
for values, field in (
(self.reviewed_producer_digests, "reviewed producer"),
(self.reviewed_control_reviewer_digests, "reviewed control reviewer"),
):
if not values:
raise ProofValidationError(f"{field} identities must be nonempty")
for value in values:
_digest(value, field)
canonical.require_unique_identities(values, kind=field)
if not self.trusted_store_identities or not all(isinstance(value, str) and value for value in self.trusted_store_identities):
raise ProofValidationError("trusted store identities must be nonempty text")
canonical.require_unique_identities(self.trusted_store_identities, kind="trusted store")
_positive(self.maximum_artifact_bytes, "maximum_artifact_bytes")
expected = derive_proof_trust_policy_digest(self)
if not self.policy_digest:
object.__setattr__(self, "policy_digest", expected)
elif self.policy_digest != expected:
raise ProofValidationError("proof trust policy digest cannot be re-derived")
def derive_proof_trust_policy_digest(value: ProofTrustPolicy) -> str:
payload: canonical.JsonValue = {
"reviewed_producer_digests": tuple(sorted(value.reviewed_producer_digests)),
"reviewed_control_reviewer_digests": tuple(sorted(value.reviewed_control_reviewer_digests)),
"trusted_store_identities": tuple(sorted(value.trusted_store_identities)),
"maximum_artifact_bytes": value.maximum_artifact_bytes,
}
return canonical.content_digest(payload)
@dataclass(frozen=True)
class ProofAttachment:
proof_id: str
family_id: str
packet_digest: str
location: SourceLocation
proof: DifferentialProofRecord
verification: ProofVerificationReceipt
def __post_init__(self) -> None:
_text(self.family_id, "proof family_id")
_digest(self.packet_digest, "proof packet_digest")
if not isinstance(self.location, SourceLocation):
raise ProofValidationError("proof location must be a SourceLocation")
if not isinstance(self.proof, DifferentialProofRecord) or not isinstance(self.verification, ProofVerificationReceipt):
raise ProofValidationError("proof attachment has invalid proof or verification")
expected = derive_proof_attachment_id(self)
if not self.proof_id:
object.__setattr__(self, "proof_id", expected)
elif self.proof_id != expected:
raise ProofValidationError("proof attachment identity cannot be re-derived")
def derive_proof_attachment_id(value: ProofAttachment) -> str:
payload: canonical.JsonValue = {
"family_id": value.family_id,
"packet_digest": value.packet_digest,
"location": _location_value(value.location),
"proof_record_digest": value.proof.record_digest,
"verification_receipt_digest": value.verification.receipt_digest,
}
return canonical.content_digest(payload)
def attach_differential_proof(
claim: DefectClaim,
attachment: ProofAttachment,
policy: ProofTrustPolicy,
resolver: canonical.ContentResolver,
) -> DefectClaim:
"""Validate and attach affected-only proof without granting rollout permission."""
from openultrasast.core._proof_apply import attach_differential_proof as apply_proof
return apply_proof(claim, attachment, policy, resolver)
__all__ = [
"ControlBinding",
"DIFFERENTIAL_PROOF_SCHEMA",
"DifferentialProofRecord",
"ProofAttachment",
"ProofOutcome",
"ProofProducerIdentity",
"ProofSideResult",
"ProofTrustPolicy",
"ProofValidationError",
"ProofVerificationReceipt",
"attach_differential_proof",
"derive_producer_identity_digest",
]