From fd3d8ac1e695c9b38c0626bbf922ccfa77208825 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:02:04 +0100 Subject: [PATCH] feat(access-control-prover): define production family contract --- .kiro/specs/access-control-prover/tasks.md | 2 +- .../semantic/obligations/core_contract.py | 82 ++++++++++++ tests/test_obligation_core_contract.py | 121 ++++++++++++++++++ 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/openultrasast/semantic/obligations/core_contract.py create mode 100644 tests/test_obligation_core_contract.py diff --git a/.kiro/specs/access-control-prover/tasks.md b/.kiro/specs/access-control-prover/tasks.md index 42f7082..2ebaeb1 100644 --- a/.kiro/specs/access-control-prover/tasks.md +++ b/.kiro/specs/access-control-prover/tasks.md @@ -331,7 +331,7 @@ its final resolution, dominance, fact-acquisition and measurement semantics with - _Depends: 12.6, 13.3_ - [ ] 14. Reviewed detection-core fact adapter -- [ ] 14.1 Define the production access-control family contract +- [x] 14.1 Define the production access-control family contract - Encode the versioned access-control security object, roles, three decision rules, residual question and closed incompleteness reasons through public detection-core types. - Keep target names confined to the reviewed ecosystem declaration while pinning every digest-affecting field, status, origin policy and decision semantic declared by the design. - Done when the canonical production contract validates, round-trips deterministically, and rejects every altered or undeclared role, decision, field and reason fixture. diff --git a/src/openultrasast/semantic/obligations/core_contract.py b/src/openultrasast/semantic/obligations/core_contract.py new file mode 100644 index 0000000..be389b7 --- /dev/null +++ b/src/openultrasast/semantic/obligations/core_contract.py @@ -0,0 +1,82 @@ +"""Reviewed production access-control contract for detection-core v2.""" + +from __future__ import annotations + +from openultrasast.core import evidence, family, semantics + + +class AccessControlContractError(family.FamilyContractError): + """A contract does not equal the reviewed production access-control contract.""" + + +_ORIGIN = family.RelationOriginPolicy.PRODUCT_OR_REVIEWED_FACT +_TEXT_FIELDS = ( + family.FieldSpec("operation", semantics.FieldKind.TEXT, True), + family.FieldSpec("boundary", semantics.FieldKind.TEXT, True), +) + + +def _relation_role(role: str, kind: semantics.RelationKind) -> family.RoleSpec: + return family.RoleSpec(role, (), (kind,), _ORIGIN) + + +def _build_contract() -> family.FamilyContract: + required = ("operation", "constraint", "security_object") + complete = (semantics.RelationStatus.COMPLETE,) + return family.FamilyContract( + family_id="access_control", + family_version="2.0.0", + schema_version=evidence.EVIDENCE_PACKET_SCHEMA, + ecosystems=("express",), + security_object=family.ObjectSpec("entry_operation_authorization", _TEXT_FIELDS, ("operation", "boundary")), + roles=( + _relation_role("operation", semantics.RelationKind.OPERATION_EFFECT), + _relation_role("constraint", semantics.RelationKind.CONSTRAINT_EFFECT), + family.RoleSpec("security_object", _TEXT_FIELDS, (), _ORIGIN), + _relation_role("framework_factory", semantics.RelationKind.REGISTRATION), + _relation_role("framework_instance", semantics.RelationKind.REGISTRATION), + _relation_role("entry_handler", semantics.RelationKind.INVOCATION), + _relation_role("entry_registration", semantics.RelationKind.REGISTRATION), + ), + decisions=( + family.DecisionRule("unguarded", evidence.NeutralOutcome.VIOLATION, required, complete), + family.DecisionRule("discharged", evidence.NeutralOutcome.SATISFIED, required, complete), + family.DecisionRule( + "indeterminate", + evidence.NeutralOutcome.INDETERMINATE, + ("constraint",), + (semantics.RelationStatus.INCOMPLETE, semantics.RelationStatus.UNRESOLVED), + ), + ), + residual_questions=( + family.ResidualQuestion("is_path_authorized", required, ("unguarded", "discharged", "indeterminate")), + ), + indeterminate_reasons=("missing_constraint_relation", "ambiguous_constraint_relation"), + ) + + +_PRODUCTION_ACCESS_CONTROL_CONTRACT = _build_contract() +ACCESS_CONTROL_CONTRACT_DIGEST = family.family_contract_digest(_PRODUCTION_ACCESS_CONTROL_CONTRACT) + + +def production_access_control_contract() -> family.FamilyContract: + """Return the immutable reviewed production access-control contract.""" + return _PRODUCTION_ACCESS_CONTROL_CONTRACT + + +def validate_access_control_contract(contract: family.FamilyContract) -> None: + """Refuse any contract that differs from the reviewed production identity.""" + try: + actual_digest = family.family_contract_digest(contract) + except (family.FamilyContractError, TypeError) as exc: + raise AccessControlContractError("contract does not match the reviewed production contract") from exc + if actual_digest != ACCESS_CONTROL_CONTRACT_DIGEST: + raise AccessControlContractError("contract does not match the reviewed production contract") + + +__all__ = [ + "ACCESS_CONTROL_CONTRACT_DIGEST", + "AccessControlContractError", + "production_access_control_contract", + "validate_access_control_contract", +] diff --git a/tests/test_obligation_core_contract.py b/tests/test_obligation_core_contract.py new file mode 100644 index 0000000..21760b9 --- /dev/null +++ b/tests/test_obligation_core_contract.py @@ -0,0 +1,121 @@ +"""Production access-control family contract conformance.""" + +from collections.abc import Callable +from dataclasses import replace + +import pytest + +from openultrasast.core import evidence, family, semantics +from openultrasast.semantic.obligations.core_contract import ( + ACCESS_CONTROL_CONTRACT_DIGEST, + AccessControlContractError, + production_access_control_contract, + validate_access_control_contract, +) + + +def test_production_contract_pins_the_reviewed_access_control_vocabulary() -> None: + contract = production_access_control_contract() + + assert contract.family_id == "access_control" + assert contract.family_version == "2.0.0" + assert contract.schema_version == evidence.EVIDENCE_PACKET_SCHEMA + assert contract.ecosystems == ("express",) + assert contract.security_object == family.ObjectSpec( + "entry_operation_authorization", + ( + family.FieldSpec("operation", semantics.FieldKind.TEXT, True), + family.FieldSpec("boundary", semantics.FieldKind.TEXT, True), + ), + ("operation", "boundary"), + ) + origin = family.RelationOriginPolicy.PRODUCT_OR_REVIEWED_FACT + assert contract.roles == ( + family.RoleSpec("operation", (), (semantics.RelationKind.OPERATION_EFFECT,), origin), + family.RoleSpec("constraint", (), (semantics.RelationKind.CONSTRAINT_EFFECT,), origin), + family.RoleSpec("security_object", contract.security_object.fields, (), origin), + family.RoleSpec("framework_factory", (), (semantics.RelationKind.REGISTRATION,), origin), + family.RoleSpec("framework_instance", (), (semantics.RelationKind.REGISTRATION,), origin), + family.RoleSpec("entry_handler", (), (semantics.RelationKind.INVOCATION,), origin), + family.RoleSpec("entry_registration", (), (semantics.RelationKind.REGISTRATION,), origin), + ) + required = ("operation", "constraint", "security_object") + assert contract.decisions == ( + family.DecisionRule( + "unguarded", + evidence.NeutralOutcome.VIOLATION, + required, + (semantics.RelationStatus.COMPLETE,), + ), + family.DecisionRule( + "discharged", + evidence.NeutralOutcome.SATISFIED, + required, + (semantics.RelationStatus.COMPLETE,), + ), + family.DecisionRule( + "indeterminate", + evidence.NeutralOutcome.INDETERMINATE, + ("constraint",), + (semantics.RelationStatus.INCOMPLETE, semantics.RelationStatus.UNRESOLVED), + ), + ) + assert contract.residual_questions == ( + family.ResidualQuestion( + "is_path_authorized", + required, + ("unguarded", "discharged", "indeterminate"), + ), + ) + assert contract.indeterminate_reasons == ("missing_constraint_relation", "ambiguous_constraint_relation") + + +def test_production_contract_has_one_deterministic_canonical_identity() -> None: + first = production_access_control_contract() + second = production_access_control_contract() + + family.validate_contract(first) + validate_access_control_contract(first) + assert first == second + assert family.family_contract_digest(first) == ACCESS_CONTROL_CONTRACT_DIGEST + assert family.family_contract_digest(second) == ACCESS_CONTROL_CONTRACT_DIGEST + + +def _add_role(contract: family.FamilyContract) -> family.FamilyContract: + extra = family.RoleSpec( + "undeclared", + (), + (semantics.RelationKind.CALL,), + family.RelationOriginPolicy.PRODUCT_OR_REVIEWED_FACT, + ) + return replace(contract, roles=(*contract.roles, extra)) + + +def _alter_decision(contract: family.FamilyContract) -> family.FamilyContract: + changed = replace(contract.decisions[0], required_roles=("constraint",)) + return replace(contract, decisions=(changed, *contract.decisions[1:])) + + +def _alter_field(contract: family.FamilyContract) -> family.FamilyContract: + changed = replace(contract.security_object.fields[0], kind=semantics.FieldKind.INTEGER) + security_object = replace(contract.security_object, fields=(changed, *contract.security_object.fields[1:])) + return replace(contract, security_object=security_object) + + +@pytest.mark.parametrize( + "mutate", + ( + lambda contract: replace(contract, ecosystems=("other",)), + _add_role, + _alter_decision, + _alter_field, + lambda contract: replace(contract, indeterminate_reasons=(*contract.indeterminate_reasons, "undeclared_reason")), + ), +) +def test_exact_validator_rejects_altered_or_undeclared_contract_values( + mutate: Callable[[family.FamilyContract], family.FamilyContract], +) -> None: + contract = production_access_control_contract() + + with pytest.raises(AccessControlContractError, match="does not match the reviewed production contract"): + validate_access_control_contract(mutate(contract))