feat(ttp): implement evidence-shape validation and confidence range constraint
- TolerantTagger.tag validates evidence keys against EVIDENCE_SCHEMA TypedDicts; TypeError (programmer error) propagates instead of being swallowed - IntelEvidence and EmailEvidence expanded from stubs to full per-provider key sets (total=False); IntelEvidence old stub fields replaced wholesale - EVIDENCE_SCHEMA map added to models/ttp.py and imported by base.py - TTPTag __table_args__ gains confidence [0,1] CheckConstraint (DB-enforced) - xfail removed from test_confidence_outside_range_rejected_at_insert and test_evidence_shape_violation_propagates_as_typeerror — both now pass - TypeError removed from _SWALLOWED_EXCS fuzz list; test_intel_evidence_keys updated to assert the real provider key set
This commit is contained in:
@@ -140,7 +140,8 @@ _SWALLOWED_EXCS: tuple[type[Exception], ...] = (
|
||||
ValueError,
|
||||
RuntimeError,
|
||||
KeyError,
|
||||
TypeError,
|
||||
# TypeError is intentionally NOT swallowed — it propagates as a
|
||||
# programmer-error signal (bad evidence shape). See TolerantTagger.tag.
|
||||
AttributeError,
|
||||
LookupError,
|
||||
OSError,
|
||||
|
||||
@@ -67,7 +67,19 @@ def test_intel_evidence_keys() -> None:
|
||||
keys = (
|
||||
IntelEvidence.__required_keys__ | IntelEvidence.__optional_keys__
|
||||
)
|
||||
assert keys == {"intel_uuid", "provider", "category", "score"}
|
||||
assert keys == {
|
||||
# AbuseIPDB
|
||||
"abuseipdb_categories", "abuseipdb_score", "abuse_confidence_score",
|
||||
# GreyNoise
|
||||
"greynoise_classification", "greynoise_tags", "greynoise_name",
|
||||
# Feodo
|
||||
"feodo_listed", "feodo_malware_family", "first_seen_feodo", "malware_family",
|
||||
# ThreatFox
|
||||
"threatfox_threat_types", "threatfox_ioc_types", "threatfox_malware_families",
|
||||
"threat_types", "malware_families", "ioc_types",
|
||||
# Aggregate meta-rule
|
||||
"aggregate_verdict", "bumped_rule_ids",
|
||||
}
|
||||
|
||||
|
||||
def test_canary_fingerprint_evidence_keys() -> None:
|
||||
@@ -140,10 +152,6 @@ def test_lifter_emits_evidence_matching_typeddict(
|
||||
# ── Negative case: shape violation propagates (impl phase) ──────────
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
strict=True,
|
||||
reason="impl phase: TolerantTagger currently swallows TypeError",
|
||||
)
|
||||
def test_evidence_shape_violation_propagates_as_typeerror() -> None:
|
||||
"""A lifter that emits an evidence dict with a key not in its
|
||||
``TypedDict`` is a programmer error — it MUST propagate past the
|
||||
|
||||
@@ -169,7 +169,6 @@ def test_guard_runs_before_super_init() -> None:
|
||||
# ── confidence range guard (impl phase) ─────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.xfail(strict=True, reason="impl phase: confidence range guard not yet enforced")
|
||||
async def test_confidence_outside_range_rejected_at_insert(session: AsyncSession) -> None:
|
||||
"""``confidence`` outside [0.0, 1.0] must be rejected. The contract
|
||||
schema currently types it as bare ``float`` without a range
|
||||
|
||||
Reference in New Issue
Block a user