} */ public function startCompare( Tenant $tenant, User $initiator, ?int $baselineSnapshotId = null, ): array { $assignment = BaselineTenantAssignment::query() ->where('workspace_id', $tenant->workspace_id) ->where('tenant_id', $tenant->getKey()) ->first(); if (! $assignment instanceof BaselineTenantAssignment) { return $this->failedStart(BaselineReasonCodes::COMPARE_NO_ASSIGNMENT); } $profile = $assignment->baselineProfile; if (! $profile instanceof BaselineProfile) { return $this->failedStart(BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE); } return $this->startCompareForProfile($profile, $tenant, $initiator, $baselineSnapshotId); } /** * @return array{ok: bool, run?: OperationRun, reason_code?: string, reason_translation?: array} */ public function startCompareForProfile( BaselineProfile $profile, Tenant $tenant, User $initiator, ?int $baselineSnapshotId = null, ): array { $assignment = BaselineTenantAssignment::query() ->where('workspace_id', (int) $profile->workspace_id) ->where('tenant_id', (int) $tenant->getKey()) ->where('baseline_profile_id', (int) $profile->getKey()) ->first(); if (! $assignment instanceof BaselineTenantAssignment) { return $this->failedStart(BaselineReasonCodes::COMPARE_NO_ASSIGNMENT); } $precondition = $this->validatePreconditions($profile); if ($precondition !== null) { return $this->failedStart($precondition); } $selectedSnapshot = null; if (is_int($baselineSnapshotId) && $baselineSnapshotId > 0) { $selectedSnapshot = BaselineSnapshot::query() ->where('workspace_id', (int) $profile->workspace_id) ->where('baseline_profile_id', (int) $profile->getKey()) ->whereKey((int) $baselineSnapshotId) ->first(); if (! $selectedSnapshot instanceof BaselineSnapshot) { return $this->failedStart(BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT); } } $snapshotResolution = $this->snapshotTruthResolver->resolveCompareSnapshot($profile, $selectedSnapshot); if (! ($snapshotResolution['ok'] ?? false)) { return $this->failedStart($snapshotResolution['reason_code'] ?? BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT); } /** @var BaselineSnapshot $snapshot */ $snapshot = $snapshotResolution['snapshot']; $snapshotId = (int) $snapshot->getKey(); $profileScope = BaselineScope::fromJsonb( is_array($profile->scope_jsonb) ? $profile->scope_jsonb : null, ); $overrideScope = $assignment->override_scope_jsonb !== null ? BaselineScope::fromJsonb(is_array($assignment->override_scope_jsonb) ? $assignment->override_scope_jsonb : null) : null; $effectiveScope = BaselineScope::effective($profileScope, $overrideScope); $captureMode = $profile->capture_mode instanceof BaselineCaptureMode ? $profile->capture_mode : BaselineCaptureMode::Opportunistic; $context = [ 'target_scope' => [ 'entra_tenant_id' => $tenant->graphTenantId(), 'entra_tenant_name' => (string) $tenant->name, ], 'baseline_profile_id' => (int) $profile->getKey(), 'baseline_snapshot_id' => $snapshotId, 'effective_scope' => $effectiveScope->toEffectiveScopeContext($this->capabilityGuard, 'compare'), 'capture_mode' => $captureMode->value, ]; $run = $this->runs->ensureRunWithIdentity( tenant: $tenant, type: OperationRunType::BaselineCompare->value, identityInputs: [ 'baseline_profile_id' => (int) $profile->getKey(), ], context: $context, initiator: $initiator, ); if ($run->wasRecentlyCreated) { CompareBaselineToTenantJob::dispatch($run); } return ['ok' => true, 'run' => $run]; } /** * @return array{ * baselineProfileId: int, * visibleAssignedTenantCount: int, * queuedCount: int, * alreadyQueuedCount: int, * blockedCount: int, * targets: list * } */ public function startCompareForVisibleAssignments(BaselineProfile $profile, User $initiator): array { $assignments = BaselineTenantAssignment::query() ->where('workspace_id', (int) $profile->workspace_id) ->where('baseline_profile_id', (int) $profile->getKey()) ->with('tenant') ->get(); $queuedCount = 0; $alreadyQueuedCount = 0; $blockedCount = 0; $targets = []; foreach ($assignments as $assignment) { $tenant = $assignment->tenant; if (! $tenant instanceof Tenant) { continue; } if (! $this->capabilityResolver->isMember($initiator, $tenant)) { continue; } if (! $this->capabilityResolver->can($initiator, $tenant, \App\Support\Auth\Capabilities::TENANT_VIEW)) { continue; } if (! $this->capabilityResolver->can($initiator, $tenant, \App\Support\Auth\Capabilities::TENANT_SYNC)) { $blockedCount++; $targets[] = [ 'tenantId' => (int) $tenant->getKey(), 'runId' => null, 'launchState' => 'blocked', 'reasonCode' => 'tenant_sync_required', ]; continue; } $result = $this->startCompareForProfile($profile, $tenant, $initiator); $run = $result['run'] ?? null; $reasonCode = is_string($result['reason_code'] ?? null) ? (string) $result['reason_code'] : null; if (! ($result['ok'] ?? false) || ! $run instanceof OperationRun) { $blockedCount++; $targets[] = [ 'tenantId' => (int) $tenant->getKey(), 'runId' => null, 'launchState' => 'blocked', 'reasonCode' => $reasonCode, ]; continue; } if ($run->wasRecentlyCreated) { $queuedCount++; $targets[] = [ 'tenantId' => (int) $tenant->getKey(), 'runId' => (int) $run->getKey(), 'launchState' => 'queued', 'reasonCode' => null, ]; continue; } $alreadyQueuedCount++; $targets[] = [ 'tenantId' => (int) $tenant->getKey(), 'runId' => (int) $run->getKey(), 'launchState' => 'already_queued', 'reasonCode' => null, ]; } return [ 'baselineProfileId' => (int) $profile->getKey(), 'visibleAssignedTenantCount' => count($targets), 'queuedCount' => $queuedCount, 'alreadyQueuedCount' => $alreadyQueuedCount, 'blockedCount' => $blockedCount, 'targets' => $targets, ]; } private function validatePreconditions(BaselineProfile $profile): ?string { if ($profile->status !== BaselineProfileStatus::Active) { return BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE; } if ($profile->capture_mode === BaselineCaptureMode::FullContent && ! $this->rolloutGate->enabled()) { return BaselineReasonCodes::COMPARE_ROLLOUT_DISABLED; } return null; } /** * @return array{ok: false, reason_code: string, reason_translation?: array} */ private function failedStart(string $reasonCode): array { $translation = app(ReasonPresenter::class)->forArtifactTruth($reasonCode, 'baseline_compare'); return array_filter([ 'ok' => false, 'reason_code' => $reasonCode, 'reason_translation' => $translation?->toArray(), ], static fn (mixed $value): bool => $value !== null); } }