toBodyLines())->toBe([ 'Admin consent required', 'The provider connection cannot continue until admin consent is granted.', 'Next step: Grant admin consent.', ])->and($envelope->toLegacyNextSteps())->toBe([ [ 'label' => 'Grant admin consent', 'url' => 'https://example.test/admin-consent', ], ]); }); it('builds understandable fallback translations without exposing raw codes as operator labels', function (): void { $envelope = app(FallbackReasonTranslator::class)->translate('custom_retry_timeout'); expect($envelope)->not->toBeNull() ->and($envelope?->operatorLabel)->toBe('Custom Retry Timeout') ->and($envelope?->operatorLabel)->not->toBe('custom_retry_timeout') ->and($envelope?->shortExplanation)->toContain('transient dependency issue') ->and($envelope?->guidanceText())->toBe('Next step: Retry after the dependency recovers.'); }); it('round-trips explicit reason ownership and platform-family metadata', function (): void { $envelope = new ReasonResolutionEnvelope( internalCode: 'provider_consent_missing', operatorLabel: 'Admin consent required', shortExplanation: 'The provider connection cannot continue until admin consent is granted.', actionability: 'prerequisite_missing', reasonOwnership: new ReasonOwnershipDescriptor( ownerLayer: 'provider_owned', ownerNamespace: 'provider.microsoft_graph', reasonCode: 'provider_consent_missing', platformReasonFamily: PlatformReasonFamily::Prerequisite, ), ); $restored = ReasonResolutionEnvelope::fromArray($envelope->toArray()); expect($restored)->not->toBeNull() ->and($restored?->ownerLayer())->toBe('provider_owned') ->and($restored?->ownerNamespace())->toBe('provider.microsoft_graph') ->and($restored?->platformReasonFamily())->toBe(PlatformReasonFamily::Prerequisite->value); });