operationRun = $run; } /** * @return array */ public function middleware(): array { return [ new EnsureQueuedExecutionLegitimate, new TrackOperationRun, ]; } public function getOperationRun(): ?OperationRun { return $this->operationRun; } public function getOperationRunId(): int { return (int) $this->run->getKey(); } public function handle( GenericContentEvidenceCaptureService $captureService, OperationRunService $operationRuns, AuditRecorder $auditRecorder, ): void { $run = $this->run->fresh(['tenant.workspace', 'user']); if (! $run instanceof OperationRun) { throw new RuntimeException('OperationRun context is required for tenant configuration capture.'); } $this->operationRun = $run; if ($run->status === OperationRunStatus::Completed->value) { return; } $tenant = $run->tenant; $providerConnectionId = (int) data_get($run->context, 'target_scope.provider_connection_id', 0); $providerConnection = ProviderConnection::query() ->whereKey($providerConnectionId) ->where('workspace_id', (int) $run->workspace_id) ->where('managed_environment_id', (int) $run->managed_environment_id) ->first(); if (! $tenant || ! $providerConnection instanceof ProviderConnection) { throw new RuntimeException('Tenant configuration capture run is missing its managed environment or same-scope provider connection.'); } try { $result = $captureService->capture( tenant: $tenant, providerConnection: $providerConnection, operationRun: $run, canonicalTypes: $this->canonicalTypes($run), ); $run->forceFill([ 'context' => $this->contextWithCaptureResult($run, $result['outcomes']), ])->save(); $completed = $operationRuns->updateRun( run: $run, status: OperationRunStatus::Completed->value, outcome: $result['run_outcome'], summaryCounts: $result['summary_counts'], failures: $result['failures'], ); $this->recordTerminalAudit($auditRecorder, $completed, $providerConnection, $result); } catch (Throwable $e) { $this->recordFailureAudit($auditRecorder, $run, $providerConnection, $e); throw $e; } } /** * @return list|null */ private function canonicalTypes(OperationRun $run): ?array { $types = data_get($run->context, 'resource_types'); if (! is_array($types)) { return null; } return collect($types) ->filter(static fn (mixed $type): bool => is_string($type) && trim($type) !== '') ->map(static fn (string $type): string => trim($type)) ->values() ->all(); } /** * @param list> $outcomes * @return array */ private function contextWithCaptureResult(OperationRun $run, array $outcomes): array { $context = is_array($run->context) ? $run->context : []; $context['capture'] = [ 'resource_type_outcomes' => $outcomes, 'completed_at' => now()->toJSON(), ]; return $context; } /** * @param array{outcomes: list>, summary_counts: array, run_outcome: string, failures: list>} $result */ private function recordTerminalAudit( AuditRecorder $auditRecorder, OperationRun $run, ProviderConnection $providerConnection, array $result, ): void { $tenant = $run->tenant; if (! $tenant) { return; } $auditRecorder->record( action: $result['run_outcome'] === 'failed' ? 'tenant_configuration.capture.failed' : 'tenant_configuration.capture.completed', context: [ 'metadata' => [ 'operation_run_id' => (int) $run->getKey(), 'provider_connection_id' => (int) $providerConnection->getKey(), 'resource_type_outcomes' => $result['outcomes'], 'summary_counts' => $result['summary_counts'], ], ], workspace: $tenant->workspace, tenant: $tenant, actor: $run->user ? AuditActorSnapshot::human($run->user) : null, target: new AuditTargetSnapshot('operation_run', (int) $run->getKey()), outcome: AuditOutcome::normalize($result['run_outcome']), operationRunId: (int) $run->getKey(), ); } private function recordFailureAudit( AuditRecorder $auditRecorder, OperationRun $run, ProviderConnection $providerConnection, Throwable $e, ): void { $tenant = $run->tenant; if (! $tenant) { return; } $auditRecorder->record( action: 'tenant_configuration.capture.failed', context: [ 'metadata' => [ 'operation_run_id' => (int) $run->getKey(), 'provider_connection_id' => (int) $providerConnection->getKey(), 'reason_code' => 'capture_exception', 'message' => RunFailureSanitizer::sanitizeMessage($e->getMessage()), ], ], workspace: $tenant->workspace, tenant: $tenant, actor: $run->user ? AuditActorSnapshot::human($run->user) : null, target: new AuditTargetSnapshot('operation_run', (int) $run->getKey()), outcome: AuditOutcome::Failed, operationRunId: (int) $run->getKey(), ); } }