From 4ddbc18691a763d13a9e96fe75f34b314e74030d Mon Sep 17 00:00:00 2001 From: Yernat Yestekov <2068106+doublewhy@users.noreply.github.com> Date: Fri, 14 Aug 2026 14:41:18 -0700 Subject: [PATCH 1/2] refactor: resolve sonar complexity and length violations in package modules Part of turning the dev Sonar quality gate green (raes-strict fails on any new-code violation). Fifteen recently-touched package modules each carried one violation: two files exactly one line over the 500-line cap (S104), four functions over 100 lines (S138), and nine functions with cyclomatic complexity 11-13 against the 10 threshold. All changes are behavior-preserving extractions with no public API, contract, or schema change: - raes/composition/_expand.py: move per-import provenance assembly into _import_provenance_additions - raes/phase_contracts.py: hoist the two constraint-pointer shape predicates out of _validate_constraint - raes_backend_libvirt/capability_envelope.py: split the operating-system identity probe out of _out_of_envelope_terms - raes_backend_protocols/provisioner_capabilities.py: extract operating-system row and artifact-kind validation helpers - raes_backend_stubs/stubs.py: extract the operations fold from apply - raes_conformance/snapshot_semantics.py: extract entry and disclosure builders from _snapshot_from_envelope - raes_contracts/apparatus.py, contracts/capabilities.py, realization_envelope_carrier.py: extract validator helper functions - raes_contracts/contracts/bundle.py: move _runtime_schema_bundle and its imports into contracts/bundle_runtime.py (bundle output verified identical by tools/check_generated_schemas.py) - raes_processor/planner/operations.py: extract ordered apply/delete op builders - raes_processor/semantics/realization_runtime_evaluation.py: hoist the observation-corroboration predicate - raes_runtime/backend_calls.py: move the ApplyResult shape validators into backend_call_contracts.py - raes_runtime/control_plane_api_participant_retrieval.py: fold the three governed-view routes onto one shared resolve/audit flow - raes_runtime/control_plane_store.py: move snapshot payload builders into control_plane_store_payloads.py (also keeps the file under the repo's own 500-line policy cap) Verification: nox -s tests (7,005 passed, coverage gate green), ruff format and lint clean, tools/check_generated_schemas.py identical, tools/check_repo_policy.py pass. Closes #1153 Co-Authored-By: Claude Fable 5 --- .../packages/raes/composition/_expand.py | 23 +++- .../python/packages/raes/phase_contracts.py | 36 +++--- .../capability_envelope.py | 48 +++++--- .../provisioner_capabilities.py | 65 ++++++----- .../packages/raes_backend_stubs/stubs.py | 48 +++++--- .../conformance/snapshot_semantics.py | 71 +++++++----- .../packages/raes_contracts/apparatus.py | 58 ++++++---- .../raes_contracts/contracts/bundle.py | 89 +-------------- .../contracts/bundle_runtime.py | 99 ++++++++++++++++ .../raes_contracts/contracts/capabilities.py | 54 +++++---- .../realization_envelope_carrier.py | 30 +++-- .../raes_processor/planner/operations.py | 34 ++++-- .../realization_runtime_evaluation.py | 35 ++++-- .../raes_runtime/backend_call_contracts.py | 75 ++++++++++++ .../packages/raes_runtime/backend_calls.py | 74 +----------- ...control_plane_api_participant_retrieval.py | 108 +++++++++--------- .../raes_runtime/control_plane_store.py | 80 ++----------- .../control_plane_store_payloads.py | 87 ++++++++++++++ 18 files changed, 639 insertions(+), 475 deletions(-) create mode 100644 implementations/python/packages/raes_contracts/contracts/bundle_runtime.py create mode 100644 implementations/python/packages/raes_runtime/backend_call_contracts.py create mode 100644 implementations/python/packages/raes_runtime/control_plane_store_payloads.py diff --git a/implementations/python/packages/raes/composition/_expand.py b/implementations/python/packages/raes/composition/_expand.py index 2dcde676..df35136b 100644 --- a/implementations/python/packages/raes/composition/_expand.py +++ b/implementations/python/packages/raes/composition/_expand.py @@ -45,9 +45,10 @@ SDLParserLimits, SDLSourceParseOptions, ) -from ..instantiate import _bind_scenario_content +from ..instantiate import _bind_scenario_content, _BoundScenarioResult from ..module_registry import ( Lockfile, + ResolvedModule, TrustPolicy, _VerifiedSourceBundle, load_lockfile, @@ -327,6 +328,25 @@ def _expand_one_import( context.budget.check_namespaces(namespaced_payload, path=import_path) merged = _merge_sections(merged, namespaced_payload, path=import_path) + return (merged, *_import_provenance_additions(resolved_import, import_decl, bound, inner_provenance, symbols)) + + +def _import_provenance_additions( + resolved_import: ResolvedModule, + import_decl: ImportDecl, + bound: _BoundScenarioResult, + inner_provenance: ExpansionProvenance, + symbols: dict[str, dict[str, str] | set[str]], +) -> tuple[ + list[ResolvedImportProvenance], + list[CapabilityConstraint], + list[ExplicitnessProvenanceRecord], + list[RealizationDesignationRecord], + list[RealizationConstraintRecord], +]: + """Build the namespaced provenance additions contributed by one import.""" + + namespace = import_decl.namespace import_records: list[ResolvedImportProvenance] = [ _resolved_import_record(resolved_import, requested=import_decl, bindings=bound) ] @@ -349,7 +369,6 @@ def _expand_one_import( for record in inner_provenance.realization_constraints ] return ( - merged, import_records, capability_constraints, explicitness_records, diff --git a/implementations/python/packages/raes/phase_contracts.py b/implementations/python/packages/raes/phase_contracts.py index 2f6cb7a3..e85495a4 100644 --- a/implementations/python/packages/raes/phase_contracts.py +++ b/implementations/python/packages/raes/phase_contracts.py @@ -153,6 +153,26 @@ def _validate_record(self) -> ResolvedImportProvenance: return self +def _is_ordinary_constraint_pointer(parts: list[str]) -> bool: + return len(parts) == 4 and (parts[1], parts[3]) in { + ("nodes", "os"), + ("nodes", "os_distribution"), + ("nodes", "os_version"), + ("nodes", "architecture"), + ("infrastructure", "count"), + } + + +def _is_process_limit_constraint_pointer(parts: list[str]) -> bool: + return ( + len(parts) == 9 + and parts[1] == "nodes" + and parts[3:7] == ["runtime", "operational_policy", "resource_limits", "process_limits"] + and parts[7].isdigit() + and parts[8] in {"soft", "hard"} + ) + + class CapabilityConstraint(FrozenPhaseModel): """Pre-instantiation constraint retained for one concrete field.""" @@ -169,21 +189,7 @@ def _validate_constraint(self) -> CapabilityConstraint: if not self.field_pointer.startswith("/") or _JSON_POINTER_RE.fullmatch(self.field_pointer) is None: raise ValueError("field_pointer must be a non-root RFC 6901 JSON Pointer") parts = self.field_pointer.split("/") - ordinary_pointer = len(parts) == 4 and (parts[1], parts[3]) in { - ("nodes", "os"), - ("nodes", "os_distribution"), - ("nodes", "os_version"), - ("nodes", "architecture"), - ("infrastructure", "count"), - } - process_limit_pointer = ( - len(parts) == 9 - and parts[1] == "nodes" - and parts[3:7] == ["runtime", "operational_policy", "resource_limits", "process_limits"] - and parts[7].isdigit() - and parts[8] in {"soft", "hard"} - ) - if not ordinary_pointer and not process_limit_pointer: + if not _is_ordinary_constraint_pointer(parts) and not _is_process_limit_constraint_pointer(parts): raise ValueError( "field_pointer must address /nodes//(os|os_distribution|os_version|architecture), " "/infrastructure//count, or " diff --git a/implementations/python/packages/raes_backend_libvirt/capability_envelope.py b/implementations/python/packages/raes_backend_libvirt/capability_envelope.py index 89c81229..39806800 100644 --- a/implementations/python/packages/raes_backend_libvirt/capability_envelope.py +++ b/implementations/python/packages/raes_backend_libvirt/capability_envelope.py @@ -186,25 +186,35 @@ def _out_of_envelope_terms( if term and term not in supported: yield (dimension.code, address, term), _envelope_diagnostic(dimension, address, term) if resource_type == NODE_RESOURCE_TYPE: - family = _os_family(payload) - distribution = _os_distribution(payload) - version = _os_version(payload) or None - if distribution and not capabilities.supports_operating_system( - family=family, - distribution=distribution, - version=version, - ): - identity = "/".join((family, distribution, version or "")) - yield ( - (_CODE_UNSUPPORTED_OPERATING_SYSTEM, address, identity), - Diagnostic( - code=_CODE_UNSUPPORTED_OPERATING_SYSTEM, - domain=_DOMAIN, - address=address, - message=f"Libvirt backend does not realize operating-system identity '{identity}'.", - severity=Severity.ERROR, - ), - ) + yield from _out_of_envelope_operating_system(address, payload, capabilities) + + +def _out_of_envelope_operating_system( + address: str, + payload: Mapping[str, object], + capabilities: ProvisionerCapabilities, +) -> Iterator[tuple[tuple[str, str, str], Diagnostic]]: + """Yield the unsupported operating-system identity of a node payload, if any.""" + + family = _os_family(payload) + distribution = _os_distribution(payload) + version = _os_version(payload) or None + if distribution and not capabilities.supports_operating_system( + family=family, + distribution=distribution, + version=version, + ): + identity = "/".join((family, distribution, version or "")) + yield ( + (_CODE_UNSUPPORTED_OPERATING_SYSTEM, address, identity), + Diagnostic( + code=_CODE_UNSUPPORTED_OPERATING_SYSTEM, + domain=_DOMAIN, + address=address, + message=f"Libvirt backend does not realize operating-system identity '{identity}'.", + severity=Severity.ERROR, + ), + ) def _materialized_payloads(plan: ProvisioningPlan) -> Iterator[tuple[str, str, Mapping[str, object]]]: diff --git a/implementations/python/packages/raes_backend_protocols/provisioner_capabilities.py b/implementations/python/packages/raes_backend_protocols/provisioner_capabilities.py index c6e7e180..141c9017 100644 --- a/implementations/python/packages/raes_backend_protocols/provisioner_capabilities.py +++ b/implementations/python/packages/raes_backend_protocols/provisioner_capabilities.py @@ -45,6 +45,40 @@ def _require_string_values(name: str, values: frozenset[str], *, required: bool raise ValueError(f"ProvisionerCapabilities.{name} must not contain empty strings") +def _validate_operating_system_rows(capabilities: "ProvisionerCapabilities") -> None: + os_keys = [(entry.family, entry.distribution) for entry in capabilities.operating_systems] + if len(os_keys) != len(set(os_keys)): + raise ValueError( + "ProvisionerCapabilities.operating_systems must not contain duplicate family/distribution rows" + ) + undeclared_families = { + entry.family + for entry in capabilities.operating_systems + if entry.family not in capabilities.supported_os_families + } + if undeclared_families: + raise ValueError( + "ProvisionerCapabilities.operating_systems families must be present in supported_os_families: " + + ", ".join(sorted(undeclared_families)) + ) + + +def _validated_artifact_kinds(capabilities: "ProvisionerCapabilities") -> frozenset[GeneratedArtifactKind]: + try: + normalized = frozenset(GeneratedArtifactKind(kind) for kind in capabilities.supported_generated_artifact_kinds) + except ValueError as exc: + raise ValueError("ProvisionerCapabilities contains an unknown generated artifact kind") from exc + if capabilities.supports_generated_artifacts and not normalized: + raise ValueError( + "ProvisionerCapabilities that support generated artifacts must declare supported_generated_artifact_kinds" + ) + if not capabilities.supports_generated_artifacts and normalized: + raise ValueError( + "ProvisionerCapabilities supported_generated_artifact_kinds require supports_generated_artifacts=True" + ) + return normalized + + def _validate_account_support(capabilities: "ProvisionerCapabilities") -> None: if capabilities.supports_accounts and not capabilities.supported_account_features: raise ValueError("ProvisionerCapabilities that support accounts must declare supported_account_features") @@ -92,19 +126,7 @@ def __post_init__(self) -> None: "capabilities.provisioner.supported_os_families", self.supported_os_families, ) - os_keys = [(entry.family, entry.distribution) for entry in self.operating_systems] - if len(os_keys) != len(set(os_keys)): - raise ValueError( - "ProvisionerCapabilities.operating_systems must not contain duplicate family/distribution rows" - ) - undeclared_families = { - entry.family for entry in self.operating_systems if entry.family not in self.supported_os_families - } - if undeclared_families: - raise ValueError( - "ProvisionerCapabilities.operating_systems families must be present in supported_os_families: " - + ", ".join(sorted(undeclared_families)) - ) + _validate_operating_system_rows(self) validate_controlled_vocabulary_scope_values( "capabilities.provisioner.supported_node_architectures", self.supported_node_architectures, @@ -128,22 +150,7 @@ def __post_init__(self) -> None: if self.max_total_nodes is not None and self.max_total_nodes < 1: raise ValueError("ProvisionerCapabilities.max_total_nodes must be positive when provided") _validate_account_support(self) - try: - normalized_artifact_kinds = frozenset( - GeneratedArtifactKind(kind) for kind in self.supported_generated_artifact_kinds - ) - except ValueError as exc: - raise ValueError("ProvisionerCapabilities contains an unknown generated artifact kind") from exc - object.__setattr__(self, "supported_generated_artifact_kinds", normalized_artifact_kinds) - if self.supports_generated_artifacts and not normalized_artifact_kinds: - raise ValueError( - "ProvisionerCapabilities that support generated artifacts must declare " - "supported_generated_artifact_kinds" - ) - if not self.supports_generated_artifacts and normalized_artifact_kinds: - raise ValueError( - "ProvisionerCapabilities supported_generated_artifact_kinds require supports_generated_artifacts=True" - ) + object.__setattr__(self, "supported_generated_artifact_kinds", _validated_artifact_kinds(self)) def supports_operating_system( self, diff --git a/implementations/python/packages/raes_backend_stubs/stubs.py b/implementations/python/packages/raes_backend_stubs/stubs.py index 8749b750..448a5f96 100644 --- a/implementations/python/packages/raes_backend_stubs/stubs.py +++ b/implementations/python/packages/raes_backend_stubs/stubs.py @@ -43,6 +43,34 @@ ] +def _applied_entries( + plan: ProvisioningPlan, + snapshot: RuntimeSnapshot, +) -> tuple[dict[str, SnapshotEntry], list[str]]: + """Fold the plan's operations into snapshot entries and changed addresses.""" + + entries = dict(snapshot.entries) + changed_addresses: list[str] = [] + for op in plan.operations: + if op.action == ChangeAction.DELETE: + entries.pop(op.address, None) + changed_addresses.append(op.address) + continue + status = "unchanged" if op.action == ChangeAction.UNCHANGED else "applied" + entries[op.address] = SnapshotEntry( + address=op.address, + domain=RuntimeDomain.PROVISIONING, + resource_type=op.resource_type, + payload=op.payload, + ordering_dependencies=op.ordering_dependencies, + refresh_dependencies=op.refresh_dependencies, + status=status, + ) + if op.action != ChangeAction.UNCHANGED: + changed_addresses.append(op.address) + return entries, changed_addresses + + class StubProvisioner: """In-memory provisioner.""" @@ -74,25 +102,7 @@ def apply( ) ], ) - entries = dict(snapshot.entries) - changed_addresses: list[str] = [] - for op in plan.operations: - if op.action == ChangeAction.DELETE: - entries.pop(op.address, None) - changed_addresses.append(op.address) - continue - status = "unchanged" if op.action == ChangeAction.UNCHANGED else "applied" - entries[op.address] = SnapshotEntry( - address=op.address, - domain=RuntimeDomain.PROVISIONING, - resource_type=op.resource_type, - payload=op.payload, - ordering_dependencies=op.ordering_dependencies, - refresh_dependencies=op.refresh_dependencies, - status=status, - ) - if op.action != ChangeAction.UNCHANGED: - changed_addresses.append(op.address) + entries, changed_addresses = _applied_entries(plan, snapshot) if self._realization_envelope is None: return ApplyResult( diff --git a/implementations/python/packages/raes_conformance/conformance/snapshot_semantics.py b/implementations/python/packages/raes_conformance/conformance/snapshot_semantics.py index 5afb7c8a..ffa1da26 100644 --- a/implementations/python/packages/raes_conformance/conformance/snapshot_semantics.py +++ b/implementations/python/packages/raes_conformance/conformance/snapshot_semantics.py @@ -38,9 +38,8 @@ from raes_conformance.conformance.diagnostics import _SEMANTIC_INVALID_DIAGNOSTIC_CODE, _diagnostic -def _snapshot_from_envelope(payload: dict[str, Any]) -> RuntimeSnapshot: - validated = RuntimeSnapshotEnvelopeModel.model_validate(payload) - entries = { +def _snapshot_entries(validated: RuntimeSnapshotEnvelopeModel) -> dict[str, SnapshotEntry]: + return { address: SnapshotEntry( address=entry.address, domain=RuntimeDomain(entry.domain), @@ -52,8 +51,44 @@ def _snapshot_from_envelope(payload: dict[str, Any]) -> RuntimeSnapshot: ) for address, entry in validated.entries.items() } + + +def _realization_disclosures( + validated: RuntimeSnapshotEnvelopeModel, +) -> tuple[RealizationObservationDisclosure, ...]: + return tuple( + RealizationObservationDisclosure( + address=entry.address, + field_path=entry.field_path, + domain=entry.domain, + requirement_kind=entry.requirement_kind, + verification_scope=entry.verification_scope, + observation_strength=entry.observation_strength, + observed_value=entry.observed_value, + operating_system=( + ObservedOperatingSystemIdentity( + family=entry.operating_system.family, + distribution=entry.operating_system.distribution, + version=entry.operating_system.version, + ) + if entry.operating_system is not None + else None + ), + operation_id=entry.operation_id, + envelope_digest=entry.envelope_digest, + configuration_digest=entry.configuration_digest, + observer_version=entry.observer_version, + sequence=entry.sequence, + binding_verified=entry.binding_verified, + ) + for entry in validated.realization_observations + ) + + +def _snapshot_from_envelope(payload: dict[str, Any]) -> RuntimeSnapshot: + validated = RuntimeSnapshotEnvelopeModel.model_validate(payload) return RuntimeSnapshot( - entries=entries, + entries=_snapshot_entries(validated), orchestration_results={ address: result.model_dump(mode="json") for address, result in validated.orchestration_results.items() }, @@ -119,33 +154,7 @@ def _snapshot_from_envelope(payload: dict[str, Any]) -> RuntimeSnapshot: for context_id, context in validated.time_management_contexts.items() }, time_model_state=validated.time_model_state, - realization_observations=tuple( - RealizationObservationDisclosure( - address=entry.address, - field_path=entry.field_path, - domain=entry.domain, - requirement_kind=entry.requirement_kind, - verification_scope=entry.verification_scope, - observation_strength=entry.observation_strength, - observed_value=entry.observed_value, - operating_system=( - ObservedOperatingSystemIdentity( - family=entry.operating_system.family, - distribution=entry.operating_system.distribution, - version=entry.operating_system.version, - ) - if entry.operating_system is not None - else None - ), - operation_id=entry.operation_id, - envelope_digest=entry.envelope_digest, - configuration_digest=entry.configuration_digest, - observer_version=entry.observer_version, - sequence=entry.sequence, - binding_verified=entry.binding_verified, - ) - for entry in validated.realization_observations - ), + realization_observations=_realization_disclosures(validated), metadata=dict(validated.metadata), ) diff --git a/implementations/python/packages/raes_contracts/apparatus.py b/implementations/python/packages/raes_contracts/apparatus.py index dc5d3734..2cc39299 100644 --- a/implementations/python/packages/raes_contracts/apparatus.py +++ b/implementations/python/packages/raes_contracts/apparatus.py @@ -106,6 +106,37 @@ def admits(self, value: int | str) -> bool: ) +def _validate_observation_and_limit_capabilities(declaration: RealizationSupportDeclaration) -> None: + if any(not kind.strip() for kind in declaration.observation_capabilities): + raise ValueError("observation_capabilities must not contain empty concern kinds") + if any( + not isinstance(capability, RealizationObservationCapability) + for capability in declaration.observation_capabilities.values() + ): + raise TypeError("observation_capabilities values must be RealizationObservationCapability") + if any( + not isinstance(capability, ProcessResourceLimitCapability) for capability in declaration.process_resource_limits + ): + raise TypeError("process_resource_limits values must be ProcessResourceLimitCapability") + resource_keys = [capability.resource for capability in declaration.process_resource_limits] + if len(resource_keys) != len(set(resource_keys)): + raise ValueError("process_resource_limits must not contain duplicate resource terms") + + +def _validate_artifact_mechanism_identities(declaration: RealizationSupportDeclaration) -> None: + identities = [ + ( + capability.mechanism.mechanism, + capability.mechanism.profile, + capability.mechanism.version, + capability.mechanism.digest, + ) + for capability in declaration.artifact_mechanisms + ] + if len(identities) != len(set(identities)): + raise ValueError("artifact_mechanisms must not contain duplicate mechanism profiles") + + @dataclass(frozen=True) class RealizationSupportDeclaration: """Declared realization-support and disclosure surface for one concern domain.""" @@ -129,20 +160,7 @@ def __post_init__(self) -> None: field_name="supported_exact_requirement_kinds", ) _require_non_empty_strings(self.disclosure_kinds, field_name="disclosure_kinds") - if any(not kind.strip() for kind in self.observation_capabilities): - raise ValueError("observation_capabilities must not contain empty concern kinds") - if any( - not isinstance(capability, RealizationObservationCapability) - for capability in self.observation_capabilities.values() - ): - raise TypeError("observation_capabilities values must be RealizationObservationCapability") - if any( - not isinstance(capability, ProcessResourceLimitCapability) for capability in self.process_resource_limits - ): - raise TypeError("process_resource_limits values must be ProcessResourceLimitCapability") - resource_keys = [capability.resource for capability in self.process_resource_limits] - if len(resource_keys) != len(set(resource_keys)): - raise ValueError("process_resource_limits must not contain duplicate resource terms") + _validate_observation_and_limit_capabilities(self) if not self.disclosure_kinds: raise ValueError("RealizationSupportDeclaration.disclosure_kinds must not be empty") if not (self.supported_constraint_kinds or self.supported_exact_requirement_kinds): @@ -152,14 +170,4 @@ def __post_init__(self) -> None: ) if self.support_mode == RealizationSupportMode.EXACT_ONLY and self.supported_constraint_kinds: raise ValueError("exact-only realization support must not declare supported_constraint_kinds") - identities = [ - ( - capability.mechanism.mechanism, - capability.mechanism.profile, - capability.mechanism.version, - capability.mechanism.digest, - ) - for capability in self.artifact_mechanisms - ] - if len(identities) != len(set(identities)): - raise ValueError("artifact_mechanisms must not contain duplicate mechanism profiles") + _validate_artifact_mechanism_identities(self) diff --git a/implementations/python/packages/raes_contracts/contracts/bundle.py b/implementations/python/packages/raes_contracts/contracts/bundle.py index a024d9ef..2a2b2349 100644 --- a/implementations/python/packages/raes_contracts/contracts/bundle.py +++ b/implementations/python/packages/raes_contracts/contracts/bundle.py @@ -13,15 +13,14 @@ from . import semantic_profiles, semantic_projection from .admitted_trial_plan import AdmittedTrialPlanModel -from .associated_artifacts import AssociatedArtifactManifestModel from .batch_execution import BatchExecutionReceiptModel +from .bundle_runtime import _runtime_schema_bundle from .catalogs import ( ConceptFamilyCatalogModel, ReferenceModelCatalogModel, UcoAlignmentCatalogModel, ) from .execution_state import ( - EvaluationHistoryEventModel, EvaluationResultStateModel, InstantiationRequestModel, PropositionTruthResultModel, @@ -32,7 +31,6 @@ from .experiment_apparatus import ExperimentApparatusContextModel, ExperimentTaskModel from .experiment_bindings import ( ExperimentBindingDescriptorSetModel, - ParticipantConfigurationResultModel, ) from .experiment_capture import ExperimentCaptureSpecModel from .experiment_evidence import ExperimentDerivedMeasureModel, ExperimentEvidenceRecordModel @@ -40,67 +38,28 @@ from .experiment_spec import ExperimentSpecModel, ExperimentStudyModel from .external_concept_bindings import ExternalConceptBindingDocumentModel from .manifests import ProcessorManifestV2Model -from .participant_context import ParticipantContextViewModel -from .participant_control import ParticipantControlOccurrenceModel -from .participant_crossing import ParticipantCrossingOccurrenceModel -from .participant_decision_surface import ParticipantDecisionSurfaceModel -from .participant_decision_surface_v2 import ParticipantDecisionSurfaceV2Model -from .participant_envelopes import ( - ParticipantJointActionRecordModel, - ParticipantLifecycleEventModel, - ParticipantSharedStateRecordModel, - ParticipantTimeManagementContextModel, -) -from .participant_execution import ( - ParticipantExecutionBindingModel, - ParticipantExecutionControlRequestModel, - ParticipantExecutionServiceStateModel, -) from .participant_flow_control import ( ParticipantBoundaryFlowPolicyProfileModel, - ParticipantFlowControlRelationModel, ) from .participant_information_state import ( ParticipantInformationReconstructionProfileModel, - ParticipantInformationStateRecordModel, ) from .participant_manifests import ( BackendManifestV2Model, ParticipantImplementationManifestModel, ParticipantImplementationProvenanceModel, ) -from .participant_observation import ParticipantObservationEnvelopeModel -from .participant_resource_budgets import ( - ParticipantResourceBudgetEventModel, - ParticipantResourceBudgetPolicyModel, - ParticipantResourceBudgetStateModel, - ParticipantResourcePoolCapacityModel, -) -from .participant_runtime import ( - ParticipantBehaviorHistoryEventModel, - ParticipantEpisodeHistoryEventModel, - ParticipantEpisodeStateModel, -) -from .participant_views import ( - ParticipantHistoryViewModel, - ParticipantOutcomeReportModel, - ParticipantStatusViewModel, -) from .random_stream import RandomStreamProfileModel, RandomStreamVectorModel from .realization_plans import ( EvaluationPlanModel, - OperationReceiptModel, - OperationStatusModel, OrchestrationPlanModel, ProvisioningPlanModel, RuntimeSnapshotEnvelopeModel, ) from .reusable_assets import ( - ReusableAssetTrustPolicyModel, _backend_profile_schema_for_bundle, _event_stream_schema, ) -from .runtime_facts import RuntimeFactBindingPlaneModel from .schema_constraints import ( _attach_compiled_address_map_constraints, _attach_instantiation_invariants, @@ -231,52 +190,6 @@ def _core_schema_bundle() -> dict[str, dict[str, Any]]: } -def _runtime_schema_bundle() -> dict[str, dict[str, Any]]: - return { - "evaluation-history-event-stream-v1": _event_stream_schema( - "EvaluationHistoryEventStream", - EvaluationHistoryEventModel.model_json_schema(), - ), - "participant-episode-state-envelope-v1": ParticipantEpisodeStateModel.model_json_schema(), - "participant-episode-history-event-stream-v1": _event_stream_schema( - "ParticipantEpisodeHistoryEventStream", - ParticipantEpisodeHistoryEventModel.model_json_schema(), - ), - "participant-behavior-history-event-stream-v1": _event_stream_schema( - "ParticipantBehaviorHistoryEventStream", - ParticipantBehaviorHistoryEventModel.model_json_schema(), - ), - "participant-execution-binding-v1": ParticipantExecutionBindingModel.model_json_schema(), - "participant-execution-control-v1": ParticipantExecutionControlRequestModel.model_json_schema(), - "participant-execution-service-state-v1": ParticipantExecutionServiceStateModel.model_json_schema(), - "participant-resource-budget-policy-v1": ParticipantResourceBudgetPolicyModel.model_json_schema(), - "participant-resource-pool-capacity-v1": ParticipantResourcePoolCapacityModel.model_json_schema(), - "participant-resource-budget-state-v1": ParticipantResourceBudgetStateModel.model_json_schema(), - "participant-resource-budget-event-v1": ParticipantResourceBudgetEventModel.model_json_schema(), - "participant-lifecycle-event-v1": ParticipantLifecycleEventModel.model_json_schema(), - "participant-observation-envelope-v1": ParticipantObservationEnvelopeModel.model_json_schema(), - "participant-information-state-record-v1": ParticipantInformationStateRecordModel.model_json_schema(), - "participant-shared-state-record-v1": ParticipantSharedStateRecordModel.model_json_schema(), - "participant-joint-action-record-v1": ParticipantJointActionRecordModel.model_json_schema(), - "participant-time-management-context-v1": ParticipantTimeManagementContextModel.model_json_schema(), - "participant-control-occurrence-v1": ParticipantControlOccurrenceModel.model_json_schema(), - "participant-crossing-occurrence-v1": ParticipantCrossingOccurrenceModel.model_json_schema(), - "participant-flow-control-relation-v1": ParticipantFlowControlRelationModel.model_json_schema(), - "participant-outcome-report-v1": ParticipantOutcomeReportModel.model_json_schema(), - "participant-status-view-v1": ParticipantStatusViewModel.model_json_schema(), - "participant-history-view-v1": ParticipantHistoryViewModel.model_json_schema(), - "participant-context-view-v1": ParticipantContextViewModel.model_json_schema(), - "runtime-fact-binding-plane-v1": RuntimeFactBindingPlaneModel.model_json_schema(), - "participant-decision-surface-v1": ParticipantDecisionSurfaceModel.model_json_schema(), - "participant-decision-surface-v2": ParticipantDecisionSurfaceV2Model.model_json_schema(), - "participant-configuration-result-v1": ParticipantConfigurationResultModel.model_json_schema(), - "operation-receipt-v1": OperationReceiptModel.model_json_schema(), - "operation-status-v1": OperationStatusModel.model_json_schema(), - "associated-artifact-manifest-v1": AssociatedArtifactManifestModel.model_json_schema(), - "reusable-asset-trust-policy-v1": ReusableAssetTrustPolicyModel.model_json_schema(), - } - - def _raw_schema_bundle() -> dict[str, dict[str, Any]]: return {**_core_schema_bundle(), **_runtime_schema_bundle()} diff --git a/implementations/python/packages/raes_contracts/contracts/bundle_runtime.py b/implementations/python/packages/raes_contracts/contracts/bundle_runtime.py new file mode 100644 index 00000000..f1554069 --- /dev/null +++ b/implementations/python/packages/raes_contracts/contracts/bundle_runtime.py @@ -0,0 +1,99 @@ +"""Runtime-facing slice of the published JSON Schema bundle (split from bundle.py).""" + +from __future__ import annotations + +from typing import Any + +from .associated_artifacts import AssociatedArtifactManifestModel +from .execution_state import EvaluationHistoryEventModel +from .experiment_bindings import ParticipantConfigurationResultModel +from .participant_context import ParticipantContextViewModel +from .participant_control import ParticipantControlOccurrenceModel +from .participant_crossing import ParticipantCrossingOccurrenceModel +from .participant_decision_surface import ParticipantDecisionSurfaceModel +from .participant_decision_surface_v2 import ParticipantDecisionSurfaceV2Model +from .participant_envelopes import ( + ParticipantJointActionRecordModel, + ParticipantLifecycleEventModel, + ParticipantSharedStateRecordModel, + ParticipantTimeManagementContextModel, +) +from .participant_execution import ( + ParticipantExecutionBindingModel, + ParticipantExecutionControlRequestModel, + ParticipantExecutionServiceStateModel, +) +from .participant_flow_control import ParticipantFlowControlRelationModel +from .participant_information_state import ParticipantInformationStateRecordModel +from .participant_observation import ParticipantObservationEnvelopeModel +from .participant_resource_budgets import ( + ParticipantResourceBudgetEventModel, + ParticipantResourceBudgetPolicyModel, + ParticipantResourceBudgetStateModel, + ParticipantResourcePoolCapacityModel, +) +from .participant_runtime import ( + ParticipantBehaviorHistoryEventModel, + ParticipantEpisodeHistoryEventModel, + ParticipantEpisodeStateModel, +) +from .participant_views import ( + ParticipantHistoryViewModel, + ParticipantOutcomeReportModel, + ParticipantStatusViewModel, +) +from .realization_plans import ( + OperationReceiptModel, + OperationStatusModel, +) +from .reusable_assets import ( + ReusableAssetTrustPolicyModel, + _event_stream_schema, +) +from .runtime_facts import RuntimeFactBindingPlaneModel + + +def _runtime_schema_bundle() -> dict[str, dict[str, Any]]: + return { + "evaluation-history-event-stream-v1": _event_stream_schema( + "EvaluationHistoryEventStream", + EvaluationHistoryEventModel.model_json_schema(), + ), + "participant-episode-state-envelope-v1": ParticipantEpisodeStateModel.model_json_schema(), + "participant-episode-history-event-stream-v1": _event_stream_schema( + "ParticipantEpisodeHistoryEventStream", + ParticipantEpisodeHistoryEventModel.model_json_schema(), + ), + "participant-behavior-history-event-stream-v1": _event_stream_schema( + "ParticipantBehaviorHistoryEventStream", + ParticipantBehaviorHistoryEventModel.model_json_schema(), + ), + "participant-execution-binding-v1": ParticipantExecutionBindingModel.model_json_schema(), + "participant-execution-control-v1": ParticipantExecutionControlRequestModel.model_json_schema(), + "participant-execution-service-state-v1": ParticipantExecutionServiceStateModel.model_json_schema(), + "participant-resource-budget-policy-v1": ParticipantResourceBudgetPolicyModel.model_json_schema(), + "participant-resource-pool-capacity-v1": ParticipantResourcePoolCapacityModel.model_json_schema(), + "participant-resource-budget-state-v1": ParticipantResourceBudgetStateModel.model_json_schema(), + "participant-resource-budget-event-v1": ParticipantResourceBudgetEventModel.model_json_schema(), + "participant-lifecycle-event-v1": ParticipantLifecycleEventModel.model_json_schema(), + "participant-observation-envelope-v1": ParticipantObservationEnvelopeModel.model_json_schema(), + "participant-information-state-record-v1": ParticipantInformationStateRecordModel.model_json_schema(), + "participant-shared-state-record-v1": ParticipantSharedStateRecordModel.model_json_schema(), + "participant-joint-action-record-v1": ParticipantJointActionRecordModel.model_json_schema(), + "participant-time-management-context-v1": ParticipantTimeManagementContextModel.model_json_schema(), + "participant-control-occurrence-v1": ParticipantControlOccurrenceModel.model_json_schema(), + "participant-crossing-occurrence-v1": ParticipantCrossingOccurrenceModel.model_json_schema(), + "participant-flow-control-relation-v1": ParticipantFlowControlRelationModel.model_json_schema(), + "participant-outcome-report-v1": ParticipantOutcomeReportModel.model_json_schema(), + "participant-status-view-v1": ParticipantStatusViewModel.model_json_schema(), + "participant-history-view-v1": ParticipantHistoryViewModel.model_json_schema(), + "participant-context-view-v1": ParticipantContextViewModel.model_json_schema(), + "runtime-fact-binding-plane-v1": RuntimeFactBindingPlaneModel.model_json_schema(), + "participant-decision-surface-v1": ParticipantDecisionSurfaceModel.model_json_schema(), + "participant-decision-surface-v2": ParticipantDecisionSurfaceV2Model.model_json_schema(), + "participant-configuration-result-v1": ParticipantConfigurationResultModel.model_json_schema(), + "operation-receipt-v1": OperationReceiptModel.model_json_schema(), + "operation-status-v1": OperationStatusModel.model_json_schema(), + "associated-artifact-manifest-v1": AssociatedArtifactManifestModel.model_json_schema(), + "reusable-asset-trust-policy-v1": ReusableAssetTrustPolicyModel.model_json_schema(), + } diff --git a/implementations/python/packages/raes_contracts/contracts/capabilities.py b/implementations/python/packages/raes_contracts/contracts/capabilities.py index 0c4c7be3..27242a85 100644 --- a/implementations/python/packages/raes_contracts/contracts/capabilities.py +++ b/implementations/python/packages/raes_contracts/contracts/capabilities.py @@ -59,6 +59,35 @@ def __get_pydantic_json_schema__( return json_schema +def _validate_operating_system_rows(model: ProvisionerCapabilitiesModel) -> None: + os_keys = [(entry.family, entry.distribution) for entry in model.operating_systems] + if len(os_keys) != len(set(os_keys)): + raise ValueError("operating_systems must not contain duplicate family/distribution rows") + undeclared_families = { + entry.family for entry in model.operating_systems if entry.family not in model.supported_os_families + } + if undeclared_families: + raise ValueError( + "operating_systems families must be present in supported_os_families: " + + ", ".join(sorted(undeclared_families)) + ) + + +def _validate_feature_coupling(model: ProvisionerCapabilitiesModel) -> None: + if model.supports_accounts and not model.supported_account_features: + raise ValueError("provisioners that support accounts must declare supported_account_features") + if not model.supports_accounts and model.supported_account_features: + raise ValueError("supported_account_features require supports_accounts=true") + if len(model.supported_generated_artifact_kinds) != len(set(model.supported_generated_artifact_kinds)): + raise ValueError("supported_generated_artifact_kinds must not contain duplicates") + if model.supports_generated_artifacts and not model.supported_generated_artifact_kinds: + raise ValueError( + "provisioners that support generated artifacts must declare supported_generated_artifact_kinds" + ) + if not model.supports_generated_artifacts and model.supported_generated_artifact_kinds: + raise ValueError("supported_generated_artifact_kinds require supports_generated_artifacts=true") + + class ProvisionerCapabilitiesModel(ContractModel): name: NonEmptyString supported_node_types: list[NonEmptyString] = Field(min_length=1) @@ -90,17 +119,7 @@ def _validate_account_support(self) -> ProvisionerCapabilitiesModel: "capabilities.provisioner.supported_os_families", self.supported_os_families, ) - os_keys = [(entry.family, entry.distribution) for entry in self.operating_systems] - if len(os_keys) != len(set(os_keys)): - raise ValueError("operating_systems must not contain duplicate family/distribution rows") - undeclared_families = { - entry.family for entry in self.operating_systems if entry.family not in self.supported_os_families - } - if undeclared_families: - raise ValueError( - "operating_systems families must be present in supported_os_families: " - + ", ".join(sorted(undeclared_families)) - ) + _validate_operating_system_rows(self) _validate_controlled_vocabulary_terms( "capabilities.provisioner.supported_node_architectures", self.supported_node_architectures, @@ -121,18 +140,7 @@ def _validate_account_support(self) -> ProvisionerCapabilitiesModel: "capabilities.provisioner.supported_service_materialization_profiles", self.supported_service_materialization_profiles, ) - if self.supports_accounts and not self.supported_account_features: - raise ValueError("provisioners that support accounts must declare supported_account_features") - if not self.supports_accounts and self.supported_account_features: - raise ValueError("supported_account_features require supports_accounts=true") - if len(self.supported_generated_artifact_kinds) != len(set(self.supported_generated_artifact_kinds)): - raise ValueError("supported_generated_artifact_kinds must not contain duplicates") - if self.supports_generated_artifacts and not self.supported_generated_artifact_kinds: - raise ValueError( - "provisioners that support generated artifacts must declare supported_generated_artifact_kinds" - ) - if not self.supports_generated_artifacts and self.supported_generated_artifact_kinds: - raise ValueError("supported_generated_artifact_kinds require supports_generated_artifacts=true") + _validate_feature_coupling(self) return self @classmethod diff --git a/implementations/python/packages/raes_contracts/realization_envelope_carrier.py b/implementations/python/packages/raes_contracts/realization_envelope_carrier.py index fd7d1bec..a65be85e 100644 --- a/implementations/python/packages/raes_contracts/realization_envelope_carrier.py +++ b/implementations/python/packages/raes_contracts/realization_envelope_carrier.py @@ -152,6 +152,23 @@ def __get_pydantic_json_schema__( return json_schema +def _validate_transformation_coupling(model: RealizationConcernDisclosureModel) -> None: + if len(model.transformations) != len(set(model.transformations)): + raise ValueError("transformations must not contain duplicates") + if model.disposition is ConcernDisposition.TRANSFORMED and not model.transformations: + raise ValueError("transformed disposition requires transformations") + if model.disposition is not ConcernDisposition.TRANSFORMED and model.transformations: + raise ValueError("transformations require transformed disposition") + + +def _validate_observation_coupling(model: RealizationConcernDisclosureModel) -> None: + if model.disposition is ConcernDisposition.UNSUPPORTED: + if model.observation_strength is not ObservationStrength.NONE or model.mechanism is not None: + raise ValueError("unsupported disposition cannot claim observation or mechanism") + elif model.observation_strength is ObservationStrength.NONE or model.mechanism is None: + raise ValueError("supported dispositions require observation and mechanism") + + class RealizationConcernDisclosureModel(ContractModel): """Typed support, transformation, and observation claim for one concern.""" @@ -163,17 +180,8 @@ class RealizationConcernDisclosureModel(ContractModel): @model_validator(mode="after") def _validate_disposition(self) -> RealizationConcernDisclosureModel: - if len(self.transformations) != len(set(self.transformations)): - raise ValueError("transformations must not contain duplicates") - if self.disposition is ConcernDisposition.TRANSFORMED and not self.transformations: - raise ValueError("transformed disposition requires transformations") - if self.disposition is not ConcernDisposition.TRANSFORMED and self.transformations: - raise ValueError("transformations require transformed disposition") - if self.disposition is ConcernDisposition.UNSUPPORTED: - if self.observation_strength is not ObservationStrength.NONE or self.mechanism is not None: - raise ValueError("unsupported disposition cannot claim observation or mechanism") - elif self.observation_strength is ObservationStrength.NONE or self.mechanism is None: - raise ValueError("supported dispositions require observation and mechanism") + _validate_transformation_coupling(self) + _validate_observation_coupling(self) if self.concern is RealizationConcern.COMPUTE_SUBSTRATE and self.mechanism is not None: try: validate_controlled_vocabulary_value(_COMPUTE_SUBSTRATE_VOCABULARY, self.mechanism) diff --git a/implementations/python/packages/raes_processor/planner/operations.py b/implementations/python/packages/raes_processor/planner/operations.py index 435c66a3..972835fc 100644 --- a/implementations/python/packages/raes_processor/planner/operations.py +++ b/implementations/python/packages/raes_processor/planner/operations.py @@ -41,17 +41,10 @@ def _build_operations( return actions, deleted_entries -def _build_provisioning_plan( - resources: dict[str, PlannedResource], +def _ordered_apply_ops( + provisioning_resources: dict[str, PlannedResource], actions: dict[str, ChangeAction], - deleted_entries: dict[str, SnapshotEntry], - manifest: BackendManifest, - realization_requirements: tuple[CompiledRealizationRequirement, ...], - realization_authority: tuple[ResolvedRealizationAuthority, ...], -) -> ProvisioningPlan: - provisioning_resources = { - address: resource for address, resource in resources.items() if resource.domain == RuntimeDomain.PROVISIONING - } +) -> list[ProvisionOp]: ops: list[ProvisionOp] = [] for address in _topological_order(provisioning_resources): resource = provisioning_resources[address] @@ -65,6 +58,11 @@ def _build_provisioning_plan( refresh_dependencies=resource.refresh_dependencies, ) ) + return ops + + +def _ordered_delete_ops(deleted_entries: dict[str, SnapshotEntry]) -> list[ProvisionOp]: + ops: list[ProvisionOp] = [] for address in _delete_order( {address: entry for address, entry in deleted_entries.items() if entry.domain == RuntimeDomain.PROVISIONING} ): @@ -79,6 +77,22 @@ def _build_provisioning_plan( refresh_dependencies=entry.refresh_dependencies, ) ) + return ops + + +def _build_provisioning_plan( + resources: dict[str, PlannedResource], + actions: dict[str, ChangeAction], + deleted_entries: dict[str, SnapshotEntry], + manifest: BackendManifest, + realization_requirements: tuple[CompiledRealizationRequirement, ...], + realization_authority: tuple[ResolvedRealizationAuthority, ...], +) -> ProvisioningPlan: + provisioning_resources = { + address: resource for address, resource in resources.items() if resource.domain == RuntimeDomain.PROVISIONING + } + ops = _ordered_apply_ops(provisioning_resources, actions) + ops.extend(_ordered_delete_ops(deleted_entries)) return ProvisioningPlan( resources=provisioning_resources, operations=ops, diff --git a/implementations/python/packages/raes_processor/semantics/realization_runtime_evaluation.py b/implementations/python/packages/raes_processor/semantics/realization_runtime_evaluation.py index caf65a09..87aaedf8 100644 --- a/implementations/python/packages/raes_processor/semantics/realization_runtime_evaluation.py +++ b/implementations/python/packages/raes_processor/semantics/realization_runtime_evaluation.py @@ -18,6 +18,7 @@ from raes_contracts.diagnostics import Diagnostic, Severity from raes_contracts.planning import ChangeAction, ProvisioningPlan from raes_contracts.runtime_state import ( + RealizationObservationDisclosure, RealizationProvenanceEntry, RuntimeSnapshot, ) @@ -190,6 +191,27 @@ def _projected_declared_realization_result( return result +def _observation_corroborates( + requirement: CompiledRealizationRequirement, + observation: RealizationObservationDisclosure, + manifest: BackendManifest | None, +) -> bool: + """Return whether one disclosed observation satisfies the requirement's evidence bar.""" + + required_scope = requirement.verification_scope + return ( + (required_scope is None or verification_scope_satisfies(observation.verification_scope, required_scope)) + and ( + requirement.required_observation_strength is None + or observation_strength_satisfies( + observation.observation_strength, + requirement.required_observation_strength, + ) + ) + and manifest_corroborates(requirement, observation, manifest) + ) + + def _corroboration_diagnostic( requirement: CompiledRealizationRequirement, returned_snapshot: RuntimeSnapshot, @@ -206,18 +228,7 @@ def _corroboration_diagnostic( ): return None observation = matching_observation(requirement, returned_snapshot) - if ( - observation is not None - and (required_scope is None or verification_scope_satisfies(observation.verification_scope, required_scope)) - and ( - requirement.required_observation_strength is None - or observation_strength_satisfies( - observation.observation_strength, - requirement.required_observation_strength, - ) - ) - and manifest_corroborates(requirement, observation, manifest) - ): + if observation is not None and _observation_corroborates(requirement, observation, manifest): return None return Diagnostic( code=BACKEND_CONTRACT_INVALID, diff --git a/implementations/python/packages/raes_runtime/backend_call_contracts.py b/implementations/python/packages/raes_runtime/backend_call_contracts.py new file mode 100644 index 00000000..db1f4bd8 --- /dev/null +++ b/implementations/python/packages/raes_runtime/backend_call_contracts.py @@ -0,0 +1,75 @@ +"""Shape validation for values a backend returns across the runtime boundary.""" + +from __future__ import annotations + +from collections.abc import Iterable + +from raes_contracts.diagnostics import Diagnostic +from raes_contracts.runtime_state import ApplyResult, RuntimeSnapshot + + +def _diagnostics_iterable_violation(result: object, address: str) -> str | None: + message = None + if not isinstance(result, Iterable) or isinstance(result, (str, bytes)): + message = f"Backend method '{address}' returned {type(result).__name__}; expected diagnostics iterable." + return message + + +def _diagnostics_values_violation(diagnostics: list[object], address: str) -> str | None: + message = None + if any(not isinstance(diagnostic, Diagnostic) for diagnostic in diagnostics): + message = f"Backend method '{address}' returned a diagnostics iterable containing non-Diagnostic values." + return message + + +def _apply_result_contract_violation(result: object, address: str) -> str | None: + message = _apply_result_shape_violation(result, address) + if message is None and isinstance(result, ApplyResult): + message = _apply_result_diagnostics_violation(result, address) + if message is None and isinstance(result, ApplyResult): + message = _apply_result_changed_addresses_violation(result, address) + if message is None and isinstance(result, ApplyResult): + message = _apply_result_details_violation(result, address) + return message + + +def _apply_result_shape_violation(result: object, address: str) -> str | None: + message = None + if not isinstance(result, ApplyResult): + message = f"Backend method '{address}' returned {type(result).__name__}; expected ApplyResult." + elif not isinstance(result.snapshot, RuntimeSnapshot): + message = ( + f"Backend method '{address}' returned ApplyResult.snapshot " + f"as {type(result.snapshot).__name__}; expected RuntimeSnapshot." + ) + return message + + +def _apply_result_diagnostics_violation(result: ApplyResult, address: str) -> str | None: + message = None + if not isinstance(result.diagnostics, Iterable) or isinstance(result.diagnostics, (str, bytes)): + message = ( + f"Backend method '{address}' returned ApplyResult.diagnostics " + f"as {type(result.diagnostics).__name__}; expected iterable." + ) + elif any(not isinstance(diagnostic, Diagnostic) for diagnostic in result.diagnostics): + message = f"Backend method '{address}' returned ApplyResult.diagnostics containing non-Diagnostic values." + return message + + +def _apply_result_changed_addresses_violation(result: ApplyResult, address: str) -> str | None: + message = None + if not isinstance(result.changed_addresses, list): + message = ( + f"Backend method '{address}' returned ApplyResult.changed_addresses " + f"as {type(result.changed_addresses).__name__}; expected list." + ) + elif any(not isinstance(changed_address, str) for changed_address in result.changed_addresses): + message = f"Backend method '{address}' returned ApplyResult.changed_addresses containing non-string values." + return message + + +def _apply_result_details_violation(result: ApplyResult, address: str) -> str | None: + if isinstance(result.details, dict): + return None + return f"Backend method '{address}' returned ApplyResult.details as {type(result.details).__name__}; expected dict." diff --git a/implementations/python/packages/raes_runtime/backend_calls.py b/implementations/python/packages/raes_runtime/backend_calls.py index 1644c6e9..5ce228ee 100644 --- a/implementations/python/packages/raes_runtime/backend_calls.py +++ b/implementations/python/packages/raes_runtime/backend_calls.py @@ -1,6 +1,6 @@ from __future__ import annotations -from collections.abc import Callable, Iterable +from collections.abc import Callable from copy import deepcopy from raes_contracts.addressing import require_compiled_address @@ -22,6 +22,11 @@ sanitize_account_credential_result, value_free_backend_diagnostics, ) +from .backend_call_contracts import ( + _apply_result_contract_violation, + _diagnostics_iterable_violation, + _diagnostics_values_violation, +) from .backend_realization_authority import ( _apply_authority_diagnostics, _bind_submitted_plan, @@ -327,73 +332,6 @@ def _failed_apply_result(snapshot: RuntimeSnapshot, diagnostic: Diagnostic) -> A return ApplyResult(success=False, snapshot=snapshot, diagnostics=[diagnostic]) -def _diagnostics_iterable_violation(result: object, address: str) -> str | None: - message = None - if not isinstance(result, Iterable) or isinstance(result, (str, bytes)): - message = f"Backend method '{address}' returned {type(result).__name__}; expected diagnostics iterable." - return message - - -def _diagnostics_values_violation(diagnostics: list[object], address: str) -> str | None: - message = None - if any(not isinstance(diagnostic, Diagnostic) for diagnostic in diagnostics): - message = f"Backend method '{address}' returned a diagnostics iterable containing non-Diagnostic values." - return message - - -def _apply_result_contract_violation(result: object, address: str) -> str | None: - message = _apply_result_shape_violation(result, address) - if message is None and isinstance(result, ApplyResult): - message = _apply_result_diagnostics_violation(result, address) - if message is None and isinstance(result, ApplyResult): - message = _apply_result_changed_addresses_violation(result, address) - if message is None and isinstance(result, ApplyResult): - message = _apply_result_details_violation(result, address) - return message - - -def _apply_result_shape_violation(result: object, address: str) -> str | None: - message = None - if not isinstance(result, ApplyResult): - message = f"Backend method '{address}' returned {type(result).__name__}; expected ApplyResult." - elif not isinstance(result.snapshot, RuntimeSnapshot): - message = ( - f"Backend method '{address}' returned ApplyResult.snapshot " - f"as {type(result.snapshot).__name__}; expected RuntimeSnapshot." - ) - return message - - -def _apply_result_diagnostics_violation(result: ApplyResult, address: str) -> str | None: - message = None - if not isinstance(result.diagnostics, Iterable) or isinstance(result.diagnostics, (str, bytes)): - message = ( - f"Backend method '{address}' returned ApplyResult.diagnostics " - f"as {type(result.diagnostics).__name__}; expected iterable." - ) - elif any(not isinstance(diagnostic, Diagnostic) for diagnostic in result.diagnostics): - message = f"Backend method '{address}' returned ApplyResult.diagnostics containing non-Diagnostic values." - return message - - -def _apply_result_changed_addresses_violation(result: ApplyResult, address: str) -> str | None: - message = None - if not isinstance(result.changed_addresses, list): - message = ( - f"Backend method '{address}' returned ApplyResult.changed_addresses " - f"as {type(result.changed_addresses).__name__}; expected list." - ) - elif any(not isinstance(changed_address, str) for changed_address in result.changed_addresses): - message = f"Backend method '{address}' returned ApplyResult.changed_addresses containing non-string values." - return message - - -def _apply_result_details_violation(result: ApplyResult, address: str) -> str | None: - if isinstance(result.details, dict): - return None - return f"Backend method '{address}' returned ApplyResult.details as {type(result.details).__name__}; expected dict." - - def _snapshot_contract_diagnostics( snapshot: RuntimeSnapshot, *, diff --git a/implementations/python/packages/raes_runtime/control_plane_api_participant_retrieval.py b/implementations/python/packages/raes_runtime/control_plane_api_participant_retrieval.py index 701a34c1..b7fd4d8e 100644 --- a/implementations/python/packages/raes_runtime/control_plane_api_participant_retrieval.py +++ b/implementations/python/packages/raes_runtime/control_plane_api_participant_retrieval.py @@ -32,6 +32,36 @@ def _read_identity_dependency(request: Request) -> ControlPlaneIdentity: _ReadIdentity = Annotated[ControlPlaneIdentity, Depends(_read_identity_dependency)] +async def _resolved_governed_view( + control_plane: RuntimeControlPlane, + request: Request, + identity: ControlPlaneIdentity, + participant_address: str, + *, + action: str, + not_found_detail: str, + resolve: Callable[[ParticipantAudienceSubjectBinding | None, str], _ViewT | None], +) -> _ViewT: + """Resolve one governed participant view with the shared audit and error flow.""" + + audience_binding = _require_governed_audience_candidate(control_plane, identity, participant_address) + calls = _control_plane_calls(request) + view = await calls.mutate( + _governed_view, + lambda: resolve(audience_binding, request.headers.get("idempotency-key", "")), + ) + if view is None: + raise HTTPException(status_code=404, detail=not_found_detail) + await calls.run( + control_plane.record_audit, + action=action, + identity=identity.identity, + allowed=True, + target=str(request.url.path), + ) + return view + + def register_participant_retrieval_routes( app: FastAPI, control_plane: RuntimeControlPlane, @@ -45,27 +75,20 @@ async def get_participant_status_view( request: Request, identity: _ReadIdentity, ) -> ParticipantStatusViewModel: - audience_binding = _require_governed_audience_candidate(control_plane, identity, participant_address) - calls = _control_plane_calls(request) - view = await calls.mutate( - _governed_view, - lambda: control_plane.get_participant_status_view( + return await _resolved_governed_view( + control_plane, + request, + identity, + participant_address, + action="get_participant_status_view", + not_found_detail=f"Unknown participant: {participant_address}", + resolve=lambda audience_binding, idempotency_key: control_plane.get_participant_status_view( participant_address, identity=identity, audience_binding=audience_binding, - idempotency_key=request.headers.get("idempotency-key", ""), + idempotency_key=idempotency_key, ), ) - if view is None: - raise HTTPException(status_code=404, detail=f"Unknown participant: {participant_address}") - await calls.run( - control_plane.record_audit, - action="get_participant_status_view", - identity=identity.identity, - allowed=True, - target=str(request.url.path), - ) - return view @app.get( "/participants/{participant_address}/episodes/{episode_id}/history", @@ -77,31 +100,21 @@ async def get_participant_history_view( request: Request, identity: _ReadIdentity, ) -> ParticipantHistoryViewModel: - audience_binding = _require_governed_audience_candidate(control_plane, identity, participant_address) - calls = _control_plane_calls(request) - view = await calls.mutate( - _governed_view, - lambda: control_plane.get_participant_history_view( + return await _resolved_governed_view( + control_plane, + request, + identity, + participant_address, + action="get_participant_history_view", + not_found_detail=f"Unknown participant episode: {participant_address}/{episode_id}", + resolve=lambda audience_binding, idempotency_key: control_plane.get_participant_history_view( participant_address, episode_id, identity=identity, audience_binding=audience_binding, - idempotency_key=request.headers.get("idempotency-key", ""), + idempotency_key=idempotency_key, ), ) - if view is None: - raise HTTPException( - status_code=404, - detail=f"Unknown participant episode: {participant_address}/{episode_id}", - ) - await calls.run( - control_plane.record_audit, - action="get_participant_history_view", - identity=identity.identity, - allowed=True, - target=str(request.url.path), - ) - return view @app.get( "/participants/{participant_address}/context", @@ -116,11 +129,14 @@ async def get_participant_context_view( derivation_basis_ref: str | None = None, payload_ref: str | None = None, ) -> ParticipantContextViewModel: - audience_binding = _require_governed_audience_candidate(control_plane, identity, participant_address) - calls = _control_plane_calls(request) - view = await calls.mutate( - _governed_view, - lambda: control_plane.get_participant_context_view( + return await _resolved_governed_view( + control_plane, + request, + identity, + participant_address, + action="get_participant_context_view", + not_found_detail=f"Unknown participant: {participant_address}", + resolve=lambda audience_binding, idempotency_key: control_plane.get_participant_context_view( participant_address, view_ref=view_ref, episode_id=episode_id, @@ -128,19 +144,9 @@ async def get_participant_context_view( payload_ref=payload_ref, identity=identity, audience_binding=audience_binding, - idempotency_key=request.headers.get("idempotency-key", ""), + idempotency_key=idempotency_key, ), ) - if view is None: - raise HTTPException(status_code=404, detail=f"Unknown participant: {participant_address}") - await calls.run( - control_plane.record_audit, - action="get_participant_context_view", - identity=identity.identity, - allowed=True, - target=str(request.url.path), - ) - return view def _require_governed_audience_candidate( diff --git a/implementations/python/packages/raes_runtime/control_plane_store.py b/implementations/python/packages/raes_runtime/control_plane_store.py index f5e7a1e5..6f7868ac 100644 --- a/implementations/python/packages/raes_runtime/control_plane_store.py +++ b/implementations/python/packages/raes_runtime/control_plane_store.py @@ -9,10 +9,6 @@ from enum import Enum from typing import TYPE_CHECKING, Any, Protocol -from raes_contracts.account_credentials import ( - account_placement_has_credential_bindings, - value_free_account_placement_payload, -) from raes_contracts.artifact_requirements import ArtifactSatisfactionDisclosureModel from raes_contracts.contracts import RealizationEnvelopeIdentityModel from raes_contracts.contracts.time_model import TimeRuntimeStateModel @@ -30,6 +26,11 @@ ) from .control_plane_store_observations import realization_observation_from_payload +from .control_plane_store_payloads import ( + _entry_payloads, + _realization_observations_payload, + _realization_provenance_payload, +) if TYPE_CHECKING: from .control_plane_store_local import LocalControlPlaneStore @@ -177,18 +178,7 @@ def _snapshot_payload(snapshot: RuntimeSnapshot) -> dict[str, Any]: require_participant_autonomous_runtime_snapshot(snapshot) payload = { "schema_version": RuntimeSnapshotEnvelope().schema_version, - "entries": { - address: { - "address": entry.address, - "domain": entry.domain.value, - "resource_type": entry.resource_type, - "payload": dict(entry.payload), - "ordering_dependencies": list(entry.ordering_dependencies), - "refresh_dependencies": list(entry.refresh_dependencies), - "status": entry.status, - } - for address, entry in snapshot.entries.items() - }, + "entries": _entry_payloads(snapshot), "orchestration_results": dict(snapshot.orchestration_results), "orchestration_history": {address: list(events) for address, events in snapshot.orchestration_history.items()}, "evaluation_results": dict(snapshot.evaluation_results), @@ -229,67 +219,13 @@ def _snapshot_payload(snapshot: RuntimeSnapshot) -> dict[str, Any]: "time_model_state": ( snapshot.time_model_state.model_dump(mode="json") if snapshot.time_model_state is not None else None ), - "realization_provenance": [ - { - "address": entry.address, - "field_path": entry.field_path, - "domain": entry.domain, - "requirement_kind": entry.requirement_kind, - "explicitness": entry.explicitness.value, - "provenance": entry.provenance.value, - "governing_scope": entry.governing_scope, - "artifact_satisfaction": ( - entry.artifact_satisfaction.model_dump(mode="json") - if entry.artifact_satisfaction is not None - else None - ), - } - for entry in snapshot.realization_provenance - ], - "realization_observations": [ - { - "address": entry.address, - "field_path": entry.field_path, - "domain": entry.domain, - "requirement_kind": entry.requirement_kind, - "verification_scope": entry.verification_scope.value, - "observation_strength": entry.observation_strength.value, - **( - { - "observed_value": entry.observed_value, - "operating_system": ( - { - "family": entry.operating_system.family, - "distribution": entry.operating_system.distribution, - "version": entry.operating_system.version, - } - if entry.operating_system is not None - else None - ), - "operation_id": entry.operation_id, - "envelope_digest": entry.envelope_digest, - "configuration_digest": entry.configuration_digest, - "observer_version": entry.observer_version, - "sequence": entry.sequence, - "binding_verified": entry.binding_verified, - } - if entry.requirement_kind in {"compute-substrate", "operating-system"} - else {} - ), - } - for entry in snapshot.realization_observations - ], + "realization_provenance": _realization_provenance_payload(snapshot), + "realization_observations": _realization_observations_payload(snapshot), "realization_envelope": ( snapshot.realization_envelope.model_dump(mode="json") if snapshot.realization_envelope is not None else None ), "metadata": dict(snapshot.metadata), } - for entry in payload["entries"].values(): - if entry["resource_type"] != "account-placement": - continue - entry_payload = entry["payload"] - if account_placement_has_credential_bindings(entry_payload): - entry["payload"] = value_free_account_placement_payload(entry_payload) return payload diff --git a/implementations/python/packages/raes_runtime/control_plane_store_payloads.py b/implementations/python/packages/raes_runtime/control_plane_store_payloads.py new file mode 100644 index 00000000..c4723d9f --- /dev/null +++ b/implementations/python/packages/raes_runtime/control_plane_store_payloads.py @@ -0,0 +1,87 @@ +"""Serialized-payload builders for the durable runtime-snapshot envelope.""" + +from __future__ import annotations + +from typing import Any + +from raes_contracts.account_credentials import ( + account_placement_has_credential_bindings, + value_free_account_placement_payload, +) +from raes_contracts.runtime_state import RuntimeSnapshot + + +def _entry_payloads(snapshot: RuntimeSnapshot) -> dict[str, dict[str, Any]]: + entries = { + address: { + "address": entry.address, + "domain": entry.domain.value, + "resource_type": entry.resource_type, + "payload": dict(entry.payload), + "ordering_dependencies": list(entry.ordering_dependencies), + "refresh_dependencies": list(entry.refresh_dependencies), + "status": entry.status, + } + for address, entry in snapshot.entries.items() + } + for entry in entries.values(): + if entry["resource_type"] != "account-placement": + continue + entry_payload = entry["payload"] + if account_placement_has_credential_bindings(entry_payload): + entry["payload"] = value_free_account_placement_payload(entry_payload) + return entries + + +def _realization_provenance_payload(snapshot: RuntimeSnapshot) -> list[dict[str, Any]]: + return [ + { + "address": entry.address, + "field_path": entry.field_path, + "domain": entry.domain, + "requirement_kind": entry.requirement_kind, + "explicitness": entry.explicitness.value, + "provenance": entry.provenance.value, + "governing_scope": entry.governing_scope, + "artifact_satisfaction": ( + entry.artifact_satisfaction.model_dump(mode="json") if entry.artifact_satisfaction is not None else None + ), + } + for entry in snapshot.realization_provenance + ] + + +def _realization_observations_payload(snapshot: RuntimeSnapshot) -> list[dict[str, Any]]: + return [ + { + "address": entry.address, + "field_path": entry.field_path, + "domain": entry.domain, + "requirement_kind": entry.requirement_kind, + "verification_scope": entry.verification_scope.value, + "observation_strength": entry.observation_strength.value, + **( + { + "observed_value": entry.observed_value, + "operating_system": ( + { + "family": entry.operating_system.family, + "distribution": entry.operating_system.distribution, + "version": entry.operating_system.version, + } + if entry.operating_system is not None + else None + ), + "operation_id": entry.operation_id, + "envelope_digest": entry.envelope_digest, + "configuration_digest": entry.configuration_digest, + "observer_version": entry.observer_version, + "sequence": entry.sequence, + "binding_verified": entry.binding_verified, + } + if entry.requirement_kind in {"compute-substrate", "operating-system"} + else {} + ), + } + for entry in snapshot.realization_observations + ] From a8e1331c1c84466ba77a97445d8fae929fdb5295 Mon Sep 17 00:00:00 2001 From: Yernat Yestekov <2068106+doublewhy@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:55:46 -0700 Subject: [PATCH 2/2] refactor: share the realization-observations payload builder The extracted _realization_observations_payload had a token-identical twin inside control_plane_api_models._snapshot_model, which the PR analysis surfaced as new-code duplication once the store copy moved. The API model now calls the shared builder; behavior is unchanged (both sites operate on the same RuntimeSnapshot disclosure objects). Co-Authored-By: Claude Fable 5 --- .../raes_runtime/control_plane_api_models.py | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/implementations/python/packages/raes_runtime/control_plane_api_models.py b/implementations/python/packages/raes_runtime/control_plane_api_models.py index b96253e9..bb1d964a 100644 --- a/implementations/python/packages/raes_runtime/control_plane_api_models.py +++ b/implementations/python/packages/raes_runtime/control_plane_api_models.py @@ -34,6 +34,8 @@ ) from raes_contracts.runtime_state import OperationStatus, RuntimeSnapshotEnvelope +from raes_runtime.control_plane_store_payloads import _realization_observations_payload + class _ParticipantInitializeBody(BaseModel): model_config = ConfigDict(extra="forbid") @@ -239,39 +241,7 @@ def _snapshot_model(envelope: RuntimeSnapshotEnvelope) -> RuntimeSnapshotEnvelop } for entry in snapshot.realization_provenance ], - "realization_observations": [ - { - "address": entry.address, - "field_path": entry.field_path, - "domain": entry.domain, - "requirement_kind": entry.requirement_kind, - "verification_scope": entry.verification_scope.value, - "observation_strength": entry.observation_strength.value, - **( - { - "observed_value": entry.observed_value, - "operating_system": ( - { - "family": entry.operating_system.family, - "distribution": entry.operating_system.distribution, - "version": entry.operating_system.version, - } - if entry.operating_system is not None - else None - ), - "operation_id": entry.operation_id, - "envelope_digest": entry.envelope_digest, - "configuration_digest": entry.configuration_digest, - "observer_version": entry.observer_version, - "sequence": entry.sequence, - "binding_verified": entry.binding_verified, - } - if entry.requirement_kind in {"compute-substrate", "operating-system"} - else {} - ), - } - for entry in snapshot.realization_observations - ], + "realization_observations": _realization_observations_payload(snapshot), "realization_envelope": ( snapshot.realization_envelope.model_dump(mode="json") if snapshot.realization_envelope is not None else None ),