*/ private const SYSTEM_AUTHORITY_ALLOWLIST = [ 'backup_schedule_run', 'backup_schedule_retention', 'backup_schedule_purge', ]; public function __construct( private readonly OperationRunCapabilityResolver $operationRunCapabilityResolver, private readonly CapabilityResolver $capabilityResolver, private readonly TenantOperabilityService $tenantOperabilityService, private readonly WriteGateInterface $writeGate, ) {} public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision { $run = OperationRun::query()->with(['tenant', 'user'])->findOrFail($run->getKey()); $context = $this->buildContext($run); $checks = QueuedExecutionLegitimacyDecision::defaultChecks(); if ($context->tenant instanceof Tenant) { $checks['workspace_scope'] = ((int) $context->tenant->workspace_id === $context->workspaceId) ? 'passed' : 'failed'; if ($checks['workspace_scope'] === 'failed') { return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::WorkspaceMismatch); } $checks['tenant_scope'] = 'passed'; } elseif ($run->tenant_id !== null) { $checks['tenant_scope'] = 'failed'; return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::TenantMissing); } else { $checks['workspace_scope'] = $context->workspaceId > 0 ? 'passed' : 'not_applicable'; } if ($context->authorityMode === ExecutionAuthorityMode::ActorBound) { if (! $context->initiator instanceof User) { return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::InitiatorMissing); } if ($context->tenant instanceof Tenant && ! $this->capabilityResolver->isMember($context->initiator, $context->tenant)) { $checks['tenant_scope'] = 'failed'; return QueuedExecutionLegitimacyDecision::deny($context, $checks, ExecutionDenialReasonCode::InitiatorNotEntitled); } if ($context->requiredCapability !== null && $context->tenant instanceof Tenant) { $checks['capability'] = $this->capabilityResolver->can( $context->initiator, $context->tenant, $context->requiredCapability, ) ? 'passed' : 'failed'; if ($checks['capability'] === 'failed') { return QueuedExecutionLegitimacyDecision::deny( $context, $checks, ExecutionDenialReasonCode::MissingCapability, ['required_capability' => $context->requiredCapability], ); } } } else { if (! $this->isSystemAuthorityAllowed($context->operationType)) { $checks['execution_prerequisites'] = 'failed'; return QueuedExecutionLegitimacyDecision::deny( $context, $checks, ExecutionDenialReasonCode::ExecutionPrerequisiteInvalid, ['system_allowlist' => self::SYSTEM_AUTHORITY_ALLOWLIST], ); } } if ($context->tenant instanceof Tenant) { $operabilityQuestion = $this->questionForContext($context); $operability = $this->tenantOperabilityService->outcomeFor( tenant: $context->tenant, question: $operabilityQuestion, workspaceId: $context->workspaceId, lane: TenantInteractionLane::AdministrativeManagement, ); $checks['tenant_operability'] = $operability->allowed ? 'passed' : 'failed'; if (! $operability->allowed) { return QueuedExecutionLegitimacyDecision::deny( $context, $checks, ExecutionDenialReasonCode::TenantNotOperable, [ 'tenant_operability_question' => $operabilityQuestion->value, 'tenant_operability_reason_code' => $operability->reasonCode?->value, ], ); } } $prerequisiteDecision = $this->evaluateExecutionPrerequisites($context, $checks); if ($prerequisiteDecision instanceof QueuedExecutionLegitimacyDecision) { return $prerequisiteDecision; } return QueuedExecutionLegitimacyDecision::allow($context, $prerequisiteDecision); } public function buildContext(OperationRun $run): QueuedExecutionContext { $context = is_array($run->context) ? $run->context : []; $authorityMode = ExecutionAuthorityMode::fromNullable($context['execution_authority_mode'] ?? null) ?? ($run->user_id === null ? ExecutionAuthorityMode::SystemAuthority : ExecutionAuthorityMode::ActorBound); $providerConnectionId = $this->resolveProviderConnectionId($context); $workspaceId = (int) ($run->workspace_id ?? $run->tenant?->workspace_id ?? 0); return new QueuedExecutionContext( run: $run, operationType: (string) $run->type, workspaceId: $workspaceId, tenant: $run->tenant, initiator: $run->user, authorityMode: $authorityMode, requiredCapability: is_string($context['required_capability'] ?? null) ? $context['required_capability'] : $this->operationRunCapabilityResolver->requiredExecutionCapabilityForType((string) $run->type), providerConnectionId: $providerConnectionId, targetScope: [ 'workspace_id' => $workspaceId, 'tenant_id' => $run->tenant_id !== null ? (int) $run->tenant_id : null, 'provider_connection_id' => $providerConnectionId, ], prerequisiteClasses: $this->prerequisiteClassesFor((string) $run->type, $providerConnectionId), ); } public function isSystemAuthorityAllowed(string $operationType): bool { return in_array($operationType, self::SYSTEM_AUTHORITY_ALLOWLIST, true); } /** * @param array{workspace_scope:string,tenant_scope:string,capability:string,tenant_operability:string,execution_prerequisites:string} $checks * @return array{workspace_scope:string,tenant_scope:string,capability:string,tenant_operability:string,execution_prerequisites:string}|QueuedExecutionLegitimacyDecision */ private function evaluateExecutionPrerequisites(QueuedExecutionContext $context, array $checks): array|QueuedExecutionLegitimacyDecision { if ($context->providerConnectionId !== null) { $validProviderConnection = ProviderConnection::query() ->whereKey($context->providerConnectionId) ->when( $context->tenant instanceof Tenant, fn ($query) => $query->where('tenant_id', (int) $context->tenant->getKey()), ) ->exists(); if (! $validProviderConnection) { $checks['execution_prerequisites'] = 'failed'; return QueuedExecutionLegitimacyDecision::deny( $context, $checks, ExecutionDenialReasonCode::ProviderConnectionInvalid, ['provider_connection_id' => $context->providerConnectionId], ); } } if ($this->requiresWriteGate($context) && $context->tenant instanceof Tenant) { try { $this->writeGate->evaluate($context->tenant, $context->operationType); } catch (ProviderAccessHardeningRequired $exception) { $checks['execution_prerequisites'] = 'failed'; return QueuedExecutionLegitimacyDecision::deny( $context, $checks, ExecutionDenialReasonCode::WriteGateBlocked, [ 'write_gate_reason_code' => $exception->reasonCode, 'write_gate_message' => $exception->reasonMessage, ], ); } } if ($context->prerequisiteClasses !== []) { $checks['execution_prerequisites'] = 'passed'; } return $checks; } /** * @param array $context */ private function resolveProviderConnectionId(array $context): ?int { $providerConnectionId = $context['provider_connection_id'] ?? ($context['target_scope']['provider_connection_id'] ?? null); return is_numeric($providerConnectionId) ? (int) $providerConnectionId : null; } /** * @return list */ private function prerequisiteClassesFor(string $operationType, ?int $providerConnectionId): array { $prerequisites = []; if ($providerConnectionId !== null) { $prerequisites[] = 'provider_connection'; } if (str_starts_with($operationType, 'restore.')) { $prerequisites[] = 'write_gate'; } return $prerequisites; } private function questionForContext(QueuedExecutionContext $context): TenantOperabilityQuestion { if ($context->providerConnectionId !== null || in_array($context->operationType, ['provider.connection.check', 'compliance.snapshot', 'provider.compliance.snapshot'], true)) { return TenantOperabilityQuestion::VerificationReadinessEligibility; } return match ($context->operationType) { 'restore.execute' => TenantOperabilityQuestion::RestoreEligibility, default => TenantOperabilityQuestion::AdministrativeDiscoverability, }; } private function requiresWriteGate(QueuedExecutionContext $context): bool { return in_array('write_gate', $context->prerequisiteClasses, true); } }