feat(access-control-prover): 10.3 authority matching through promoted implications

This commit is contained in:
Marius Ciepluch
2026-10-29 19:11:30 +01:00
parent cf60e02804
commit 7eb491dc82
3 changed files with 164 additions and 13 deletions
@@ -1,17 +1,22 @@
"""Witness kind, scope and resource matching (task 10.2, Reqs 2.1, 2.2, 2.5-2.7, 11.1).
"""Witness kind, scope, resource and authority matching (tasks 10.2-10.3, Reqs 2.1, 2.2, 2.5-2.8, 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.
matches the kind, scope, resource and authority dimensions; dominance (task 10.1) joins
through the same algebra. Definite mismatches are NO, unresolved compatibility is UNKNOWN
(never a broad match, never a definite rejection), per Req 2.7. Authority implications come
only from promoted knowledge and their fact identifiers are preserved as evidence (Req 2.8).
"""
from .abstract_domain import (
AuthorityImplication,
AuthorityRef,
EvidenceRelation,
ResourceRef,
WitnessMatch,
authority_satisfies,
conjoin_dimensions,
parse_resource,
supporting_implications,
)
from .facts import OperationFact
from .operations import Discharge, Operation
@@ -19,17 +24,25 @@ 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."""
def match_witness(
witness: Discharge,
operation: Operation,
fact: OperationFact | None = None,
*,
required_authority: AuthorityRef | None = None,
implications: tuple[AuthorityImplication, ...] = (),
) -> WitnessMatch:
"""Match one witness against one operation on kind, scope, resource and authority."""
if witness.kind not in operation.requires:
return _match(EvidenceRelation.NO, EvidenceRelation.NO, witness, operation)
return _match(EvidenceRelation.NO, EvidenceRelation.NO, EvidenceRelation.UNKNOWN, witness, operation, ())
if _out_of_scope(witness, operation):
return _match(EvidenceRelation.NO, EvidenceRelation.NO, witness, operation)
return _match(EvidenceRelation.NO, EvidenceRelation.NO, EvidenceRelation.UNKNOWN, witness, operation, ())
resource_relation = _resource_relation(witness, operation, fact)
authority_relation, implication_ids = _authority_relation(witness, required_authority, implications)
relation = conjoin_dimensions(
(EvidenceRelation.MUST, resource_relation),
(EvidenceRelation.MUST, resource_relation, authority_relation),
)
return _match(relation, resource_relation, witness, operation)
return _match(relation, resource_relation, authority_relation, witness, operation, implication_ids)
def _out_of_scope(witness: Discharge, operation: Operation) -> bool:
@@ -75,20 +88,41 @@ def _alias_relation(witness_resource: ResourceRef, operation_resource: ResourceR
return EvidenceRelation.UNKNOWN
def _authority_relation(
witness: Discharge,
required_authority: AuthorityRef | None,
implications: tuple[AuthorityImplication, ...],
) -> tuple[EvidenceRelation, tuple[str, ...]]:
"""Decide the authority dimension (Req 2.5-2.8); preserve implication fact ids."""
if required_authority is None:
return EvidenceRelation.MUST, ()
held_authority = witness.authority
if held_authority is None or held_authority.is_unknown or required_authority.is_unknown:
return EvidenceRelation.UNKNOWN, ()
relation = authority_satisfies(held_authority, required_authority, implications)
used_ids = tuple(
f"implication:{implication.id}" for implication in supporting_implications(held_authority, required_authority, implications)
)
return relation, used_ids
def _match(
relation: EvidenceRelation,
resource_relation: EvidenceRelation,
authority_relation: EvidenceRelation,
witness: Discharge,
operation: Operation,
implication_ids: tuple[str, ...],
) -> WitnessMatch:
evidence = (
f"witness:{witness.fact_id}",
f"operation:{operation.fact_id}",
f"resource:{operation.resource or 'unknown'}",
*implication_ids,
)
return WitnessMatch(
relation=relation,
resource_relation=resource_relation,
authority_relation=EvidenceRelation.UNKNOWN, # task 10.3 owns authority matching
authority_relation=authority_relation,
evidence=evidence,
)