$preview * @param array $preflight */ public function start( CrossTenantCompareSelection $selection, array $preview, array $preflight, User $actor, ): ProviderOperationStartResult { $workspace = $selection->targetTenant->workspace; if (! $workspace instanceof Workspace) { throw new \RuntimeException('Promotion execution requires a workspace context.'); } $decision = $this->operationalControls->evaluate('promotion.execute', $workspace); if ($decision->isPaused()) { $this->auditLogger->log( workspace: $workspace, action: AuditActionId::OperationalControlExecutionBlocked, context: [ 'metadata' => array_filter([ 'control_key' => $decision->controlKey, 'scope_type' => $decision->matchedScopeType, 'workspace_id' => (int) $workspace->getKey(), 'reason_text' => $decision->reasonText, 'expires_at' => $decision->expiresAt?->toIso8601String(), 'actor_id' => (int) $actor->getKey(), 'source_tenant_id' => (int) $selection->sourceTenant->getKey(), 'target_tenant_id' => (int) $selection->targetTenant->getKey(), 'requested_scope' => 'promotion.execute', ], static fn (mixed $value): bool => $value !== null && $value !== ''), ], actor: $actor, status: 'blocked', resourceType: 'operational_control', resourceId: $decision->sourceActivationId !== null ? (string) $decision->sourceActivationId : null, targetLabel: 'Promotion execution', summary: 'Promotion execution blocked by operational control', tenant: $selection->targetTenant, ); throw OperationalControlBlockedException::forDecision($decision, 'Promotion execution'); } $plan = $this->planner->build($preview, $preflight); $providerConnection = $this->defaultProviderConnection((int) $selection->targetTenant->getKey()); $now = CarbonImmutable::now(); $identity = array_replace($plan['identity'], [ 'provider_connection_id' => $providerConnection?->getKey(), ]); $context = [ 'operation_type' => 'promotion.execute', 'source_tenant_id' => (int) $selection->sourceTenant->getKey(), 'source_tenant_name' => (string) $selection->sourceTenant->name, 'target_tenant_id' => (int) $selection->targetTenant->getKey(), 'target_tenant_name' => (string) $selection->targetTenant->name, 'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null, 'required_capability' => Capabilities::TENANT_MANAGE, 'workspace_required_capability' => Capabilities::WORKSPACE_BASELINES_MANAGE, 'target_scope' => [ 'workspace_id' => (int) $workspace->getKey(), 'tenant_id' => (int) $selection->targetTenant->getKey(), 'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null, 'entra_tenant_id' => $providerConnection instanceof ProviderConnection ? (string) $providerConnection->entra_tenant_id : (string) ($selection->targetTenant->tenant_id ?? $selection->targetTenant->external_id ?? $selection->targetTenant->getKey()), ], 'promotion_execution' => [ 'queued_at' => $now->toIso8601String(), 'queued_by_user_id' => (int) $actor->getKey(), 'plan' => $plan, ], 'selection' => $plan['selection'], ]; $run = $this->operationRuns->ensureRunWithIdentity( tenant: $selection->targetTenant, type: 'promotion.execute', identityInputs: $identity, context: $context, initiator: $actor, ); if (! $run->wasRecentlyCreated) { return ProviderOperationStartResult::deduped($run); } $this->operationRuns->updateRun($run, OperationRunStatus::Queued->value, summaryCounts: [ 'total' => (int) $plan['summary']['ready'], 'processed' => 0, 'succeeded' => 0, 'failed' => 0, 'skipped' => 0, 'created' => 0, 'updated' => 0, ]); $this->operationRuns->dispatchOrFail( $run, fn (OperationRun $operationRun): mixed => CrossTenantPromotionExecutionJob::dispatch($operationRun), ); $this->auditLogger->logCrossTenantPromotionExecutionQueued( workspace: $workspace, sourceTenant: $selection->sourceTenant, targetTenant: $selection->targetTenant, operationRun: $run->fresh() ?? $run, plan: $plan, actor: $actor, ); return ProviderOperationStartResult::started($run->fresh() ?? $run, true); } private function defaultProviderConnection(int $tenantId): ?ProviderConnection { return ProviderConnection::query() ->where('tenant_id', $tenantId) ->where('provider', 'microsoft') ->where('is_default', true) ->orderBy('id') ->first(); } }