feat(access-control-prover): 10.2 witness kind, scope and resource matching

This commit is contained in:
Marius Ciepluch
2026-10-29 19:05:51 +01:00
parent 8ad304ffeb
commit cf60e02804
4 changed files with 237 additions and 1 deletions
@@ -12,12 +12,16 @@ from __future__ import annotations
import re
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING
from ..facts import SemanticFacts
from ..ir import FileIR, FunctionIR, trailing_name
from .facts import DischargerFact, ObligationFacts
from .shapes import _call_matches, _constraint_provenance, _is_literal, _operations, _permissive
if TYPE_CHECKING:
from .abstract_domain import AuthorityRef, ResourceRef
SCOPES = ("decorator", "router", "statement", "hop")
@@ -42,6 +46,10 @@ class Discharge:
provenance: str
scope: str # SCOPES
detail: str = "" # identifier only: the decorator or call name that witnessed the discharge
# Requirement 2.2 (access-control-prover): the resource identity and authority a witness binds.
# None means the binding was not established (unknown), matching the abstract domain's unknown grades.
resource: ResourceRef | None = None
authority: AuthorityRef | None = None
def find_operations(
@@ -0,0 +1,94 @@
"""Witness kind, scope and resource matching (task 10.2, Reqs 2.1, 2.2, 2.5-2.7, 11.1).
A witness governs an obligation only when every required dimension is MUST. This module
matches the kind, scope and resource dimensions; authority matching (task 10.3) and
dominance (task 10.1) join through the same algebra. Definite mismatches are NO, unresolved
compatibility is UNKNOWN (never a broad match, never a definite rejection), per Req 2.7.
"""
from .abstract_domain import (
EvidenceRelation,
ResourceRef,
WitnessMatch,
conjoin_dimensions,
parse_resource,
)
from .facts import OperationFact
from .operations import Discharge, Operation
_WHOLE_HANDLER_SCOPES = frozenset({"decorator", "router"})
def match_witness(witness: Discharge, operation: Operation, fact: OperationFact | None = None) -> WitnessMatch:
"""Match one witness against one operation on kind, scope and resource."""
if witness.kind not in operation.requires:
return _match(EvidenceRelation.NO, EvidenceRelation.NO, witness, operation)
if _out_of_scope(witness, operation):
return _match(EvidenceRelation.NO, EvidenceRelation.NO, witness, operation)
resource_relation = _resource_relation(witness, operation, fact)
relation = conjoin_dimensions(
(EvidenceRelation.MUST, resource_relation),
)
return _match(relation, resource_relation, witness, operation)
def _out_of_scope(witness: Discharge, operation: Operation) -> bool:
same_function = (witness.path, witness.function) == (operation.path, operation.function)
if witness.scope in _WHOLE_HANDLER_SCOPES:
return not same_function
if witness.scope == "hop":
return False
return not same_function
def _resource_relation(witness: Discharge, operation: Operation, fact: OperationFact | None) -> EvidenceRelation:
witness_resource = witness.resource
if witness_resource is None:
return EvidenceRelation.UNKNOWN
if witness_resource.kind == "request_global":
if fact is not None and witness.kind in fact.request_global_kinds:
return EvidenceRelation.MUST
return EvidenceRelation.UNKNOWN
operation_resource = parse_resource(operation.resource)
if operation_resource.kind == "unknown":
return EvidenceRelation.UNKNOWN
if witness_resource.kind == "unknown":
return EvidenceRelation.UNKNOWN
if witness_resource.kind == "exact":
return _exact_relation(witness_resource, operation_resource)
return _alias_relation(witness_resource, operation_resource)
def _exact_relation(witness_resource: ResourceRef, operation_resource: ResourceRef) -> EvidenceRelation:
if operation_resource.kind == "exact":
if witness_resource.values == operation_resource.values:
return EvidenceRelation.MUST
return EvidenceRelation.NO
return _alias_relation(witness_resource, operation_resource)
def _alias_relation(witness_resource: ResourceRef, operation_resource: ResourceRef) -> EvidenceRelation:
if set(witness_resource.values) & set(operation_resource.values):
return EvidenceRelation.MAY
if witness_resource.kind == "exact" and operation_resource.kind == "exact":
return EvidenceRelation.NO
return EvidenceRelation.UNKNOWN
def _match(
relation: EvidenceRelation,
resource_relation: EvidenceRelation,
witness: Discharge,
operation: Operation,
) -> WitnessMatch:
evidence = (
f"witness:{witness.fact_id}",
f"operation:{operation.fact_id}",
f"resource:{operation.resource or 'unknown'}",
)
return WitnessMatch(
relation=relation,
resource_relation=resource_relation,
authority_relation=EvidenceRelation.UNKNOWN, # task 10.3 owns authority matching
evidence=evidence,
)