$context */ public function translate( ?string $reasonCode, ?string $artifactKey = null, string $surface = 'detail', array $context = [], ): ?ReasonResolutionEnvelope { $reasonCode = is_string($reasonCode) ? trim($reasonCode) : ''; if ($reasonCode === '') { return null; } return match (true) { $artifactKey === ProviderReasonTranslator::ARTIFACT_KEY, $artifactKey === null && $this->providerReasonTranslator->canTranslate($reasonCode) => $this->providerReasonTranslator->translate($reasonCode, $surface, $context), $artifactKey === self::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT && BaselineReasonCodes::isKnown($reasonCode) => $this->translateBaselineReason($reasonCode), $artifactKey === null && BaselineReasonCodes::isKnown($reasonCode) => $this->translateBaselineReason($reasonCode), $artifactKey === self::EXECUTION_DENIAL_ARTIFACT, $artifactKey === null && ExecutionDenialReasonCode::tryFrom($reasonCode) instanceof ExecutionDenialReasonCode => ExecutionDenialReasonCode::tryFrom($reasonCode)?->toReasonResolutionEnvelope($surface, $context), $artifactKey === self::TENANT_OPERABILITY_ARTIFACT, $artifactKey === null && TenantOperabilityReasonCode::tryFrom($reasonCode) instanceof TenantOperabilityReasonCode => TenantOperabilityReasonCode::tryFrom($reasonCode)?->toReasonResolutionEnvelope($surface, $context), $artifactKey === self::RBAC_ARTIFACT, $artifactKey === null && RbacReason::tryFrom($reasonCode) instanceof RbacReason => RbacReason::tryFrom($reasonCode)?->toReasonResolutionEnvelope($surface, $context), $artifactKey === self::GOVERNANCE_ARTIFACT_TRUTH_ARTIFACT => $this->fallbackReasonTranslator->translate($reasonCode, $surface, $context), $artifactKey === null && ProviderReasonCodes::isKnown($reasonCode) => $this->providerReasonTranslator->translate($reasonCode, $surface, $context), default => $this->fallbackTranslate($reasonCode, $artifactKey, $surface, $context), }; } /** * @param array $context */ private function fallbackTranslate( string $reasonCode, ?string $artifactKey, string $surface, array $context, ): ?ReasonResolutionEnvelope { if ($artifactKey === null) { $normalizedCode = \App\Support\OpsUx\RunFailureSanitizer::normalizeReasonCode($reasonCode); if ($normalizedCode !== $reasonCode) { return $this->translate($normalizedCode, null, $surface, $context + ['source_reason_code' => $reasonCode]); } } return $this->fallbackReasonTranslator->translate($reasonCode, $surface, $context); } private function translateBaselineReason(string $reasonCode): ReasonResolutionEnvelope { [$operatorLabel, $shortExplanation, $actionability, $nextStep] = match ($reasonCode) { BaselineReasonCodes::CAPTURE_MISSING_SOURCE_TENANT => [ 'Source tenant unavailable', 'The selected tenant is not available in this workspace for baseline capture.', 'prerequisite_missing', 'Select a source tenant from the same workspace before capturing again.', ], BaselineReasonCodes::CAPTURE_PROFILE_NOT_ACTIVE => [ 'Baseline profile inactive', 'Only active baseline profiles can be captured or compared.', 'prerequisite_missing', 'Activate the baseline profile before retrying this action.', ], BaselineReasonCodes::CAPTURE_ROLLOUT_DISABLED, BaselineReasonCodes::COMPARE_ROLLOUT_DISABLED => [ 'Full-content rollout disabled', 'This workflow is disabled by rollout configuration in the current environment.', 'prerequisite_missing', 'Enable the rollout before retrying full-content baseline work.', ], BaselineReasonCodes::SNAPSHOT_BUILDING, BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING => [ 'Baseline still building', 'The selected baseline snapshot is still building and cannot be trusted for compare yet.', 'prerequisite_missing', 'Wait for capture to finish or use the current complete snapshot instead.', ], BaselineReasonCodes::SNAPSHOT_INCOMPLETE, BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE => [ 'Baseline snapshot incomplete', 'The snapshot did not finish cleanly, so TenantPilot will not use it for compare.', 'prerequisite_missing', 'Capture a new baseline and wait for it to complete before comparing.', ], BaselineReasonCodes::SNAPSHOT_SUPERSEDED, BaselineReasonCodes::COMPARE_SNAPSHOT_SUPERSEDED => [ 'Snapshot superseded', 'A newer complete baseline snapshot is current, so this historical snapshot is not compare input anymore.', 'prerequisite_missing', 'Use the current complete snapshot for compare instead of this historical copy.', ], BaselineReasonCodes::SNAPSHOT_CAPTURE_FAILED => [ 'Baseline capture failed', 'Snapshot capture stopped after the row was created, so the artifact remains unusable.', 'retryable_transient', 'Review the run details, then retry the capture once the failure is addressed.', ], BaselineReasonCodes::SNAPSHOT_COMPLETION_PROOF_FAILED => [ 'Completion proof failed', 'TenantPilot could not prove that every expected snapshot item was persisted successfully.', 'prerequisite_missing', 'Capture the baseline again so a complete snapshot can be finalized.', ], BaselineReasonCodes::SNAPSHOT_LEGACY_NO_PROOF => [ 'Legacy completion unproven', 'This older snapshot has no reliable completion proof, so it is blocked from compare.', 'prerequisite_missing', 'Recapture the baseline to create a complete snapshot with explicit lifecycle proof.', ], BaselineReasonCodes::SNAPSHOT_LEGACY_CONTRADICTORY => [ 'Legacy completion contradictory', 'Stored counts or producer-run evidence disagree, so TenantPilot treats this snapshot as incomplete.', 'prerequisite_missing', 'Recapture the baseline to replace this ambiguous historical snapshot.', ], BaselineReasonCodes::COMPARE_NO_ASSIGNMENT => [ 'No baseline assigned', 'This tenant has no assigned baseline profile yet.', 'prerequisite_missing', 'Assign a baseline profile to the tenant before starting compare.', ], BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => [ 'Assigned baseline inactive', 'The assigned baseline profile is not active, so compare cannot start.', 'prerequisite_missing', 'Activate the assigned baseline profile or assign a different active profile.', ], BaselineReasonCodes::COMPARE_NO_ACTIVE_SNAPSHOT, BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT => [ 'Current baseline unavailable', 'No complete baseline snapshot is currently available for compare.', 'prerequisite_missing', 'Capture a baseline and wait for it to complete before comparing.', ], BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET => [ 'No eligible compare target', 'No assigned tenant with compare access is currently available for this baseline profile.', 'prerequisite_missing', 'Assign this baseline to a tenant you can compare, or use an account with access to an assigned tenant.', ], BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT => [ 'Selected snapshot unavailable', 'The requested baseline snapshot could not be found for this profile.', 'prerequisite_missing', 'Refresh the page and select a valid snapshot for this baseline profile.', ], default => [ 'Baseline workflow blocked', 'TenantPilot recorded a baseline precondition that prevents this workflow from continuing safely.', 'prerequisite_missing', 'Review the recorded baseline state before retrying.', ], }; return new ReasonResolutionEnvelope( internalCode: $reasonCode, operatorLabel: $operatorLabel, shortExplanation: $shortExplanation, actionability: $actionability, nextSteps: [ NextStepOption::instruction($nextStep), ], diagnosticCodeLabel: $reasonCode, ); } }