Add governed institutional capability contracts

This commit is contained in:
2026-08-01 20:57:25 +02:00
parent 7192d32e65
commit 2b4eb0151f
16 changed files with 2203 additions and 249 deletions
+207
View File
@@ -0,0 +1,207 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Protocol, runtime_checkable
CAPABILITY_APPROVAL_REQUESTS = "approvals.requests"
class ApprovalCapabilityError(ValueError):
"""Stable error raised by Approval capability implementations."""
@dataclass(frozen=True, slots=True)
class ApprovalActorSelector:
kind: str
value: str
label: str | None = None
@dataclass(frozen=True, slots=True)
class ApprovalStepDefinition:
key: str
label: str
selectors: tuple[ApprovalActorSelector, ...]
required_approvals: int = 1
rejection_policy: str = "fail_fast"
due_at: datetime | None = None
signature_required: bool = False
forbidden_evidence_roles: tuple[str, ...] = ()
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class ApprovalRequestCreateCommand:
title: str
subject_module: str
subject_type: str
subject_id: str
subject_version: str | None
subject_digest: str
steps: tuple[ApprovalStepDefinition, ...]
description: str | None = None
separation_of_duties: bool = True
unique_actors_across_steps: bool = False
expires_at: datetime | None = None
policy_refs: tuple[str, ...] = ()
evidence_actors: Mapping[str, tuple[str, ...]] = field(default_factory=dict)
template_id: str | None = None
template_revision: int | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class ApprovalTemplateCreateCommand:
key: str
title: str
steps: tuple[ApprovalStepDefinition, ...]
description: str | None = None
separation_of_duties: bool = True
unique_actors_across_steps: bool = False
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class ApprovalTemplateRef:
id: str
key: str
revision: int
state: str
content_sha256: str
@dataclass(frozen=True, slots=True)
class ApprovalDecisionCommand:
outcome: str
reason: str
expected_revision: int
idempotency_key: str
delegated_for_account_id: str | None = None
signature_ref: Mapping[str, object] | None = None
@dataclass(frozen=True, slots=True)
class ApprovalRequestRef:
id: str
revision: int
state: str
current_step_key: str | None = None
@dataclass(frozen=True, slots=True)
class ApprovalDecisionReceipt:
request_id: str
revision: int
state: str
step_key: str
outcome: str
actor_id: str
recorded_at: datetime
receipt_sha256: str
authority_provenance: Mapping[str, object] = field(default_factory=dict)
replayed: bool = False
@dataclass(frozen=True, slots=True)
class ApprovalCheck:
request_id: str
revision: int
state: str
approved: bool
subject_module: str
subject_type: str
subject_id: str
subject_version: str | None
subject_digest: str
completed_at: datetime | None = None
@runtime_checkable
class ApprovalRequestProvider(Protocol):
def create_template(
self,
session: object,
principal: object,
*,
command: ApprovalTemplateCreateCommand,
idempotency_key: str,
) -> ApprovalTemplateRef: ...
def revise_template(
self,
session: object,
principal: object,
*,
template_id: str,
command: ApprovalTemplateCreateCommand,
expected_revision: int,
idempotency_key: str,
) -> ApprovalTemplateRef: ...
def publish_template(
self,
session: object,
principal: object,
*,
template_id: str,
expected_revision: int,
idempotency_key: str,
) -> ApprovalTemplateRef: ...
def create_request(
self,
session: object,
principal: object,
*,
command: ApprovalRequestCreateCommand,
idempotency_key: str,
) -> ApprovalRequestRef: ...
def get_request(
self,
session: object,
principal: object,
*,
request_id: str,
) -> Mapping[str, object] | None: ...
def decide(
self,
session: object,
principal: object,
*,
request_id: str,
command: ApprovalDecisionCommand,
) -> ApprovalDecisionReceipt: ...
def check_approved(
self,
session: object,
principal: object,
*,
request_id: str,
subject_module: str,
subject_type: str,
subject_id: str,
subject_version: str | None,
subject_digest: str,
) -> ApprovalCheck: ...
__all__ = [
"ApprovalActorSelector",
"ApprovalCapabilityError",
"ApprovalCheck",
"ApprovalDecisionCommand",
"ApprovalDecisionReceipt",
"ApprovalRequestCreateCommand",
"ApprovalRequestProvider",
"ApprovalRequestRef",
"ApprovalStepDefinition",
"ApprovalTemplateCreateCommand",
"ApprovalTemplateRef",
"CAPABILITY_APPROVAL_REQUESTS",
]
+692
View File
@@ -0,0 +1,692 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal, Protocol, runtime_checkable
CAPABILITY_ENCRYPTION_KEY_VAULT = "encryption.keyVault"
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION = "encryption.contentProtection"
CAPABILITY_ENCRYPTION_RECOVERY = "encryption.recoveryCeremony"
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT = "encryption.disablePreflight"
CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX = "encryption.keyMaterialProvider."
ENCRYPTION_CONTRACT_VERSION = "1"
ProtectionProfileKind = Literal[
"server_envelope",
"tenant_held",
"end_to_end",
]
KeyLifecycleState = Literal[
"active",
"rotation_pending",
"revoked",
"destruction_scheduled",
"destroyed",
"unavailable",
]
ProtectionState = Literal[
"active",
"migration_pending",
"migrating",
"migrated",
"decrypted",
"exported",
"destroyed",
"unavailable",
]
@dataclass(frozen=True, slots=True)
class KeyMaterialProvisionRequest:
tenant_id: str
vault_id: str
key_version: int
algorithm_suite: str
purpose: str
idempotency_key: str
provider_policy: Mapping[str, object] = field(default_factory=dict)
import_reference: str | None = None
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.key_version < 1:
raise ValueError("Key version must be positive")
for label, value in (
("tenant id", self.tenant_id),
("vault id", self.vault_id),
("algorithm suite", self.algorithm_suite),
("purpose", self.purpose),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class KeyMaterialDescriptor:
provider_id: str
provider_key_ref: str
algorithm_suite: str
state: KeyLifecycleState
created_at: datetime
public_key_ref: str | None = None
imported: bool = False
exportable: bool = False
provider_version: str | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
for label, value in (
("provider id", self.provider_id),
("provider key reference", self.provider_key_ref),
("algorithm suite", self.algorithm_suite),
):
_require_text(value, label)
_reject_secret_mapping(self.provenance, "Provider provenance")
@dataclass(frozen=True, slots=True)
class KeyVaultCreateRequest:
tenant_id: str
vault_id: str
name: str
provider_id: str
purpose: str
algorithm_suite: str
scope_type: str
scope_id: str | None
policy_ref: str
idempotency_key: str
recovery_quorum: int = 2
profile_kind: ProtectionProfileKind = "server_envelope"
import_reference: str | None = None
provider_policy: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.recovery_quorum < 1:
raise ValueError("Recovery quorum must be positive")
for label, value in (
("tenant id", self.tenant_id),
("vault id", self.vault_id),
("name", self.name),
("provider id", self.provider_id),
("purpose", self.purpose),
("algorithm suite", self.algorithm_suite),
("scope type", self.scope_type),
("policy reference", self.policy_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
_reject_secret_mapping(self.provider_policy, "Provider policy")
@dataclass(frozen=True, slots=True)
class KeyVersionRef:
tenant_id: str
vault_id: str
version: int
provider_id: str
provider_key_ref: str
algorithm_suite: str
state: KeyLifecycleState
created_at: datetime
activated_at: datetime | None = None
revoked_at: datetime | None = None
destruction_scheduled_at: datetime | None = None
destroyed_at: datetime | None = None
public_key_ref: str | None = None
imported: bool = False
exportable: bool = False
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class KeyVaultRef:
tenant_id: str
vault_id: str
name: str
provider_id: str
purpose: str
profile_kind: ProtectionProfileKind
scope_type: str
scope_id: str | None
policy_ref: str
recovery_quorum: int
state: str
revision: int
current_key: KeyVersionRef | None
created_at: datetime
updated_at: datetime
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class KeyRotationRequest:
tenant_id: str
vault_id: str
expected_revision: int
reason: str
policy_decision_ref: str
assurance_evidence_ref: str
idempotency_key: str
algorithm_suite: str | None = None
provider_policy: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.expected_revision < 1:
raise ValueError("Expected revision must be positive")
for label, value in (
("tenant id", self.tenant_id),
("vault id", self.vault_id),
("reason", self.reason),
("policy decision reference", self.policy_decision_ref),
("assurance evidence reference", self.assurance_evidence_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
_reject_secret_mapping(self.provider_policy, "Provider policy")
@dataclass(frozen=True, slots=True)
class KeyLifecycleRequest:
tenant_id: str
vault_id: str
key_version: int
expected_revision: int
reason: str
policy_decision_ref: str
assurance_evidence_ref: str
idempotency_key: str
effective_at: datetime | None = None
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.key_version < 1 or self.expected_revision < 1:
raise ValueError("Key version and expected revision must be positive")
for label, value in (
("tenant id", self.tenant_id),
("vault id", self.vault_id),
("reason", self.reason),
("policy decision reference", self.policy_decision_ref),
("assurance evidence reference", self.assurance_evidence_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class ContentProtectionEnvelope:
envelope_id: str
tenant_id: str
owner_module: str
resource_type: str
resource_id: str
profile_kind: ProtectionProfileKind
profile_id: str
provider_id: str
vault_id: str
key_version: int
algorithm_suite: str
ciphertext_ref: str
ciphertext_digest: str
authenticated_context_digest: str
state: ProtectionState
created_at: datetime
wrapped_key_refs: tuple[str, ...] = ()
source_envelope_id: str | None = None
migration_id: str | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.key_version < 1:
raise ValueError("Envelope key version must be positive")
for label, value in (
("envelope id", self.envelope_id),
("tenant id", self.tenant_id),
("owner module", self.owner_module),
("resource type", self.resource_type),
("resource id", self.resource_id),
("profile id", self.profile_id),
("provider id", self.provider_id),
("vault id", self.vault_id),
("algorithm suite", self.algorithm_suite),
("ciphertext reference", self.ciphertext_ref),
("ciphertext digest", self.ciphertext_digest),
("authenticated context digest", self.authenticated_context_digest),
):
_require_text(value, label)
_reject_secret_mapping(self.metadata, "Protection metadata")
@dataclass(frozen=True, slots=True)
class ProtectionRegistrationRequest:
envelope: ContentProtectionEnvelope
idempotency_key: str
policy_decision_ref: str
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
_require_text(self.idempotency_key, "idempotency key")
_require_text(self.policy_decision_ref, "policy decision reference")
@dataclass(frozen=True, slots=True)
class ProtectionMigrationRequest:
tenant_id: str
envelope_id: str
target_provider_id: str
target_vault_id: str
target_key_version: int
target_algorithm_suite: str
mode: Literal["rewrap", "reencrypt", "decrypt", "export", "destroy"]
policy_decision_ref: str
assurance_evidence_ref: str
idempotency_key: str
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.target_key_version < 1:
raise ValueError("Target key version must be positive")
for label, value in (
("tenant id", self.tenant_id),
("envelope id", self.envelope_id),
("target provider id", self.target_provider_id),
("target vault id", self.target_vault_id),
("target algorithm suite", self.target_algorithm_suite),
("policy decision reference", self.policy_decision_ref),
("assurance evidence reference", self.assurance_evidence_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class ProtectionMigrationResult:
migration_id: str
state: Literal[
"requested",
"running",
"succeeded",
"rejected",
"outcome_unknown",
"reconciled",
]
source_envelope_id: str
target_envelope: ContentProtectionEnvelope | None = None
error: str | None = None
evidence_refs: tuple[str, ...] = ()
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class RecoveryRequest:
tenant_id: str
vault_id: str
reason: str
requested_scope: str
policy_decision_ref: str
assurance_evidence_ref: str
idempotency_key: str
expires_at: datetime
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
for label, value in (
("tenant id", self.tenant_id),
("vault id", self.vault_id),
("reason", self.reason),
("requested scope", self.requested_scope),
("policy decision reference", self.policy_decision_ref),
("assurance evidence reference", self.assurance_evidence_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class RecoveryApprovalRequest:
tenant_id: str
recovery_id: str
decision: Literal["approve", "reject"]
reason: str
assurance_evidence_ref: str
expected_revision: int
idempotency_key: str
contract_version: str = ENCRYPTION_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.expected_revision < 1:
raise ValueError("Expected revision must be positive")
for label, value in (
("tenant id", self.tenant_id),
("recovery id", self.recovery_id),
("reason", self.reason),
("assurance evidence reference", self.assurance_evidence_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class RecoveryRef:
tenant_id: str
recovery_id: str
vault_id: str
state: Literal["pending", "approved", "rejected", "expired", "executed"]
requested_scope: str
quorum: int
approvals: int
rejections: int
revision: int
expires_at: datetime
policy_decision_ref: str
execution_ref: str | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = ENCRYPTION_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class DisablePreflightReport:
allowed: bool
protected_count: int
unresolved_count: int
state_counts: Mapping[str, int]
blocking_envelope_refs: tuple[str, ...]
required_actions: tuple[str, ...]
generated_at: datetime
contract_version: str = ENCRYPTION_CONTRACT_VERSION
@runtime_checkable
class EncryptionKeyMaterialProvider(Protocol):
def provision_key(
self,
session: object,
principal: object,
*,
request: KeyMaterialProvisionRequest,
) -> KeyMaterialDescriptor: ...
def schedule_key_destruction(
self,
session: object,
principal: object,
*,
provider_key_ref: str,
effective_at: datetime,
idempotency_key: str,
) -> KeyMaterialDescriptor: ...
def revoke_key(
self,
session: object,
principal: object,
*,
provider_key_ref: str,
reason: str,
idempotency_key: str,
) -> KeyMaterialDescriptor: ...
def key_status(
self,
session: object,
*,
provider_key_ref: str,
) -> KeyMaterialDescriptor | None: ...
@runtime_checkable
class EncryptionKeyVault(Protocol):
def create_vault(
self,
session: object,
principal: object,
*,
request: KeyVaultCreateRequest,
) -> KeyVaultRef: ...
def rotate_key(
self,
session: object,
principal: object,
*,
request: KeyRotationRequest,
) -> KeyVaultRef: ...
def revoke_key(
self,
session: object,
principal: object,
*,
request: KeyLifecycleRequest,
) -> KeyVaultRef: ...
def schedule_destruction(
self,
session: object,
principal: object,
*,
request: KeyLifecycleRequest,
) -> KeyVaultRef: ...
def get_vault(
self,
session: object,
*,
tenant_id: str,
vault_id: str,
) -> KeyVaultRef | None: ...
def reconcile_vault(
self,
session: object,
principal: object,
*,
tenant_id: str,
vault_id: str,
) -> KeyVaultRef: ...
@runtime_checkable
class ContentProtectionRegistry(Protocol):
def register_envelope(
self,
session: object,
principal: object,
*,
request: ProtectionRegistrationRequest,
) -> ContentProtectionEnvelope: ...
def request_migration(
self,
session: object,
principal: object,
*,
request: ProtectionMigrationRequest,
) -> ProtectionMigrationResult: ...
def reconcile_migration(
self,
session: object,
principal: object,
*,
migration_id: str,
) -> ProtectionMigrationResult: ...
def get_envelope(
self,
session: object,
principal: object,
*,
tenant_id: str,
envelope_id: str,
) -> ContentProtectionEnvelope | None: ...
def record_migration_outcome(
self,
session: object,
principal: object,
*,
migration_id: str,
state: Literal["succeeded", "rejected", "outcome_unknown"],
evidence_refs: tuple[str, ...],
target_envelope: ContentProtectionEnvelope | None = None,
error_code: str | None = None,
) -> ProtectionMigrationResult: ...
@runtime_checkable
class EncryptionRecoveryCeremony(Protocol):
def request_recovery(
self,
session: object,
principal: object,
*,
request: RecoveryRequest,
) -> RecoveryRef: ...
def decide_recovery(
self,
session: object,
principal: object,
*,
request: RecoveryApprovalRequest,
) -> RecoveryRef: ...
@runtime_checkable
class EncryptionDisablePreflight(Protocol):
def assess_disable(
self,
session: object,
*,
tenant_id: str | None = None,
limit: int = 100,
) -> DisablePreflightReport: ...
def key_material_provider(
registry: object | None,
provider_id: str,
) -> EncryptionKeyMaterialProvider | None:
capability = _capability(
registry,
f"{CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX}{provider_id}",
)
return capability if isinstance(capability, EncryptionKeyMaterialProvider) else None
def encryption_key_vault(registry: object | None) -> EncryptionKeyVault | None:
value = _capability(registry, CAPABILITY_ENCRYPTION_KEY_VAULT)
return value if isinstance(value, EncryptionKeyVault) else None
def content_protection_registry(
registry: object | None,
) -> ContentProtectionRegistry | None:
value = _capability(registry, CAPABILITY_ENCRYPTION_CONTENT_PROTECTION)
return value if isinstance(value, ContentProtectionRegistry) else None
def encryption_recovery_ceremony(
registry: object | None,
) -> EncryptionRecoveryCeremony | None:
value = _capability(registry, CAPABILITY_ENCRYPTION_RECOVERY)
return value if isinstance(value, EncryptionRecoveryCeremony) else None
def encryption_disable_preflight(
registry: object | None,
) -> EncryptionDisablePreflight | None:
value = _capability(registry, CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT)
return value if isinstance(value, EncryptionDisablePreflight) else None
def _capability(registry: object | None, name: str) -> object | None:
if (
registry is None
or not hasattr(registry, "has_capability")
or not hasattr(registry, "capability")
or not registry.has_capability(name)
):
return None
return registry.capability(name)
def _validate_contract(value: str) -> None:
if value != ENCRYPTION_CONTRACT_VERSION:
raise ValueError("Unsupported encryption contract version")
def _require_text(value: str, label: str) -> None:
if not value.strip():
raise ValueError(f"{label.capitalize()} is required")
def _reject_secret_mapping(value: Mapping[str, object], label: str) -> None:
forbidden = {
"ciphertext",
"credential",
"key",
"key_material",
"password",
"plaintext",
"private_key",
"secret",
"token",
}
keys = {str(key).strip().casefold() for key in value}
if forbidden & keys:
raise ValueError(f"{label} must not contain secret material")
__all__ = [
"CAPABILITY_ENCRYPTION_CONTENT_PROTECTION",
"CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT",
"CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX",
"CAPABILITY_ENCRYPTION_KEY_VAULT",
"CAPABILITY_ENCRYPTION_RECOVERY",
"ContentProtectionEnvelope",
"ContentProtectionRegistry",
"DisablePreflightReport",
"EncryptionDisablePreflight",
"EncryptionKeyMaterialProvider",
"EncryptionKeyVault",
"EncryptionRecoveryCeremony",
"KeyLifecycleRequest",
"KeyMaterialDescriptor",
"KeyMaterialProvisionRequest",
"KeyRotationRequest",
"KeyVaultCreateRequest",
"KeyVaultRef",
"KeyVersionRef",
"ProtectionMigrationRequest",
"ProtectionMigrationResult",
"ProtectionRegistrationRequest",
"RecoveryApprovalRequest",
"RecoveryRef",
"RecoveryRequest",
"content_protection_registry",
"encryption_disable_preflight",
"encryption_key_vault",
"encryption_recovery_ceremony",
"key_material_provider",
]
+321
View File
@@ -0,0 +1,321 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal, Protocol, runtime_checkable
CAPABILITY_IDENTITY_TRUST_DIRECTORY = "identity_trust.directory"
CAPABILITY_IDENTITY_TRUST_ASSURANCE = "identity_trust.assurance"
IDENTITY_TRUST_CONTRACT_VERSION = "1"
DeviceKeyPurpose = Literal["encryption", "signing", "encryption_and_signing"]
DeviceKeyStatus = Literal["active", "revoked", "expired"]
TrustSubjectKind = Literal[
"identity",
"account",
"function",
"postbox",
"external_recipient",
]
_PRIVATE_JWK_FIELDS = frozenset({"d", "p", "q", "dp", "dq", "qi", "oth", "k"})
@dataclass(frozen=True, slots=True)
class DeviceKeyRegistration:
tenant_id: str
identity_id: str
account_id: str
device_id: str
key_id: str
algorithm: str
public_jwk: Mapping[str, object]
purpose: DeviceKeyPurpose = "encryption"
assurance_level: str = "software"
attestation_ref: str | None = None
expires_at: datetime | None = None
idempotency_key: str = ""
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
for label, value in (
("tenant id", self.tenant_id),
("identity id", self.identity_id),
("account id", self.account_id),
("device id", self.device_id),
("key id", self.key_id),
("algorithm", self.algorithm),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
if not self.public_jwk or _PRIVATE_JWK_FIELDS & set(self.public_jwk):
raise ValueError("Only a bounded public JWK may be registered")
@dataclass(frozen=True, slots=True)
class DeviceKeyRef:
tenant_id: str
identity_id: str
account_id: str
device_id: str
key_id: str
algorithm: str
public_jwk: Mapping[str, object]
purpose: DeviceKeyPurpose
assurance_level: str
status: DeviceKeyStatus
epoch: int
registered_at: datetime
attestation_ref: str | None = None
expires_at: datetime | None = None
revoked_at: datetime | None = None
revocation_reason: str | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class KeyEpochRotationRequest:
tenant_id: str
subject_kind: TrustSubjectKind
subject_id: str
reason: str
access_decision_ref: str
idempotency_key: str
history_policy: str = "all_retained"
previous_epoch: int | None = None
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
for label, value in (
("tenant id", self.tenant_id),
("subject id", self.subject_id),
("reason", self.reason),
("access decision reference", self.access_decision_ref),
("idempotency key", self.idempotency_key),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class KeyEpochRef:
tenant_id: str
subject_kind: TrustSubjectKind
subject_id: str
epoch: int
state: Literal["active", "superseded", "revoked"]
history_policy: str
effective_at: datetime
previous_epoch: int | None = None
reason: str | None = None
access_decision_ref: str | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class KeyAccessRequest:
tenant_id: str
account_id: str
device_key_id: str
subject_kind: TrustSubjectKind
subject_id: str
key_epoch: int
access_decision_ref: str
purpose: str
requested_at: datetime
function_assignment_id: str | None = None
delegation_id: str | None = None
resource_ref: str | None = None
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.key_epoch < 1:
raise ValueError("Key epoch must be positive")
for label, value in (
("tenant id", self.tenant_id),
("account id", self.account_id),
("device key id", self.device_key_id),
("subject id", self.subject_id),
("access decision reference", self.access_decision_ref),
("purpose", self.purpose),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class KeyAccessDecision:
allowed: bool
decision_ref: str
reason: str
device_key: DeviceKeyRef | None = None
epoch: KeyEpochRef | None = None
audit_event_ref: str | None = None
requirements: tuple[str, ...] = ()
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
@dataclass(frozen=True, slots=True)
class AssuranceCheckRequest:
tenant_id: str
account_id: str
purpose: str
minimum_level: str
evidence_ref: str
evaluated_at: datetime
maximum_age_seconds: int = 300
device_key_id: str | None = None
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
def __post_init__(self) -> None:
_validate_contract(self.contract_version)
if self.maximum_age_seconds < 1:
raise ValueError("Assurance maximum age must be positive")
for label, value in (
("tenant id", self.tenant_id),
("account id", self.account_id),
("purpose", self.purpose),
("minimum level", self.minimum_level),
("evidence reference", self.evidence_ref),
):
_require_text(value, label)
@dataclass(frozen=True, slots=True)
class AssuranceDecision:
allowed: bool
reason: str
assurance_level: str | None = None
evidence_ref: str | None = None
verified_at: datetime | None = None
expires_at: datetime | None = None
provenance: Mapping[str, object] = field(default_factory=dict)
contract_version: str = IDENTITY_TRUST_CONTRACT_VERSION
@runtime_checkable
class IdentityTrustDirectory(Protocol):
def register_device_key(
self,
session: object,
principal: object,
*,
request: DeviceKeyRegistration,
) -> DeviceKeyRef: ...
def revoke_device_key(
self,
session: object,
principal: object,
*,
tenant_id: str,
key_id: str,
expected_epoch: int,
reason: str,
) -> DeviceKeyRef: ...
def list_device_keys(
self,
session: object,
principal: object,
*,
tenant_id: str,
account_id: str,
active_only: bool = True,
) -> tuple[DeviceKeyRef, ...]: ...
def rotate_epoch(
self,
session: object,
principal: object,
*,
request: KeyEpochRotationRequest,
) -> KeyEpochRef: ...
def resolve_epoch(
self,
session: object,
*,
tenant_id: str,
subject_kind: TrustSubjectKind,
subject_id: str,
epoch: int | None = None,
) -> KeyEpochRef | None: ...
def decide_key_access(
self,
session: object,
principal: object,
*,
request: KeyAccessRequest,
) -> KeyAccessDecision: ...
@runtime_checkable
class IdentityTrustAssurance(Protocol):
def verify_assurance(
self,
session: object,
principal: object,
*,
request: AssuranceCheckRequest,
) -> AssuranceDecision: ...
def identity_trust_directory(
registry: object | None,
) -> IdentityTrustDirectory | None:
capability = _capability(registry, CAPABILITY_IDENTITY_TRUST_DIRECTORY)
return capability if isinstance(capability, IdentityTrustDirectory) else None
def identity_trust_assurance(
registry: object | None,
) -> IdentityTrustAssurance | None:
capability = _capability(registry, CAPABILITY_IDENTITY_TRUST_ASSURANCE)
return capability if isinstance(capability, IdentityTrustAssurance) else None
def _capability(registry: object | None, name: str) -> object | None:
if (
registry is None
or not hasattr(registry, "has_capability")
or not hasattr(registry, "capability")
or not registry.has_capability(name)
):
return None
return registry.capability(name)
def _validate_contract(value: str) -> None:
if value != IDENTITY_TRUST_CONTRACT_VERSION:
raise ValueError("Unsupported identity-trust contract version")
def _require_text(value: str, label: str) -> None:
if not value.strip():
raise ValueError(f"{label.capitalize()} is required")
__all__ = [
"AssuranceCheckRequest",
"AssuranceDecision",
"CAPABILITY_IDENTITY_TRUST_ASSURANCE",
"CAPABILITY_IDENTITY_TRUST_DIRECTORY",
"DeviceKeyRef",
"DeviceKeyRegistration",
"IdentityTrustAssurance",
"IdentityTrustDirectory",
"KeyAccessDecision",
"KeyAccessRequest",
"KeyEpochRef",
"KeyEpochRotationRequest",
"identity_trust_assurance",
"identity_trust_directory",
]
File diff suppressed because it is too large Load Diff
+223
View File
@@ -0,0 +1,223 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass, field
from datetime import datetime
from typing import Protocol, runtime_checkable
CAPABILITY_VOTING_BALLOTS = "voting.ballots"
CAPABILITY_VOTING_PROVIDER_PREFIX = "voting.provider."
VOTING_ASSURANCE_RECORDED = "recorded"
VOTING_ASSURANCE_CONFIDENTIAL = "confidential"
VOTING_ASSURANCE_SECRET = "secret"
VOTING_ASSURANCE_EXTERNAL_CERTIFIED = "external_certified"
class VotingCapabilityError(ValueError):
"""Stable error raised by Voting capability implementations."""
def voting_provider_capability(provider_id: str) -> str:
normalized = str(provider_id or "").strip().lower()
if not normalized or any(
character not in "abcdefghijklmnopqrstuvwxyz0123456789_.-"
for character in normalized
):
raise VotingCapabilityError("Voting provider id is invalid.")
return f"{CAPABILITY_VOTING_PROVIDER_PREFIX}{normalized}"
@dataclass(frozen=True, slots=True)
class VotingOption:
key: str
label: str
description: str | None = None
@dataclass(frozen=True, slots=True)
class VotingElector:
subject_id: str
label: str | None = None
weight: int = 1
provenance: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class VotingBallotCreateCommand:
title: str
method: str
assurance_profile: str
options: tuple[VotingOption, ...]
electorate: tuple[VotingElector, ...]
description: str | None = None
context_module: str | None = None
context_resource_type: str | None = None
context_resource_id: str | None = None
quorum_weight: int = 0
threshold_numerator: int = 1
threshold_denominator: int = 2
allow_replacement: bool = True
opens_at: datetime | None = None
closes_at: datetime | None = None
provider_id: str | None = None
provider_ballot_ref: str | None = None
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class VotingCastCommand:
selections: tuple[str, ...]
idempotency_key: str
elector_id: str | None = None
@dataclass(frozen=True, slots=True)
class VotingBallotRef:
id: str
revision: int
state: str
assurance_profile: str
definition_sha256: str | None = None
electorate_sha256: str | None = None
@dataclass(frozen=True, slots=True)
class VotingReceipt:
ballot_id: str
revision: int
receipt_sha256: str
cast_at: datetime
replaced_previous: bool = False
replayed: bool = False
@dataclass(frozen=True, slots=True)
class VotingResult:
ballot_id: str
revision: int
counts: Mapping[str, int]
weighted_counts: Mapping[str, int]
cast_count: int
cast_weight: int
eligible_count: int
eligible_weight: int
quorum_met: bool
threshold_met: bool
winning_options: tuple[str, ...]
result_sha256: str
evidence: tuple[Mapping[str, object], ...] = ()
@dataclass(frozen=True, slots=True)
class ExternalVotingFinalizationRequest:
tenant_id: str
ballot_id: str
provider_ballot_ref: str
definition_sha256: str
electorate_sha256: str
options: tuple[VotingOption, ...]
eligible_count: int
eligible_weight: int
requested_at: datetime
idempotency_key: str
@runtime_checkable
class ExternalVotingProvider(Protocol):
"""Provider boundary for confidential, secret, or certified voting.
Providers return aggregate results and evidence only. Raw ballots and
provider credentials must not cross this boundary.
"""
def finalize_ballot(
self,
session: object,
principal: object,
*,
request: ExternalVotingFinalizationRequest,
) -> VotingResult: ...
@runtime_checkable
class VotingBallotProvider(Protocol):
def create_ballot(
self,
session: object,
principal: object,
*,
command: VotingBallotCreateCommand,
idempotency_key: str,
) -> VotingBallotRef: ...
def get_ballot(
self,
session: object,
principal: object,
*,
ballot_id: str,
) -> Mapping[str, object] | None: ...
def open_ballot(
self,
session: object,
principal: object,
*,
ballot_id: str,
expected_revision: int,
idempotency_key: str,
) -> VotingBallotRef: ...
def cast_ballot(
self,
session: object,
principal: object,
*,
ballot_id: str,
command: VotingCastCommand,
) -> VotingReceipt: ...
def close_ballot(
self,
session: object,
principal: object,
*,
ballot_id: str,
expected_revision: int,
idempotency_key: str,
) -> VotingResult: ...
def certify_ballot(
self,
session: object,
principal: object,
*,
ballot_id: str,
expected_revision: int,
evidence: Sequence[Mapping[str, object]],
idempotency_key: str,
) -> VotingBallotRef: ...
__all__ = [
"CAPABILITY_VOTING_BALLOTS",
"CAPABILITY_VOTING_PROVIDER_PREFIX",
"ExternalVotingFinalizationRequest",
"ExternalVotingProvider",
"VOTING_ASSURANCE_CONFIDENTIAL",
"VOTING_ASSURANCE_EXTERNAL_CERTIFIED",
"VOTING_ASSURANCE_RECORDED",
"VOTING_ASSURANCE_SECRET",
"VotingBallotCreateCommand",
"VotingBallotProvider",
"VotingBallotRef",
"VotingCapabilityError",
"VotingCastCommand",
"VotingElector",
"VotingOption",
"VotingReceipt",
"VotingResult",
"voting_provider_capability",
]
+44 -15
View File
@@ -7,10 +7,13 @@ import hashlib
import json
from typing import Protocol, runtime_checkable
from govoplan_core.core.events import PlatformEvent
CAPABILITY_WORKFLOW_RUNTIME_WORKER = "workflow.runtimeWorker"
CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS = "workflow.definitionContributions"
CAPABILITY_WORKFLOW_ORCHESTRATION = "workflow.orchestration"
CAPABILITY_WORKFLOW_TRIGGER_DISPATCHER = "workflow.triggerDispatcher"
@dataclass(frozen=True, slots=True)
@@ -126,8 +129,27 @@ class WorkflowRuntimeWorker(Protocol):
*,
now: datetime | None = None,
limit: int = 50,
) -> Mapping[str, object]:
...
) -> Mapping[str, object]: ...
@runtime_checkable
class WorkflowTriggerDispatcher(Protocol):
"""Durable schedule/event start and wait-resumption boundary."""
def dispatch_due(
self,
session: object,
*,
now: datetime | None = None,
limit: int = 50,
) -> Mapping[str, object]: ...
def ingest_event(
self,
session: object,
*,
event: PlatformEvent,
) -> Mapping[str, object]: ...
@runtime_checkable
@@ -137,8 +159,7 @@ class WorkflowDefinitionContributionProvider(Protocol):
session: object,
*,
tenant_ids: Sequence[str] = (),
) -> Mapping[str, object]:
...
) -> Mapping[str, object]: ...
@runtime_checkable
@@ -180,11 +201,20 @@ def workflow_runtime_worker(
):
return None
capability = registry.capability(CAPABILITY_WORKFLOW_RUNTIME_WORKER)
return (
capability
if isinstance(capability, WorkflowRuntimeWorker)
else None
)
return capability if isinstance(capability, WorkflowRuntimeWorker) else None
def workflow_trigger_dispatcher(
registry: object | None,
) -> WorkflowTriggerDispatcher | None:
if (
registry is None
or not hasattr(registry, "has_capability")
or not registry.has_capability(CAPABILITY_WORKFLOW_TRIGGER_DISPATCHER)
):
return None
capability = registry.capability(CAPABILITY_WORKFLOW_TRIGGER_DISPATCHER)
return capability if isinstance(capability, WorkflowTriggerDispatcher) else None
def workflow_definition_contribution_provider(
@@ -193,14 +223,10 @@ def workflow_definition_contribution_provider(
if (
registry is None
or not hasattr(registry, "has_capability")
or not registry.has_capability(
CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS
)
or not registry.has_capability(CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS)
):
return None
capability = registry.capability(
CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS
)
capability = registry.capability(CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS)
return (
capability
if isinstance(capability, WorkflowDefinitionContributionProvider)
@@ -225,15 +251,18 @@ __all__ = [
"CAPABILITY_WORKFLOW_DEFINITION_CONTRIBUTIONS",
"CAPABILITY_WORKFLOW_ORCHESTRATION",
"CAPABILITY_WORKFLOW_RUNTIME_WORKER",
"CAPABILITY_WORKFLOW_TRIGGER_DISPATCHER",
"WorkflowCurrentStepResolution",
"WorkflowDefinitionContribution",
"WorkflowDefinitionContributionProvider",
"WorkflowInstanceRef",
"WorkflowOrchestrationProvider",
"WorkflowRuntimeWorker",
"WorkflowTriggerDispatcher",
"WorkflowStandardStartRequest",
"workflow_definition_contribution_hash",
"workflow_definition_contribution_provider",
"workflow_orchestration_provider",
"workflow_runtime_worker",
"workflow_trigger_dispatcher",
]