$context */ public function translate(string $reasonCode, string $surface = 'detail', array $context = []): ?ReasonResolutionEnvelope { $normalizedCode = trim($reasonCode); if ($normalizedCode === '') { return null; } $actionability = $this->actionabilityFor($normalizedCode); $nextSteps = $this->fallbackNextStepsFor($actionability); return new ReasonResolutionEnvelope( internalCode: $normalizedCode, operatorLabel: $this->operatorLabelFor($normalizedCode), shortExplanation: $this->shortExplanationFor($actionability), actionability: $actionability, nextSteps: $nextSteps, showNoActionNeeded: $actionability === 'non_actionable', diagnosticCodeLabel: $normalizedCode, ); } private function operatorLabelFor(string $reasonCode): string { return Str::headline(str_replace(['.', '-'], '_', $reasonCode)); } private function actionabilityFor(string $reasonCode): string { $reasonCode = strtolower($reasonCode); if (str_contains($reasonCode, 'timeout') || str_contains($reasonCode, 'throttle') || str_contains($reasonCode, 'rate') || str_contains($reasonCode, 'network') || str_contains($reasonCode, 'unreachable') || str_contains($reasonCode, 'transient') || str_contains($reasonCode, 'retry') ) { return 'retryable_transient'; } if (str_contains($reasonCode, 'missing') || str_contains($reasonCode, 'required') || str_contains($reasonCode, 'consent') || str_contains($reasonCode, 'stale') || str_contains($reasonCode, 'prerequisite') || str_contains($reasonCode, 'invalid') ) { return 'prerequisite_missing'; } if (str_contains($reasonCode, 'already_') || str_contains($reasonCode, 'not_applicable') || str_contains($reasonCode, 'no_action') || str_contains($reasonCode, 'info') ) { return 'non_actionable'; } return 'permanent_configuration'; } private function shortExplanationFor(string $actionability): string { return match ($actionability) { 'retryable_transient' => 'TenantPilot recorded a transient dependency issue. Retry after the dependency recovers.', 'prerequisite_missing' => 'TenantPilot recorded a missing or invalid prerequisite for this workflow.', 'non_actionable' => 'TenantPilot recorded this state for visibility only. No operator action is required.', default => 'TenantPilot recorded an access, scope, or configuration issue that needs review before retrying.', }; } /** * @return array */ private function fallbackNextStepsFor(string $actionability): array { return match ($actionability) { 'retryable_transient' => [NextStepOption::instruction('Retry after the dependency recovers.')], 'prerequisite_missing' => [NextStepOption::instruction('Review the recorded prerequisite before retrying.')], 'non_actionable' => [], default => [NextStepOption::instruction('Review access and configuration before retrying.')], }; } }