feat(detection-core): fact-pack admission and push-consumer contracts

This commit is contained in:
Marius Ciepluch
2026-10-30 17:05:24 +01:00
parent 43fd59f960
commit c924a69514
5 changed files with 190 additions and 2 deletions
+2 -2
View File
@@ -101,13 +101,13 @@ _Requirement IDs use N.M where N is the requirement number in requirements.md an
- _Depends: 4.1, 5.1_
- [ ] 6. Fact packs and consumer interfaces
- [ ] 6.1 Fact-pack admission contract
- [x] 6.1 Fact-pack admission contract
- Ruleset loader admits family fact packs only with review evidence and provenance stamping; packs carry their family tag.
- Done when an unreviewed pack is refused and a reviewed pack stamps provenance in a test.
- _Requirements: 10.1_
- _Boundary: fact-pack contract_
- _Depends: 1.4_
- [ ] 6.2 Consumer interfaces for push and proof
- [x] 6.2 Consumer interfaces for push and proof
- Define the interfaces through which the pre-push transaction consumes traces/verdicts within its deadline and the proof layer attaches demonstrated evidence to a verdict; existing arms express behavior over the core contract.
- Done when both interfaces are exercised by conformance tests from a mock consumer.
- _Requirements: 9.2, 11.1, 11.2, 11.3_
+9
View File
@@ -11,7 +11,9 @@ from openultrasast.core.admission import (
validate_admission_manifest,
)
from openultrasast.core.budget import Budget, BudgetGateFailure, Complexity, check_budget, derive_budget
from openultrasast.core.consumer import PushRequest, PushVerdict, consume_verdict
from openultrasast.core.coverage import Coverage, InstrumentFailure, ResolutionOutcome, check_reads
from openultrasast.core.factpack import FactPack, FactPackError, admit_fact_pack, verify_fact_pack
from openultrasast.core.probes import (
FittedComponentBlocked,
ProbeResult,
@@ -33,6 +35,8 @@ __all__ = [
"BudgetGateFailure",
"Complexity",
"Coverage",
"FactPack",
"FactPackError",
"FittedComponentBlocked",
"Guard",
"Hop",
@@ -41,18 +45,23 @@ __all__ = [
"ProbeResult",
"ProvenClaim",
"ProvenanceMismatch",
"PushRequest",
"PushVerdict",
"ResolutionOutcome",
"ShortcutDetected",
"TraceRecord",
"Witness",
"admit_fact_pack",
"as_clean",
"assert_no_shortcut",
"check_budget",
"check_reads",
"consume_verdict",
"derive_budget",
"require_admission_evidence",
"reversed_pair_probe",
"stamp",
"validate_admission_manifest",
"verify_fact_pack",
"verify_stamp",
]
+55
View File
@@ -0,0 +1,55 @@
"""Consumer contracts for the detection core (Req 9, 11).
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))
__all__ = ["PushRequest", "PushVerdict", "consume_verdict"]
+55
View File
@@ -0,0 +1,55 @@
"""Family fact-pack admission contract for the detection core (Req 10).
New vulnerability families enter only as reviewed fact packs: the pack carries its
family tag and a review digest, and its fact-set digest is stamped so consumers can
refuse tampered content. The core never reads pool or admission labels.
"""
from __future__ import annotations
import hashlib
import json
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import Any
class FactPackError(ValueError):
"""A fact pack is unreviewed or its content does not match its digest."""
@dataclass(frozen=True)
class FactPack:
"""A reviewed family fact pack with provenance."""
family: str
facts: tuple[dict[str, Any], ...]
review_digest: str
fact_set_digest: str
def _digest(payload: object) -> str:
return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()).hexdigest()[:12]
def admit_fact_pack(family: str, facts: Sequence[Mapping[str, Any]], *, review_digest: str) -> FactPack:
"""Admit a pack only with review evidence; stamp its fact-set digest (Req 10.1)."""
if not review_digest:
raise FactPackError("unreviewed fact pack refused: no review digest")
normalized = tuple(dict(fact) for fact in facts)
return FactPack(
family=family,
facts=normalized,
review_digest=review_digest,
fact_set_digest=_digest([family, normalized]),
)
def verify_fact_pack(pack: FactPack, facts: Sequence[Mapping[str, Any]]) -> None:
"""Refuse content that does not match the pack's stamped digest."""
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}")
__all__ = ["FactPack", "FactPackError", "admit_fact_pack", "verify_fact_pack"]
+69
View File
@@ -0,0 +1,69 @@
"""Fact-pack and consumer contract tests for detection-core tasks 6.1-6.2 (Req 9, 10, 11)."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from openultrasast.core.consumer import PushRequest, consume_verdict # noqa: E402
from openultrasast.core.factpack import FactPackError, admit_fact_pack, verify_fact_pack # noqa: E402
from openultrasast.core.trace import TraceRecord # noqa: E402
from openultrasast.core.verdict import BoundedSilence, Judgment, ProvenClaim # noqa: E402
FACTS = ({"id": "obligation:protected_read", "language": "java"},)
def test_unreviewed_fact_pack_is_refused() -> None:
try:
admit_fact_pack("access_control", FACTS, review_digest="")
raise AssertionError("unreviewed pack must be refused")
except FactPackError:
pass
def test_reviewed_fact_pack_stamps_provenance() -> None:
pack = admit_fact_pack("access_control", FACTS, review_digest="review-2026-10-30")
assert pack.family == "access_control"
assert pack.review_digest == "review-2026-10-30"
assert len(pack.fact_set_digest) == 12
verify_fact_pack(pack, FACTS)
try:
verify_fact_pack(pack, FACTS + ({"id": "tampered"},))
raise AssertionError("tampered facts must be refused")
except FactPackError:
pass
def _record() -> TraceRecord:
return TraceRecord(
entry="src/app.py::handler",
hops=(),
operation_kind="protected_read",
resource="credentials",
authority="admin",
judgment=Judgment.UNGUARDED,
digests={"input": "abc"},
)
def test_consumer_accepts_witnessed_claim_and_reasoned_silence() -> None:
claim = ProvenClaim(trace_id="t1", witness="w1", path="src/app.py", line=10, family="access_control")
silence = BoundedSilence(path="src/other.py", reason="unreached", coverage={"entry_points": 0})
verdict = consume_verdict(PushRequest(base="b", head="h", budget_class="dev-pool"), _record(), (claim,), (silence,))
assert verdict.claims == (claim,)
assert verdict.silence == (silence,)
def test_consumer_refuses_claim_without_witness_or_silence_without_reason() -> None:
bad_claim = ProvenClaim(trace_id="t1", witness="", path="src/app.py", line=10, family="access_control")
try:
consume_verdict(PushRequest(base="b", head="h", budget_class="c"), _record(), (bad_claim,), ())
raise AssertionError("witnessless claim must be refused")
except ValueError:
pass
bad_silence = BoundedSilence(path="src/other.py", reason="", coverage={})
try:
consume_verdict(PushRequest(base="b", head="h", budget_class="c"), _record(), (), (bad_silence,))
raise AssertionError("reasonless silence must be refused")
except ValueError:
pass