*/ private const ACCEPTED_OUTPUT_STATES = [ 'structured_collection', 'empty_collection', ]; public function __construct( private readonly ResourceTypeRegistry $resourceTypes, private readonly CoverageSourceContractResolver $contractResolver, private readonly ExchangePowerShellCommandContracts $commandContracts, private readonly ExchangePowerShellInvocationReadinessEvaluator $readiness, private readonly ProviderCapabilityEvaluator $providerCapabilities, ) {} /** * @return array{ * allowed: bool, * resource_type?: TenantConfigurationResourceType, * decision?: CoverageSourceContractDecision, * contract?: array, * failure_code?: string, * reason_code?: string, * outcome?: CaptureOutcome * } */ public function evaluate( ManagedEnvironment $tenant, ProviderConnection $providerConnection, OperationRun $operationRun, string $canonicalType, ExchangePowerShellInvocationResult $runnerResult, ): array { $canonicalType = trim($canonicalType); if (! in_array($canonicalType, $this->commandContracts->includedCanonicalTypes(), true)) { return $this->blocked('unsupported_exchange_capture_type', CaptureOutcome::BlockedUnsupported); } if (! $this->sameScope($tenant, $providerConnection, $operationRun)) { return $this->blocked('scope_mismatch', CaptureOutcome::BlockedUnsupported); } if ((string) $operationRun->type !== OperationRunType::TenantConfigurationCapture->value) { return $this->blocked('operation_type_mismatch', CaptureOutcome::BlockedUnsupported); } $resourceType = $this->resourceTypes->findActive($canonicalType); if (! $resourceType instanceof TenantConfigurationResourceType) { return $this->blocked('resource_type_missing', CaptureOutcome::BlockedMissingContract); } $decision = $this->contractResolver->resolve($resourceType); $contract = $this->commandContracts->contractForCanonicalType($canonicalType); if (! $this->verifiedPendingCaptureContract($decision, $contract)) { return $this->blocked('source_contract_not_verified', CaptureOutcome::BlockedMissingContract); } $readiness = $this->readiness->evaluate($tenant, $providerConnection); if (! $readiness->allowed) { return $this->blocked($readiness->failureCode ?? 'exchange_readiness_blocked', CaptureOutcome::BlockedPermission); } $capability = $this->providerCapabilities->evaluate($tenant, $providerConnection, 'exchange_powershell_invoke'); if ($capability->status !== ProviderCapabilityStatus::Supported) { return $this->blocked($capability->reasonCode ?? 'exchange_capability_not_supported', CaptureOutcome::BlockedPermission); } if (! $this->acceptedRunnerResult($runnerResult)) { return $this->blocked($runnerResult->failureCode ?? 'runner_result_not_accepted', CaptureOutcome::Failed); } return [ 'allowed' => true, 'resource_type' => $resourceType, 'decision' => $decision, 'contract' => $contract, ]; } /** * @return array{allowed: false, failure_code: string, reason_code: string, outcome: CaptureOutcome} */ private function blocked(string $reasonCode, CaptureOutcome $outcome): array { return [ 'allowed' => false, 'failure_code' => self::FAILURE_CODE, 'reason_code' => $reasonCode, 'outcome' => $outcome, ]; } private function sameScope( ManagedEnvironment $tenant, ProviderConnection $providerConnection, OperationRun $operationRun, ): bool { return (int) $providerConnection->workspace_id === (int) $tenant->workspace_id && (int) $providerConnection->managed_environment_id === (int) $tenant->getKey() && (int) $operationRun->workspace_id === (int) $tenant->workspace_id && (int) $operationRun->managed_environment_id === (int) $tenant->getKey() && (int) data_get($operationRun->context, 'target_scope.workspace_id') === (int) $tenant->workspace_id && (int) data_get($operationRun->context, 'target_scope.managed_environment_id') === (int) $tenant->getKey() && (int) data_get($operationRun->context, 'target_scope.provider_connection_id') === (int) $providerConnection->getKey(); } /** * @param array|null $contract */ private function verifiedPendingCaptureContract(?CoverageSourceContractDecision $decision, ?array $contract): bool { return $decision instanceof CoverageSourceContractDecision && is_array($contract) && ($decision->sourceContractState ?? data_get($decision->sourceMetadata, 'source_contract_state')) === CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE && data_get($decision->sourceMetadata, 'provider_adapter_state') === 'adapter_contract_available' && data_get($decision->sourceMetadata, 'provider_calls_allowed') === false && data_get($contract, 'source_contract_state') === CoverageSourceContractDecision::CONTRACT_VERIFIED_PENDING_CAPTURE && data_get($contract, 'read_only') === true; } private function acceptedRunnerResult(ExchangePowerShellInvocationResult $runnerResult): bool { if (! $runnerResult->successful || ! is_array($runnerResult->collection) || ! array_is_list($runnerResult->collection)) { return false; } $outputState = $runnerResult->context['output_state'] ?? null; return is_string($outputState) && in_array($outputState, self::ACCEPTED_OUTPUT_STATES, true); } }