<USER_REQUEST>
Meridian detectors init · PY

"""Phase 10 — Capability Detection.

 

Facts (pure observation) + Sites (where compliance-relevant code

lives) become CapabilityClaim (an assertion that a specific

capability_id is evidenced, with a traceable link back to the facts

that justify it).

 

Hybrid by design, per Karthik's approval:

    - rules/*.yaml + rule_dsl.py + rule_engine.py cover the 80-90%

      case: "do these fact kinds exist at these site kinds." No

      graph traversal, no cross-module reasoning — deliberately, so

      most capabilities are configuration, not code.

    - python_detectors/ is the escape hatch for the ~10-20% that

      genuinely need graph traversal, multi-hop relationships, or

      cross-file correlation the declarative DSL can't express:

      RBAC (multi-hop CALLS traversal), Data_Minimization (fan-in

      counting via dependents_of), Retention_Enforcement (shortest-path

      correlation between two facts in unrelated functions).

 

Hard boundary, enforced at load time for both paths: every

capability_id — from a YAML rule's `capability:` field or a Python

detector's `capability_id` class attribute — is validated against

ComplianceGraphResolver.all_capability_ids() before any claim can be

emitted. A typo is a startup error, not a silently wrong claim.

 

Hard boundary #2: Fact.capability_hints is read by NOTHING in this

package. A fact's kind (FactKind, e.g. ENCRYPTION_CALL) is what rules

and detectors match against — never the hint. Rules and detectors are

the sole authority on which capability a fact evidences; the hint is

indexing metadata for FactStore.by_capability_hint(), nothing more.

See test_detectors_boundary.py for the structural proof.

"""Meridian detectors models · PY

"""Phase 10 — CapabilityClaim.

 

The output of both the YAML rule engine and every Python detector.

Deliberately thin: no blended confidence (that's Phase 11.1 Evidence's

job — source confidence × rule_confidence), no embedded Fact objects

(suppo
<truncated 45215 bytes>
: [PII_MODEL_SITE]

minimum_matches: 1

base_confidence: 0.9

""")

    rule = load_rule(path, VALID_CAPS)

    assert rule.rule_id == "encryption_at_rest"

    assert rule.capability_id == "Encryption_At_Rest"

    assert rule.required_facts == (FactKind.ENCRYPTION_CALL,)

    assert rule.site_kinds == (SiteKind.PII_MODEL_SITE,)

    assert rule.minimum_matches == 1

    assert rule.base_confidence == 0.9

 

 

def test_load_rule_defaults(tmp_path: Path) -> None:

    path = _write_rule(tmp_path, "minimal", """

capability: Access_Control

required_facts: [AUTH_DECORATOR]

""")

    rule = load_rule(path, VALID_CAPS)

    assert rule.site_kinds == ()

    assert rule.minimum_matches == 1

    assert rule.base_confidence == 1.0

    assert rule.description == ""

 

 

def test_unknown_capability_id_raises(tmp_path: Path) -> None:

    """The exact case Karthik flagged repeatedly across this project:

    a capability_id that doesn't exist in meridian.db must fail loudly

    at load time, not produce a silently-empty or wrong claim."""

    path = _write_rule(tmp_path, "bad", """

capability: Not_A_Real_Capability

required_facts: [ENCRYPTION_CALL]

""")

    with pytest.raises(RuleLoadError, match="not in meridian.db"):

        load_rule(path, VALID_CAPS)

 

 

def test_unknown_fact_kind_raises(tmp_path: Path) -> None:

    path = _write_rule(tmp_path, "bad", """

capability: Access_Control

required_facts: [NOT_A_REAL_FACT_KIND]

""")

    with pytest.raises(RuleLoadError, match="not a real FactKind"):

        load_rule(path, VALID_CAPS)

 

 

def test_unknown_site_kind_raises(tmp_path: Path) -> None:

    path = _write_rule(tmp_path, "bad", """

capability: Access_Control

required_facts: [AUTH_DECORATOR]

site_kinds: [NOT_A_REAL_SITE_KIND]

""")

    with pytest.raises(RuleLoadError, match="not a real SiteKind"):

        load_rule(path, VALID_CAP
<truncated 28132 bytes>

NOTE: The output was truncated because it was too long. Use a more targeted query or a smaller range to get the information you need.