$extraContext */ public function providerConnectionCheck( Tenant $tenant, ProviderConnection $connection, User $initiator, array $extraContext = [], ): ProviderOperationStartResult { return $this->providerConnectionCheckUsingConnection( tenant: $tenant, connection: $connection, initiator: $initiator, extraContext: $extraContext, ); } /** * Start (or dedupe) a provider-connection verification run for the tenant default connection. * * @param array $extraContext */ public function providerConnectionCheckForTenant( Tenant $tenant, User $initiator, array $extraContext = [], ): ProviderOperationStartResult { if (! $initiator->canAccessTenant($tenant)) { throw new NotFoundHttpException; } Gate::forUser($initiator)->authorize(Capabilities::PROVIDER_RUN, $tenant); $resolution = $this->connections->resolveDefault($tenant, 'microsoft'); if ($resolution->resolved && $resolution->connection instanceof ProviderConnection) { return $this->providerConnectionCheckUsingConnection( tenant: $tenant, connection: $resolution->connection, initiator: $initiator, extraContext: $extraContext, ); } return $this->providers->start( tenant: $tenant, connection: null, operationType: 'provider.connection.check', dispatcher: fn (OperationRun $run): mixed => $this->dispatchConnectionHealthCheck($run, $tenant, $initiator), initiator: $initiator, extraContext: array_merge($extraContext, [ 'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value, 'required_capability' => Capabilities::PROVIDER_RUN, ]), ); } /** * Start (or dedupe) a provider-connection verification run for an explicit connection. * * @param array $extraContext */ public function providerConnectionCheckUsingConnection( Tenant $tenant, ProviderConnection $connection, User $initiator, array $extraContext = [], ): ProviderOperationStartResult { if (! $initiator->canAccessTenant($tenant)) { throw new NotFoundHttpException; } Gate::forUser($initiator)->authorize(Capabilities::PROVIDER_RUN, $tenant); $identity = $this->identityResolver->resolve($connection); $result = $this->providers->start( tenant: $tenant, connection: $connection, operationType: 'provider.connection.check', dispatcher: fn (OperationRun $run): mixed => $this->dispatchConnectionHealthCheck($run, $tenant, $initiator), initiator: $initiator, extraContext: array_merge($extraContext, [ 'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value, 'required_capability' => Capabilities::PROVIDER_RUN, 'identity' => [ 'connection_type' => $identity->connectionType->value, 'credential_source' => $identity->credentialSource, 'effective_client_id' => $identity->effectiveClientId, ], ]), ); if ($result->status === 'started') { $projectedState = $this->stateProjector->project( connectionType: $connection->connection_type, consentStatus: $connection->consent_status, verificationStatus: ProviderVerificationStatus::Pending, currentStatus: is_string($connection->status) ? $connection->status : null, ); $connection->update([ 'verification_status' => ProviderVerificationStatus::Pending, 'status' => $projectedState['status'], 'health_status' => $projectedState['health_status'], 'last_error_reason_code' => null, 'last_error_message' => null, ]); } return $result; } private function dispatchConnectionHealthCheck(OperationRun $run, Tenant $tenant, User $initiator): mixed { $context = is_array($run->context ?? null) ? $run->context : []; $providerConnectionId = $context['provider_connection_id'] ?? null; if (! is_numeric($providerConnectionId)) { throw new InvalidArgumentException('Provider connection id is missing from run context.'); } return ProviderConnectionHealthCheckJob::dispatch( tenantId: (int) $tenant->getKey(), userId: (int) $initiator->getKey(), providerConnectionId: (int) $providerConnectionId, operationRun: $run, ); } }