'review_output.open_review', 'label' => 'Open review', 'url' => '/admin/reviews/1', 'kind' => 'environment_link', 'icon' => 'heroicon-o-arrow-top-right-on-square', ], 'review_output.open_review'); $download = ResolutionAction::fromArray([ 'key' => 'review_output.download_review_pack_with_limitations', 'label' => 'Download review pack with limitations', 'url' => '/admin/review-packs/1/download', 'kind' => 'download', 'icon' => 'heroicon-o-arrow-down-tray', ], 'review_output.download_review_pack_with_limitations'); expect($navigation['type'])->toBe(ResolutionAction::TYPE_NAVIGATION) ->and($navigation['kind'])->toBe('environment_link') ->and($download['type'])->toBe(ResolutionAction::TYPE_DOWNLOAD) ->and($download['kind'])->toBe('download'); }); it('degrades unsafe executable actions to a safe non-executable fallback', function (): void { $action = ResolutionAction::fromArray([ 'key' => 'review_output.retry_generation', 'label' => 'Retry generation', 'type' => ResolutionAction::TYPE_OPERATION_ACTION, 'url' => '/admin/operations/1', 'icon' => 'heroicon-o-arrow-path', ], 'review_output.retry_generation'); expect($action['type'])->toBe(ResolutionAction::TYPE_NAVIGATION) ->and($action['url'])->toBe('/admin/operations/1') ->and($action['capability'])->toBeNull() ->and($action['audit_event'])->toBeNull() ->and($action['requires_confirmation'])->toBeFalse() ->and($action['operation_run_type'])->toBeNull(); }); it('builds a resolution case with explicit scope and proof references', function (): void { $case = ResolutionCase::make( key: 'review_output.publication_blocked', scope: [ 'type' => 'review_pack', 'workspace_id' => 1, 'managed_environment_id' => 41, 'environment_review_id' => 6, 'source_surface' => 'customer_review_workspace', ], severity: 'critical', status: 'blocked', title: 'Output not customer-ready', reason: 'The published review is based on incomplete evidence.', impact: 'Do not share the current review pack externally.', primaryAction: ResolutionAction::fromArray([ 'key' => 'review_output.resolve_review_blockers', 'label' => 'Inspect review blockers', 'url' => '/admin/reviews/6', 'kind' => 'environment_link', ], 'review_output.resolve_review_blockers'), secondaryActions: [ ResolutionAction::fromArray([ 'key' => 'review_output.open_evidence_basis', 'label' => 'Open evidence basis', 'url' => '/admin/evidence/8', 'kind' => 'environment_link', ], 'review_output.open_evidence_basis'), ], sourceRefs: [ ['type' => 'environment_review', 'id' => 6], ['type' => 'review_pack', 'id' => 8], ], evidenceRefs: [ ['type' => 'evidence_snapshot', 'id' => 8], ], technicalDetails: [ 'Review status' => 'Published', ], ); expect($case['scope']['source_surface'])->toBe('customer_review_workspace') ->and($case['primary_action']['key'])->toBe('review_output.resolve_review_blockers') ->and($case['secondary_actions'])->toHaveCount(1) ->and($case['source_refs'])->toHaveCount(2) ->and($case['evidence_refs'])->toHaveCount(1) ->and($case['technical_details']['Review status'])->toBe('Published'); });