operationRun = $operationRun; } /** * @return array */ public function middleware(): array { return [new TrackOperationRun]; } public function handle( TenantPermissionService $permissions, OnboardingEvidenceWriter $evidence, OperationRunService $runs, ): void { $tenant = Tenant::query()->find($this->tenantId); if (! $tenant instanceof Tenant) { throw new RuntimeException('Tenant not found.'); } $user = User::query()->find($this->userId); if (! $user instanceof User) { throw new RuntimeException('User not found.'); } $session = OnboardingSession::query() ->where('tenant_id', $tenant->getKey()) ->find($this->onboardingSessionId); if (! $session instanceof OnboardingSession) { throw new RuntimeException('OnboardingSession not found.'); } $connection = ProviderConnection::query() ->where('tenant_id', $tenant->getKey()) ->find($this->providerConnectionId); if (! $connection instanceof ProviderConnection) { throw new RuntimeException('ProviderConnection not found.'); } // For onboarding, we default to a safe, non-live permission comparison. // Live Graph calls can be enabled later as a deliberate UX and contract decision. $result = $permissions->compare($tenant, persist: true, liveCheck: false, useConfiguredStub: true); $overall = $result['overall_status'] ?? 'error'; $evidenceStatus = match ($overall) { 'granted' => 'ok', 'missing' => 'blocked', default => 'error', }; $message = match ($overall) { 'granted' => 'All required permissions appear granted.', 'missing' => 'Some required permissions are missing.', default => 'Unable to verify permissions.', }; $evidence->record( tenant: $tenant, taskType: OnboardingTaskType::VerifyPermissions, status: $evidenceStatus, reasonCode: $overall === 'missing' ? 'permissions.missing' : null, message: $message, payload: [ 'overall_status' => $overall, 'permissions' => $result['permissions'] ?? [], ], session: $session, providerConnection: $connection, operationRun: $this->operationRun, recordedBy: $user, ); if (! $this->operationRun instanceof OperationRun) { return; } if ($evidenceStatus === 'ok') { $runs->updateRun( $this->operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Succeeded->value, ); return; } $runs->updateRun( $this->operationRun, status: OperationRunStatus::Completed->value, outcome: OperationRunOutcome::Failed->value, failures: [[ 'code' => 'onboarding.permissions.verify.failed', 'reason_code' => $overall === 'missing' ? 'permissions.missing' : 'permissions.verify.error', 'message' => $message, ]], ); } }