From 94877c9a669022441b72af6b31ef3bc5cb4afe51 Mon Sep 17 00:00:00 2001 From: ahmido Date: Fri, 12 Jun 2026 20:31:17 +0000 Subject: [PATCH] feat(ui): implement diagnostic surface separation (#444) Applied the decision-first diagnostic surface IA contract to EnvironmentDiagnostics and SupportDiagnostics bundles. Added recommended_first_check and separated technical metadata as per Spec 373. Co-authored-by: Ahmed Darrazi Reviewed-on: https://git.cloudarix.de/ahmido/TenantAtlas/pulls/444 --- .../Filament/Pages/EnvironmentDiagnostics.php | 83 +++++ .../SupportDiagnosticBundleBuilder.php | 112 +++++- .../support-diagnostic-bundle.blade.php | 70 +++- .../pages/environment-diagnostics.blade.php | 119 +++++-- .../Filament/TenantDiagnosticsRepairsTest.php | 62 +++- ...perationRunSupportDiagnosticActionTest.php | 5 + .../TenantSupportDiagnosticActionTest.php | 5 + .../SupportDiagnosticBundleBuilderTest.php | 42 ++- .../ui-012-environment-diagnostics.md | 53 +++ .../ui-ux-enterprise-audit/route-inventory.md | 2 +- .../artifacts/affected-files.md | 28 ++ .../before-after-screenshot-index.md | 24 ++ .../artifacts/browser-verification-report.md | 41 +++ .../artifacts/diagnostic-safety-checklist.md | 19 + .../artifacts/diagnostic-surface-contracts.md | 49 +++ .../artifacts/implementation-notes.md | 33 ++ .../001-environment-diagnostics-after.png | Bin 0 -> 57370 bytes ...2-support-diagnostics-after-or-blocked.png | Bin 0 -> 76541 bytes .../artifacts/source-audit-summary.md | 51 +++ .../artifacts/validation-report.md | 75 ++++ .../checklists/requirements.md | 60 ++++ .../373-diagnostic-surface-separation/plan.md | 266 ++++++++++++++ .../373-diagnostic-surface-separation/spec.md | 328 ++++++++++++++++++ .../tasks.md | 182 ++++++++++ 24 files changed, 1668 insertions(+), 41 deletions(-) create mode 100644 docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/affected-files.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/before-after-screenshot-index.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/diagnostic-safety-checklist.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/implementation-notes.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png create mode 100644 specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png create mode 100644 specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md create mode 100644 specs/373-diagnostic-surface-separation/artifacts/validation-report.md create mode 100644 specs/373-diagnostic-surface-separation/checklists/requirements.md create mode 100644 specs/373-diagnostic-surface-separation/plan.md create mode 100644 specs/373-diagnostic-surface-separation/spec.md create mode 100644 specs/373-diagnostic-surface-separation/tasks.md diff --git a/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php b/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php index 610908ff..00cc81f7 100644 --- a/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php +++ b/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php @@ -58,6 +58,89 @@ public function mount(): void ->userHasDuplicateMemberships($tenant, $user); } + /** + * @return array{ + * headline: string, + * body: string, + * status: string, + * color: string, + * impact: string, + * next_check: string, + * primary_action_label: ?string, + * secondary_action_label: ?string, + * blockers: list + * } + */ + public function diagnosticSummary(): array + { + $blockers = []; + + if ($this->missingOwner) { + $blockers[] = [ + 'key' => 'missing_owner', + 'label' => 'Missing owner', + 'failed_condition' => 'No Owner membership is currently visible for this managed environment.', + 'impact' => 'Tenant repair and accountability workflows need an owner before support can treat access as complete.', + 'next_check' => 'Confirm workspace role recovery, then use Bootstrap owner only when the current administrator is authorized to repair tenant scope.', + 'action_label' => 'Bootstrap owner', + 'action_role' => 'Primary repair path', + ]; + } + + if ($this->hasDuplicateMembershipsForCurrentUser) { + $blockers[] = [ + 'key' => 'duplicate_memberships', + 'label' => 'Duplicate memberships', + 'failed_condition' => 'The current user has more than one tenant membership row for this managed environment.', + 'impact' => 'Duplicate access scope rows make authorization support harder to reason about for this user.', + 'next_check' => 'Merge the duplicate rows, then reload the diagnostics page to confirm only one membership remains.', + 'action_label' => 'Merge duplicate access scopes', + 'action_role' => count($blockers) === 0 ? 'Primary repair path' : 'Secondary repair path', + ]; + } + + $blockerCount = count($blockers); + + if ($blockerCount === 0) { + return [ + 'headline' => 'No diagnostic action is required', + 'body' => 'No actionable tenant membership defect is visible for the current managed-environment context.', + 'status' => 'No action required', + 'color' => 'gray', + 'impact' => 'This page did not find a tenant membership repair to run for the current user and environment.', + 'next_check' => 'Review provider, operation, or audit surfaces only when another page reports a blocker.', + 'primary_action_label' => null, + 'secondary_action_label' => null, + 'blockers' => [], + ]; + } + + $primaryBlocker = $blockers[0]; + $secondaryBlocker = $blockers[1] ?? null; + + return [ + 'headline' => $blockerCount === 1 + ? '1 diagnostic blocker needs attention' + : $blockerCount.' diagnostic blockers need attention', + 'body' => 'Resolve the highest-impact tenant membership blocker first; lower repair paths remain visible for context.', + 'status' => $blockerCount === 1 ? 'Action needed' : $blockerCount.' blockers', + 'color' => 'warning', + 'impact' => $primaryBlocker['impact'], + 'next_check' => $primaryBlocker['next_check'], + 'primary_action_label' => $primaryBlocker['action_label'], + 'secondary_action_label' => $secondaryBlocker['action_label'] ?? null, + 'blockers' => $blockers, + ]; + } + /** * @return array */ diff --git a/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php b/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php index 1394d39d..76a492ce 100644 --- a/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php +++ b/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php @@ -145,6 +145,7 @@ public function forOperationRun(OperationRun $run, ?User $actor = null): array * freshness_state: string, * completeness_note: ?string, * redaction_note: string, + * recommended_first_check: array{label: string, body: string, reference: array|null}, * generated_from: string * }, * redaction: array{mode: string, markers: list}, @@ -182,6 +183,14 @@ private function bundle( array $sections, ): array { $sections = $this->sortSections($sections); + $freshnessState = $this->freshnessState($sections); + $completenessNote = $this->completenessNote($sections); + $recommendedFirstCheck = $this->recommendedFirstCheck( + contextType: $contextType, + providerConnection: $providerConnection, + operationRun: $operationRun, + sections: $sections, + ); $redactionMarkers = RedactionIntegrity::supportDiagnosticsMarkers(); $contextualHelp = $this->contextualHelp($tenant, $providerConnection, $operationRun, $sections); @@ -203,14 +212,15 @@ private function bundle( 'operation_run' => $operationRun instanceof OperationRun ? $this->operationReference($operationRun, $tenant) : null, 'headline' => $headline, 'dominant_issue' => $dominantIssue, - 'freshness_state' => $this->freshnessState($sections), + 'freshness_state' => $freshnessState, 'redaction_mode' => 'default_redacted', 'summary' => [ 'headline' => $headline, 'dominant_issue' => $dominantIssue, - 'freshness_state' => $this->freshnessState($sections), - 'completeness_note' => $this->completenessNote($sections), + 'freshness_state' => $freshnessState, + 'completeness_note' => $completenessNote, 'redaction_note' => RedactionIntegrity::supportDiagnosticsNote(), + 'recommended_first_check' => $recommendedFirstCheck, 'generated_from' => 'derived_existing_truth', ], 'contextual_help' => $contextualHelp, @@ -221,11 +231,105 @@ private function bundle( ], 'notes' => array_values(array_filter([ RedactionIntegrity::supportDiagnosticsNote(), - $this->completenessNote($sections), + $completenessNote, ])), ]; } + /** + * @param list> $sections + * @return array{label: string, body: string, reference: array|null} + */ + private function recommendedFirstCheck( + string $contextType, + ?ProviderConnection $providerConnection, + ?OperationRun $operationRun, + array $sections, + ): array { + if ($contextType === 'operation_run' && $operationRun instanceof OperationRun) { + return [ + 'label' => 'Check operation context first', + 'body' => 'Start with the operation context because this support bundle was opened from a specific run.', + 'reference' => $this->firstSectionReference($sections, 'operation_context'), + ]; + } + + if ($providerConnection instanceof ProviderConnection && $this->providerIssue($providerConnection) !== null) { + return [ + 'label' => 'Check provider connection first', + 'body' => 'Start with the provider connection and required access because the dominant issue points to provider readiness.', + 'reference' => $this->firstSectionReference($sections, 'provider_connection'), + ]; + } + + if ($operationRun instanceof OperationRun && in_array((string) $operationRun->outcome, ['failed', 'blocked', 'partially_succeeded'], true)) { + return [ + 'label' => 'Check operation context first', + 'body' => 'Start with the operation context because the latest run outcome is the dominant support signal.', + 'reference' => $this->firstSectionReference($sections, 'operation_context'), + ]; + } + + if ($this->sectionAvailability($sections, 'findings') === 'available') { + return [ + 'label' => 'Check findings first', + 'body' => 'Start with the finding references because open findings are the clearest support signal in this bundle.', + 'reference' => $this->firstSectionReference($sections, 'findings'), + ]; + } + + if ($this->sectionAvailability($sections, 'provider_connection') === 'missing') { + return [ + 'label' => 'Confirm provider connection availability', + 'body' => 'No provider connection reference is available; confirm the environment has a repo-backed provider connection before deeper diagnostics.', + 'reference' => $this->firstSectionReference($sections, 'provider_connection'), + ]; + } + + return [ + 'label' => 'Confirm scoped context first', + 'body' => 'Start with workspace and managed-environment scope, then inspect lower sections only if another surface reported a blocker.', + 'reference' => $this->firstSectionReference($sections, 'overview'), + ]; + } + + /** + * @param list> $sections + */ + private function sectionAvailability(array $sections, string $key): ?string + { + foreach ($sections as $section) { + if (($section['key'] ?? null) !== $key) { + continue; + } + + $availability = $section['availability'] ?? null; + + return is_string($availability) ? $availability : null; + } + + return null; + } + + /** + * @param list> $sections + * @return array|null + */ + private function firstSectionReference(array $sections, string $key): ?array + { + foreach ($sections as $section) { + if (($section['key'] ?? null) !== $key) { + continue; + } + + $references = is_array($section['references'] ?? null) ? $section['references'] : []; + + return is_array($references[0] ?? null) ? $references[0] : null; + } + + return null; + } + /** * @param list> $sections * @return array|null diff --git a/apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php b/apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php index b8183dcd..bff578ca 100644 --- a/apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php +++ b/apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php @@ -7,6 +7,9 @@ $sections = is_array($bundle['sections'] ?? null) ? $bundle['sections'] : []; $redaction = is_array($bundle['redaction'] ?? null) ? $bundle['redaction'] : []; $notes = is_array($bundle['notes'] ?? null) ? $bundle['notes'] : []; + $recommendedFirstCheck = is_array(data_get($summary, 'recommended_first_check')) + ? data_get($summary, 'recommended_first_check') + : null; $availabilityColor = static function (?string $availability): string { return match ($availability) { @@ -54,16 +57,62 @@ -
-
-
Workspace
-
{{ data_get($context, 'workspace_label', 'Workspace unavailable') }}
-
-
-
ManagedEnvironment
-
{{ data_get($context, 'tenant_label', 'ManagedEnvironment unavailable') }}
-
-
+
+ @if ($recommendedFirstCheck !== null) + @php + $firstCheckReference = is_array($recommendedFirstCheck['reference'] ?? null) + ? $recommendedFirstCheck['reference'] + : null; + $firstCheckReferenceLabel = $firstCheckReference['label'] ?? 'Reference unavailable'; + $firstCheckReferenceUrl = is_string($firstCheckReference['url'] ?? null) && trim((string) $firstCheckReference['url']) !== '' + ? (string) $firstCheckReference['url'] + : null; + $firstCheckReferenceActionLabel = $firstCheckReference['action_label'] ?? ($firstCheckReferenceUrl ? 'Open' : 'Unavailable'); + @endphp + + + + + {{ data_get($recommendedFirstCheck, 'label', 'Review source first') }} + + + + @if ($firstCheckReference !== null) +
+ + {{ $firstCheckReferenceLabel }} + + + @if ($firstCheckReferenceUrl) + + {{ $firstCheckReferenceActionLabel }} + + @else + + {{ $firstCheckReferenceActionLabel }} + + @endif +
+ @endif +
+ @endif + +
+
+
Workspace
+
{{ data_get($context, 'workspace_label', 'Workspace unavailable') }}
+
+
+
ManagedEnvironment
+
{{ data_get($context, 'tenant_label', 'ManagedEnvironment unavailable') }}
+
+
+
@if (is_array($bundle['contextual_help'] ?? null)) @@ -164,4 +213,3 @@ @endforeach - diff --git a/apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php b/apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php index fecf2b5d..9cdbe457 100644 --- a/apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php +++ b/apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php @@ -1,31 +1,104 @@ +@php + $summary = $this->diagnosticSummary(); + $blockers = $summary['blockers']; +@endphp +
-
-

ManagedEnvironment diagnostics

-

- Identify common tenant configuration issues and apply safe repairs. -

-
+ + + + {{ $summary['status'] }} + + - @if ($missingOwner) -
-
Missing owner
-
This tenant currently has no Owner members.
-
- @endif +
+
+
+
+ Impact +
+
+ {{ $summary['impact'] }} +
+
+
+
+ Recommended first check +
+
+ {{ $summary['next_check'] }} +
+
+
- @if ($hasDuplicateMembershipsForCurrentUser) -
-
Duplicate memberships
-
This tenant has duplicate membership rows for your user.
-
- @endif + @if ($summary['primary_action_label'] !== null) +
+ + Primary repair path + + + {{ $summary['primary_action_label'] }} + - @if (! $missingOwner && ! $hasDuplicateMembershipsForCurrentUser) -
-
All good
-
No known issues detected.
+ @if ($summary['secondary_action_label'] !== null) + + Secondary repair path + + + {{ $summary['secondary_action_label'] }} + + @endif +
+ @endif
- @endif + + + @foreach ($blockers as $blocker) + + + + {{ $blocker['action_role'] }} + + + +
+
+
+ Repair action +
+
+ {{ $blocker['action_label'] }} +
+
+
+
+ Impact +
+
+ {{ $blocker['impact'] }} +
+
+
+
+ Next check +
+
+ {{ $blocker['next_check'] }} +
+
+
+
+ @endforeach
diff --git a/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php b/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php index 1d51b25b..4018908e 100644 --- a/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php +++ b/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php @@ -24,9 +24,12 @@ $this->actingAs($owner); Filament::setTenant($tenant, true); + bindFailHardGraphClient(); Livewire::test(EnvironmentDiagnostics::class) - ->assertSee('All good') + ->assertSee('No diagnostic action is required') + ->assertDontSee('All good') + ->assertDontSee('No known issues detected') ->assertActionHidden('bootstrapOwner') ->assertActionHidden('mergeDuplicateMemberships'); }); @@ -48,6 +51,62 @@ ->assertActionHidden('bootstrapOwner'); }); + it('renders a single action hierarchy for duplicate membership diagnostics', function () { + [$owner, $tenant] = createUserWithTenant(role: 'owner'); + + $this->actingAs($owner); + + Filament::setTenant($tenant, true); + bindFailHardGraphClient(); + + Schema::table('managed_environment_memberships', function (Blueprint $table): void { + $table->dropUnique(['managed_environment_id', 'user_id']); + }); + + ManagedEnvironmentMembership::query()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'user_id' => (int) $owner->getKey(), + 'role' => 'readonly', + 'source' => 'manual', + 'created_by_user_id' => (int) $owner->getKey(), + ]); + + Livewire::test(EnvironmentDiagnostics::class) + ->assertSee('1 diagnostic blocker needs attention') + ->assertSee('Recommended first check') + ->assertSee('Duplicate memberships') + ->assertSee('Merge duplicate access scopes') + ->assertDontSee('No diagnostic action is required') + ->assertActionVisible('mergeDuplicateMemberships') + ->assertActionEnabled('mergeDuplicateMemberships') + ->assertActionExists('mergeDuplicateMemberships', fn (Action $action): bool => $action->isConfirmationRequired()); + }); + + it('prioritizes missing owner over duplicate memberships when both blockers are visible', function () { + [$owner, $tenant] = createUserWithTenant(role: 'owner'); + + $this->actingAs($owner); + + Filament::setTenant($tenant, true); + bindFailHardGraphClient(); + + Livewire::test(EnvironmentDiagnostics::class) + ->set('missingOwner', true) + ->set('hasDuplicateMembershipsForCurrentUser', true) + ->assertSeeInOrder([ + '2 diagnostic blockers need attention', + 'Bootstrap owner', + 'Duplicate memberships', + 'Merge duplicate access scopes', + ]) + ->assertSee('Primary repair path') + ->assertSee('Secondary repair path') + ->assertActionVisible('bootstrapOwner') + ->assertActionVisible('mergeDuplicateMemberships') + ->assertActionExists('bootstrapOwner', fn (Action $action): bool => $action->isConfirmationRequired()) + ->assertActionExists('mergeDuplicateMemberships', fn (Action $action): bool => $action->isConfirmationRequired()); + }); + it('shows duplicate-scope repair as disabled for readonly members', function () { [$readonly, $tenant] = createUserWithTenant(role: 'readonly'); @@ -103,6 +162,7 @@ Livewire::test(EnvironmentDiagnostics::class) ->assertActionVisible('mergeDuplicateMemberships') ->assertActionEnabled('mergeDuplicateMemberships') + ->assertActionExists('mergeDuplicateMemberships', fn (Action $action): bool => $action->isConfirmationRequired()) ->mountAction('mergeDuplicateMemberships') ->callMountedAction() ->assertSuccessful(); diff --git a/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php b/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php index d93c59f6..5d571bdc 100644 --- a/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php +++ b/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php @@ -140,6 +140,8 @@ function operationSupportDiagnosticsComponent(User $user, OperationRun $run): \L 'recorded_at' => now()->subMinutes(5), ]); + bindFailHardGraphClient(); + operationSupportDiagnosticsComponent($user, $run) ->assertActionVisible('openSupportDiagnostics') ->assertActionEnabled('openSupportDiagnostics') @@ -147,6 +149,9 @@ function operationSupportDiagnosticsComponent(User $user, OperationRun $run): \L ->assertActionHasIcon('openSupportDiagnostics', 'heroicon-o-lifebuoy') ->mountAction('openSupportDiagnostics') ->assertMountedActionModalSee('Support diagnostics') + ->assertMountedActionModalSee('Recommended first check') + ->assertMountedActionModalSee('Check operation context first') + ->assertMountedActionModalSee('Start with the operation context because this support bundle was opened from a specific run') ->assertMountedActionModalSee(OperationRunLinks::identifier($run)) ->assertMountedActionModalSee('The compare finished, but no decision-grade result is available yet.') ->assertMountedActionModalSee('Contoso Microsoft connection') diff --git a/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php b/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php index f6644e82..c4b5d693 100644 --- a/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php +++ b/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php @@ -136,12 +136,17 @@ function tenantSupportDiagnosticsComponent(User $user, ManagedEnvironment $tenan 'recorded_at' => now()->subMinutes(5), ]); + bindFailHardGraphClient(); + tenantSupportDiagnosticsComponent($user, $tenant) ->assertActionVisible('openSupportDiagnostics') ->assertActionEnabled('openSupportDiagnostics') ->assertActionExists('openSupportDiagnostics', fn (Action $action): bool => $action->getLabel() === 'Open support diagnostics') ->mountAction('openSupportDiagnostics') ->assertMountedActionModalSee('Support diagnostics') + ->assertMountedActionModalSee('Recommended first check') + ->assertMountedActionModalSee('Check provider connection first') + ->assertMountedActionModalSee('Start with the provider connection and required access') ->assertMountedActionModalSee('Contoso Support ManagedEnvironment') ->assertMountedActionModalSee('Permissions missing') ->assertMountedActionModalSee('provider app is missing required Microsoft Graph permissions') diff --git a/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php b/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php index b1fc12e3..8a6b35c6 100644 --- a/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php +++ b/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php @@ -10,6 +10,7 @@ use App\Support\OperationRunOutcome; use App\Support\OperationRunStatus; use App\Support\OperationRunType; +use App\Support\Providers\ProviderVerificationStatus; use App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -103,6 +104,45 @@ ->and($sections['review_pack']['availability'])->toBe('missing'); }); +it('uses neutral first-check copy when tenant support context has no dominant issue or provider reference', function (): void { + $tenant = ManagedEnvironment::factory()->create(['name' => 'Quiet ManagedEnvironment']); + + $bundle = app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant); + $firstCheck = $bundle['summary']['recommended_first_check']; + + expect($bundle['dominant_issue']) + ->toBe('No dominant support blocker is currently visible from the selected tenant context.') + ->and($firstCheck['label'])->toBe('Confirm provider connection availability') + ->and($firstCheck['body'])->toContain('No provider connection reference is available') + ->and($firstCheck['reference']['label'])->toBe('Provider connection not observed') + ->and($firstCheck['reference']['availability'])->toBe('missing') + ->and($firstCheck['reference']['url'])->toBeNull(); +}); + +it('falls back to provider readiness guidance for untranslated provider reasons without exposing the raw reason code', function (): void { + $tenant = ManagedEnvironment::factory()->create(['name' => 'Untranslated Reason ManagedEnvironment']); + + ProviderConnection::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'display_name' => 'Untranslated reason connection', + 'verification_status' => ProviderVerificationStatus::Blocked->value, + 'last_error_reason_code' => 'ext.support.manual_lookup_needed', + 'last_health_check_at' => now()->subMinutes(3), + ]); + + $bundle = app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant); + $bundleJson = json_encode($bundle, JSON_THROW_ON_ERROR); + + expect($bundle['summary']['recommended_first_check']['label']) + ->toBe('Check provider connection first') + ->and($bundle['summary']['recommended_first_check']['body']) + ->toContain('Start with the provider connection and required access') + ->and($bundle['summary']['recommended_first_check']['reference']['label']) + ->toBe('Untranslated reason connection') + ->and($bundleJson)->not->toContain('ext.support.manual_lookup_needed'); +}); + it('marks references without canonical destinations as inaccessible', function (): void { $tenant = ManagedEnvironment::factory()->create(); $connection = ProviderConnection::factory()->create([ @@ -125,4 +165,4 @@ ->toBeArray() ->and($reference['availability'])->toBe('inaccessible') ->and($reference['access_reason'])->toBe('Canonical destination is not available from this context.'); -}); \ No newline at end of file +}); diff --git a/docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md b/docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md new file mode 100644 index 00000000..b5b37bfa --- /dev/null +++ b/docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md @@ -0,0 +1,53 @@ +# UI-012 Environment Diagnostics + +| Field | Value | +| --- | --- | +| Route | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | +| Source | `EnvironmentDiagnostics` | +| Area / scope | Support diagnostics / environment | +| Archetype | Support / Diagnostics | +| Design depth | Domain Pattern Surface | +| Repo truth | repo-verified | +| Screenshot | `specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png` | +| Browser status | Spec 373 smoke-login fixture reached no-action state and captured the after screenshot. | + +## First Five Seconds + +The page now leads with one diagnostic summary: current blocker count, impact, recommended first check, and the dominant repair path when a repair is applicable. + +## Productization Review + +- Decision-first: improved; the page no longer opens with a generic "all good" card or competing repair messages. +- Evidence-first: blocker details remain visible below the summary with failed condition, impact, and next check. +- Context: environment-owned diagnostic surface using DB-local tenant membership truth. +- Customer/auditor safety: internal/admin-only repair surface. +- Diagnostics: provider and technical details stay out of the first viewport unless they explain the active tenant membership blocker. + +## Information Inventory + +Default content now includes: + +- one calm no-action summary when no membership repair applies +- one ordered summary when one or more blockers apply +- failed condition, impact, and next check for missing owner and duplicate memberships +- existing capability-gated repair actions in the Filament header + +## Dangerous Actions + +`Bootstrap owner` and `Merge duplicate access scopes` remain destructive, confirmation-gated, capability-gated, and backed by the existing repair services. The UI change does not add automatic repair, provider calls, migrations, or new runtime dependencies. + +## Scores + +| IA | Density | User Clarity | Sellability | Disclosure | Hierarchy | DS Fit | A11y | Responsive | Components | UX Writing | Perf | +| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | +| 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | + +## Top Issues + +1. Missing-owner runtime truth remains intentionally hidden by the current workspace-role recovery model; tests cover only the presentation path. +2. Broader support workflows remain outside UI-012 and should stay on separate support-diagnostics specs. +3. Future blocker-state browser proof would need a safe fixture that creates duplicate membership state without changing shared smoke setup. + +## Target Direction + +Implemented in Spec 373 as a bounded hierarchy pass over the existing diagnostics page. Future work should address broader support workflows only through a separate spec. diff --git a/docs/ui-ux-enterprise-audit/route-inventory.md b/docs/ui-ux-enterprise-audit/route-inventory.md index 1f089be3..547ea3b2 100644 --- a/docs/ui-ux-enterprise-audit/route-inventory.md +++ b/docs/ui-ux-enterprise-audit/route-inventory.md @@ -17,7 +17,7 @@ # Route Inventory | UI-009 | `/admin/workspaces/{record}` and `/edit` | Workspace resource | Workspace Detail / Edit | Settings / admin | workspace | route exists | workspace view/edit capability | Settings / Admin | Workspace / Tenant Context | Domain Pattern Surface | repo-verified | - | - | Dynamic record routes need seeded workspace context for visual review. | | UI-010 | `/admin/workspaces/{workspace}/environments` | route + `ManagedEnvironmentsLanding` | Managed Environments | Workspace / environment | workspace | route exists | workspace member | Workspace / Tenant Context | Provider / Integration | Strategic Surface | repo-verified | - | [report](page-reports/ui-010-managed-environments.md) | Portfolio entry point for environments; runtime updated to robust selection layout. | | UI-011 | `/admin/workspaces/{workspace}/environments/{environment}` | route + `EnvironmentDashboard` | Environment Dashboard | Workspace / environment | environment-bound | reachable | workspace + environment entitlement | Overview / Dashboard | Workspace / Tenant Context | Strategic Surface | repo-verified | [desktop](screenshots/desktop/ui-002-environment-dashboard.png) | [report](page-reports/ui-002-environment-dashboard.md) | Core environment product surface. | -| UI-012 | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | route + page | Environment Diagnostics | Support | environment-bound | route exists | environment entitlement | Support / Diagnostics | Provider / Integration | Domain Pattern Surface | repo-verified | - | - | Diagnostics must remain secondary to operator surfaces. | +| UI-012 | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | route + page | Environment Diagnostics | Support | environment-bound | route exists | environment entitlement | Support / Diagnostics | Provider / Integration | Domain Pattern Surface | repo-verified | [desktop](../../specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png) | [report](page-reports/ui-012-environment-diagnostics.md) | Diagnostics must remain secondary to operator surfaces; Spec 373 adds a bounded hierarchy pass. | | UI-013 | `/admin/workspaces/{workspace}/environments/{environment}/access-scopes` | resource page | Environment Access Scopes | Settings / admin | environment-bound | route exists | owner/manager capability expected | Settings / Admin | Auth / Access | Strategic Surface | repo-verified | - | - | RBAC-sensitive environment access surface. | | UI-014 | `/admin/onboarding` | route + wizard | Environment Onboarding | Provider / onboarding | workspace | route exists | workspace capability | Provider / Integration | Workspace / Tenant Context | Strategic Surface | repo-verified | - | - | Large wizard; individual target treatment likely needed. | | UI-015 | `/admin/onboarding/{onboardingDraft}` | route + wizard | Onboarding Draft | Provider / onboarding | workspace | route exists | scoped draft resolver | Provider / Integration | Workspace / Tenant Context | Domain Pattern Surface | repo-verified | - | - | Dynamic workflow state requires seeded draft to review. | diff --git a/specs/373-diagnostic-surface-separation/artifacts/affected-files.md b/specs/373-diagnostic-surface-separation/artifacts/affected-files.md new file mode 100644 index 00000000..92a329fc --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/affected-files.md @@ -0,0 +1,28 @@ +# Affected Files + +Status: complete after implementation, targeted tests, browser smoke, and artifact review. + +| File | Touch | Risk | Verification | +|---|---|---|---| +| `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` | Added page-local `diagnosticSummary()` derived from existing public page state. | low; action safety had to remain unchanged | `TenantDiagnosticsRepairsTest`, source review | +| `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` | Replaced flat cards with one leading summary plus lower blocker detail sections. | medium UI hierarchy risk | `TenantDiagnosticsRepairsTest`, browser screenshot `001-environment-diagnostics-after.png` | +| `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` | Added derived `recommended_first_check` using existing sections/references. | low; no persistence or provider calls | `SupportDiagnostics` filter, builder unit test | +| `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` | Rendered recommended first check before lower support sections. | medium modal hierarchy/redaction risk | Support diagnostics modal tests, browser screenshot `002-support-diagnostics-after-or-blocked.png` | +| `apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` | Added no-action, duplicate, both-blocker, confirmation, and fail-hard Graph assertions. | low test-only | focused file run passed | +| `apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php` | Added modal hierarchy and fail-hard Graph assertions. | low test-only | `SupportDiagnostics` filter passed | +| `apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php` | Added OperationRun modal first-check assertions and fail-hard Graph guard. | low test-only | `SupportDiagnostics` filter passed | +| `apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php` | Added fallback and untranslated provider reason recommended-first-check coverage. | low test-only | focused unit test passed | +| `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` | Created dedicated UI-012 Environment Diagnostics report. | low docs/artifact | manual review, `git diff --check` | +| `docs/ui-ux-enterprise-audit/route-inventory.md` | Linked UI-012 to the new dedicated report. | low docs/artifact | `git diff --check` | +| `specs/373-diagnostic-surface-separation/artifacts/*` | Updated implementation, browser, safety, validation, and screenshot evidence. | low | artifact review | + +## Out Of Scope Side Effects + +- No migrations. +- No packages. +- No env vars. +- No queues, scheduler, or storage changes. +- No Graph/provider HTTP calls during render. +- No panel provider registration changes. +- No global search changes. +- No Provider Connections or Required Permissions reimplementation. diff --git a/specs/373-diagnostic-surface-separation/artifacts/before-after-screenshot-index.md b/specs/373-diagnostic-surface-separation/artifacts/before-after-screenshot-index.md new file mode 100644 index 00000000..bf3ed176 --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/before-after-screenshot-index.md @@ -0,0 +1,24 @@ +# Before / After Screenshot Index + +Status: complete after browser smoke. + +## Before Evidence + +| Surface | Source | Status | Notes | +|---|---|---|---| +| Environment Diagnostics | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/015-diagnostic-surface-diagnostics-environment-diagnostics.png` | available | Sparse `All good` card and diagnostic header; score 3.3 | +| Required Permissions blocked | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/blocked-or-error/016-configuration-surface-settings-required-permissions-error.png` | available | Historical fixture/auth blocker only | +| System dashboard blocked | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/blocked-or-error/031-system-surface-dashboard-system-dashboard-error.png` | available | Deferred out of scope | + +## Spec 373 Outputs + +| Target | Path | Status | +|---|---|---| +| Environment Diagnostics after | `specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png` | captured; smoke-login fixture reached no-action state | +| Support diagnostics modal after | `specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png` | captured; smoke-login fixture opened dashboard action modal | + +## Scoring Notes + +- Spec 368 source score for Environment Diagnostics: 3.3. +- Spec 373 bounded implementation-review estimate after screenshot: 4.0. The page now has one calm summary, clear impact, and a recommended first check. +- Support diagnostics modal was not part of the Spec 368 page score, but browser evidence now shows its recommended first check above lower references. diff --git a/specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md b/specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md new file mode 100644 index 00000000..c9f1971c --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md @@ -0,0 +1,41 @@ +# Browser Verification Report + +Status: complete. + +## Required Browser Targets + +| Target | URL pattern | Required proof | +|---|---|---| +| Environment Diagnostics | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | page loads, summary leads, no-action or blocker state visible, no console/runtime errors | +| Support diagnostics modal | existing `openSupportDiagnostics` action on Environment Dashboard or OperationRun detail | modal opens, first check and redaction note precede references, no console/runtime errors | + +## Fixture + +- Smoke login route: `/admin/local/smoke-login`. +- User: `smoke-requester+352@tenantpilot.local`. +- Workspace: `spec-352-guidance-browser-audit`. +- Environment: `spec-352-audit-no-urgent`. +- Environment Diagnostics URL tested: `http://localhost/admin/workspaces/spec-352-guidance-browser-audit/environments/spec-352-audit-no-urgent/diagnostics`. +- Support diagnostics modal host tested: `http://localhost/admin/workspaces/spec-352-guidance-browser-audit/environments/spec-352-audit-no-urgent`. + +## Planned Screenshot Outputs + +- `artifacts/screenshots/001-environment-diagnostics-after.png` +- `artifacts/screenshots/002-support-diagnostics-after-or-blocked.png` + +## Blocked-Path Rule + +If the current fixture cannot reach support diagnostics without broad auth/fixture work, document the route/action attempted, observed blocker, and screenshot path. Do not solve fixture/auth infrastructure inside Spec 373. + +## Results + +| Target | Result | Evidence | +|---|---|---| +| Environment Diagnostics | PASS | Route loaded through smoke-login fixture; `No diagnostic action is required` led the page; old `All good` / `No known issues detected` copy absent; screenshot `001-environment-diagnostics-after.png`. | +| Support diagnostics modal | PASS | Dashboard grouped `More` action opened `Open support diagnostics`; modal showed `Recommended first check` above contextual help, support notes, overview, provider connection, and operation sections; screenshot `002-support-diagnostics-after-or-blocked.png`. | +| Browser console | PASS | In-app Browser tab reported zero current error logs after the scoped flow. Boost browser log feed returned only stale unrelated entries dated June 6-8, 2026 on operation pages. | + +## Notes + +- The smoke fixture represented the no-action Environment Diagnostics state and a neutral support diagnostics first check. Missing-owner and duplicate-membership blocker states remain covered by Feature/Livewire tests because the current repo default intentionally keeps owner bootstrap hidden under workspace-role recovery. +- Provider Connections and Required Permissions were not recaptured; no shared Spec 353 runtime files changed. diff --git a/specs/373-diagnostic-surface-separation/artifacts/diagnostic-safety-checklist.md b/specs/373-diagnostic-surface-separation/artifacts/diagnostic-safety-checklist.md new file mode 100644 index 00000000..ae87cf35 --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/diagnostic-safety-checklist.md @@ -0,0 +1,19 @@ +# Diagnostic Safety Checklist + +Status: complete after source review, focused tests, and browser smoke. + +| Surface | Safety item | Status | Evidence | +|---|---|---|---| +| Environment Diagnostics | Single top summary leads before blocker details | pass | `TenantDiagnosticsRepairsTest`; browser screenshot `001-environment-diagnostics-after.png` | +| Environment Diagnostics | Missing-owner path, when repo-backed, states failed condition, impact, and next check | pass | `TenantDiagnosticsRepairsTest` state-driven presentation path | +| Environment Diagnostics | Duplicate-membership path states supportability/access-scope issue and preserves merge action | pass | `TenantDiagnosticsRepairsTest` duplicate membership tests | +| Environment Diagnostics | Both-blocker path shows both blockers with one dominant next action | pass | `TenantDiagnosticsRepairsTest` both-blocker test | +| Environment Diagnostics | No-action path says one calm no-action message and avoids broad health claims | pass | `TenantDiagnosticsRepairsTest`; browser screenshot | +| Environment Diagnostics | Repair actions retain `->action(...)`, `->requiresConfirmation()`, destructive styling, `TENANT_MANAGE`, `UiEnforcement`, server-side service ownership, and audit | pass | Source review plus confirmation/action/audit tests | +| Environment Diagnostics | Render remains DB-local with no Graph/provider HTTP | pass | `bindFailHardGraphClient()` render tests and source review | +| Support diagnostics modal | Summary, dominant issue, first check, redaction, freshness/completeness precede reference sections | pass | Support diagnostics action tests; browser screenshot `002-support-diagnostics-after-or-blocked.png` | +| Support diagnostics modal | Redaction markers and raw/support details remain lower priority and redacted | pass | Existing redaction tests and updated modal tests | +| Support diagnostics modal | Unavailable context is not rendered as a fake link | pass | Builder fallback unit test | +| Support diagnostics modal | Authorization, telemetry, audit, and redaction behavior remain unchanged | pass | `SupportDiagnostics` filter: 39 passed | +| Provider Connections / Required Permissions | No reimplementation or shared-helper regression | pass | Final diff review: no Spec 353 runtime files touched | +| System panel | Remains deferred; no auth/fixture work added | pass | Final diff review: no system route/auth fixture changes | diff --git a/specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md b/specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md new file mode 100644 index 00000000..8a36eb8c --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md @@ -0,0 +1,49 @@ +# Diagnostic Surface Contracts + +Status: pre-runtime implementation contract. + +## Environment Diagnostics + +| Field | Contract | +|---|---| +| Route | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | +| Source | `App\Filament\Pages\EnvironmentDiagnostics` and `filament.pages.environment-diagnostics` | +| Surface role | Tertiary Evidence / Diagnostics Surface | +| Primary question | What failed in this environment, why does it matter, and what should I check next? | +| Default-visible content | diagnostic outcome, failed condition(s), impact, recommended next check/action | +| Secondary content | individual blocker proof and repair context | +| Raw/support detail | not default-visible unless repo-backed and necessary | +| Actions | existing `Bootstrap owner` and `Merge duplicate access scopes` only when applicable | +| Safety | confirmation, `TENANT_MANAGE`, `UiEnforcement`, server-side service ownership, and audit ownership preserved | +| Scope | current Filament environment tenant and workspace context | + +## Support Diagnostics Modal + +| Field | Contract | +|---|---| +| Source | `filament.modals.support-diagnostic-bundle` rendered from existing action hosts | +| Builder | `App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder` | +| Surface role | Tertiary support diagnostics modal | +| Primary question | What should support check first, and what context is available? | +| Default-visible content | headline, dominant issue, recommended first check, freshness/completeness, redaction note | +| Secondary content | provider connection, operation context, findings, stored reports, environment review, review pack, audit history | +| Raw/support detail | redaction markers and raw-provider/support details stay lower-priority and redacted | +| Actions | repo-backed reference links only; unavailable references render as unavailable, not fake links | +| Safety | existing capability-gated support diagnostics actions, audit, telemetry, and redaction preserved | + +## Provider Connections Context + +- Completed by Spec 353. +- Treat as context/regression only. +- Do not reimplement provider-readiness guidance. + +## Required Permissions Context + +- Completed by Spec 353. +- Treat as context/regression only. +- Required Permissions browser reachability gaps from Spec 368 remain historical context unless a new shared regression is introduced. + +## System Panel Deferred Status + +- `/system` and `/system/ops/runs` browser reachability remain deferred. +- Spec 373 must not fix system-panel auth, fixtures, routes, or productization. diff --git a/specs/373-diagnostic-surface-separation/artifacts/implementation-notes.md b/specs/373-diagnostic-surface-separation/artifacts/implementation-notes.md new file mode 100644 index 00000000..75f9a82e --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/implementation-notes.md @@ -0,0 +1,33 @@ +# Implementation Notes + +Status: complete. + +## Guardrail Decision + +Spec 373 consumes the completed Spec 370 diagnostic contract and preserves completed Spec 353/371/372 work as context. The implementation must not reopen Provider Connections, Required Permissions, customer/auditor pages, or operator backup surfaces unless a confirmed shared-code regression appears. + +## Planned Implementation Shape + +- Environment Diagnostics: added one derived first-viewport diagnostic summary over existing public page state. Helper stayed page-local in `EnvironmentDiagnostics`. +- Support diagnostics: added `recommended_first_check` from existing bundle sections/references. The bundle remains DB-local and redacted. +- Tests first: focused Feature/Livewire and builder unit tests were added before runtime implementation and initially failed on missing hierarchy/copy. +- Browser proof: reused the existing smoke-login route and Spec 352 fixture; no fixture/auth expansion was added. + +## Known Repo Truth Adjustment + +`ManagedEnvironmentDiagnosticsService::tenantHasNoOwners()` currently returns `false`, and existing tests assert workspace roles own role recovery. Spec 373 can still test the missing-owner presentation path by binding a test diagnostic service or setting page state, but it must not change role-recovery ownership unless the active spec is updated first. + +## Out Of Scope + +- ProviderGateway/provider health changes. +- Permission calculation changes. +- `/system` panel auth or browser fixture repairs. +- OperationRun lifecycle changes. +- New persistence, enum/status family, provider framework, queue family, scheduler, storage, package, or env var. + +## Implementation Notes + +- Missing-owner runtime truth remains unchanged. The page can present the state, but `ManagedEnvironmentDiagnosticsService::tenantHasNoOwners()` still returns false by default and existing workspace-role recovery ownership is preserved. +- `bootstrapOwner` and `mergeDuplicateMemberships` kept their existing `Action::make(...)->action(...)`, `->requiresConfirmation()`, `UiEnforcement`, `Capabilities::TENANT_MANAGE`, destructive classification, and service ownership. +- Support diagnostics first-check priority is context-aware: OperationRun entry points recommend operation context first; tenant entry points recommend provider, operation, findings, provider availability, or scoped context based on existing bundle truth. +- No Provider Connections, Required Permissions, System panel, provider gateway, permission calculation, OperationRun lifecycle, migration, package, env var, queue, scheduler, storage, panel provider, global search, or asset registration behavior was changed. diff --git a/specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png b/specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png new file mode 100644 index 0000000000000000000000000000000000000000..17603aa6064eaa8bd3b88936fa1111c9a9dd9312 GIT binary patch literal 57370 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 rOG$n8?%RSHTAJzZAq4fA$`JGPuIrg6bSOksI literal 0 HcmV?d00001 diff --git a/specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png b/specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..5ce06f0738ff98196a2b9f8fe3bd9aa2d7566e6e GIT binary patch literal 76541 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 literal 0 HcmV?d00001 diff --git a/specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md b/specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md new file mode 100644 index 00000000..8163da72 --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md @@ -0,0 +1,51 @@ +# Source Audit Summary + +Status: complete source audit; implementation close-out is recorded in sibling artifacts. + +## Repo Safety + +- Active branch: `373-diagnostic-surface-separation`. +- Base HEAD before implementation: `22214f22 feat(ui): implement customer auditor surface safety pass (#443)`. +- Initial dirty state: untracked active spec package only. +- Runtime edits are expected to stay inside Environment Diagnostics, support diagnostics modal/bundle presentation, focused tests, active spec artifacts, and proportional UI audit artifacts. + +## Source Inputs + +| Source | Availability | Verification Class | Use In Spec 373 | +|---|---|---|---| +| Active `spec.md`, `plan.md`, `tasks.md`, checklist | available | repo-verified | Governing scope and gates | +| Spec 368 audit/findings/scorecard/browser notes | available | browser-verified where screenshots exist | Before-state and source finding | +| Spec 370 surface contract artifacts | available | repo-verified completed spec artifact | Diagnostic page contract | +| Spec 353 provider-readiness artifacts | available | repo-verified completed implementation artifacts | Completed Provider Connections and Required Permissions boundary | +| Spec 371 validation/browser artifacts | available | repo-verified completed implementation artifacts | Summary-first implementation close-out pattern | +| Spec 372 validation/browser artifacts | available | repo-verified completed implementation artifacts | Customer/support raw-detail separation pattern | + +## Spec 368 Findings Used + +| Surface | Evidence | Result For Spec 373 | +|---|---|---| +| Environment Diagnostics | `UI-AUDIT-368-F08`, screenshot `artifacts/screenshots/admin/015-diagnostic-surface-diagnostics-environment-diagnostics.png`, score 3.3 | Primary implementation target | +| Required Permissions | blocked screenshot `016-configuration-surface-settings-required-permissions-error.png` | Completed by Spec 353; fixture gaps are context only | +| System panel | blocked screenshot `031-system-surface-dashboard-system-dashboard-error.png` | Deferred; do not fix system auth or fixtures | + +## Current Runtime Truth + +- `EnvironmentDiagnostics` is a Filament page with no navigation registration and two existing capability-gated repair actions: `bootstrapOwner` and `mergeDuplicateMemberships`. +- Both repair actions use `Action::make(...)->action(...)`, `->requiresConfirmation()`, `UiEnforcement`, `Capabilities::TENANT_MANAGE`, and destructive treatment. +- `ManagedEnvironmentDiagnosticsService::tenantHasNoOwners()` currently returns `false`, so the missing-owner presentation path exists but is not the repo-default repair path; current tests preserve that workspace roles own role recovery. +- `environment-diagnostics.blade.php` currently renders a sparse header, one blocker card per issue, or `All good`. +- `SupportDiagnosticBundleBuilder` already composes redacted support bundles from stored workspace/environment/provider/operation/finding/report/review/audit truth. +- `support-diagnostic-bundle.blade.php` already uses Filament sections, badges, redaction notes, and repo-backed links. The gap is hierarchy and recommended first-check clarity. + +## Completed-Spec Guardrail + +| Related spec | Status signal | Treatment | +|---|---|---| +| Spec 353 Provider Connections / Required Permissions | checked tasks and UI reports | context/regression only | +| Spec 371 operator surfaces | validation and browser proof complete | pattern/context only | +| Spec 372 customer/auditor surfaces | validation and browser proof complete | pattern/context only | +| Spec 370 IA contract | completed preparation artifact | consumed as contract | + +## Scope Decision + +Active implementation is limited to Environment Diagnostics first-viewport guidance, support diagnostics modal hierarchy, focused tests, browser smoke, and Spec 373 artifacts. Provider Connections, Required Permissions, System panel, OperationRun lifecycle, provider health, permission calculation, Graph contracts, migrations, assets, and panel providers are out of scope. diff --git a/specs/373-diagnostic-surface-separation/artifacts/validation-report.md b/specs/373-diagnostic-surface-separation/artifacts/validation-report.md new file mode 100644 index 00000000..cc519cb1 --- /dev/null +++ b/specs/373-diagnostic-surface-separation/artifacts/validation-report.md @@ -0,0 +1,75 @@ +# Validation Report + +Status: complete. + +## Repo Safety + +- Active branch: `373-diagnostic-surface-separation`. +- Base HEAD before implementation: `22214f22 feat(ui): implement customer auditor surface safety pass (#443)`. +- Initial dirty state: untracked active spec package only. +- Spec Readiness Gate: PASS. +- Implementation Scope Gate: PASS. +- Post-Implementation Analysis Gate: PASS after one bounded implementation/test/browser/artifact cycle. +- Merge Readiness Gate: PASS for manual review. + +## Commands + +| Command | Purpose | Result | +|---|---|---| +| `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/TenantDiagnosticsRepairsTest.php tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php` | Initial focused red/green test pack | First run failed on missing new hierarchy; final run passed: 11 tests, 108 assertions | +| `cd apps/platform && ./vendor/bin/sail artisan test tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php` | Builder fallback and unavailable-link behavior | passed: 5 tests, 26 assertions | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=EnvironmentDiagnostics` | Required Environment Diagnostics filter from tasks | completed but returned `No tests found` because Pest descriptions are named around tenant diagnostics repairs | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` | Effective Environment Diagnostics hierarchy and action safety | passed: 6 tests, 49 assertions | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics` | Support diagnostics modal hierarchy, redaction, authorization, audit/telemetry | passed: 39 tests, 228 assertions | +| `cd apps/platform && ./vendor/bin/sail pint --dirty` | Formatting after PHP changes | passed: 0 files changed | +| `git diff --check` | Static diff whitespace check | passed | +| Browser smoke for Environment Diagnostics and support diagnostics modal | Real-browser hierarchy and runtime check | passed with screenshots | + +## Browser Results + +- Environment Diagnostics URL: `http://localhost/admin/workspaces/spec-352-guidance-browser-audit/environments/spec-352-audit-no-urgent/diagnostics`. +- Support diagnostics host URL: `http://localhost/admin/workspaces/spec-352-guidance-browser-audit/environments/spec-352-audit-no-urgent`. +- Fixture: existing `/admin/local/smoke-login` route with `smoke-requester+352@tenantpilot.local`, workspace `spec-352-guidance-browser-audit`, environment `spec-352-audit-no-urgent`. +- Screenshots: + - `specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png` + - `specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png` +- Console: in-app Browser tab reported zero current console errors. Boost browser log feed returned stale unrelated entries dated June 6-8, 2026. + +## Runtime Files Changed + +- `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` +- `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` +- `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` +- `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` +- `apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` +- `apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php` +- `apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php` +- `apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php` + +## Documentation And Artifact Files Changed + +- `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` +- `docs/ui-ux-enterprise-audit/route-inventory.md` +- `specs/373-diagnostic-surface-separation/` artifacts, tasks, and screenshots. + +## Final Dirty State + +- Tracked runtime/docs files are modified. +- `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` is untracked. +- `specs/373-diagnostic-surface-separation/` remains untracked as the active spec package from the start of the session. + +## Deployment Impact + +- No migration. +- No env var. +- No queue or scheduler change. +- No storage change. +- No package change. +- No panel provider registration change. +- No global search change. +- No Filament asset registration change; existing deployment reminder remains `cd apps/platform && php artisan filament:assets` only when registered Filament assets change. + +## Recommended Next Spec + +- No immediate follow-up is required for Spec 373 scope. +- Deferred items remain as already scoped: System panel auth/fixture reachability and any future blocker-state browser fixture should be handled by separate specs if product priority warrants it. diff --git a/specs/373-diagnostic-surface-separation/checklists/requirements.md b/specs/373-diagnostic-surface-separation/checklists/requirements.md new file mode 100644 index 00000000..414ea171 --- /dev/null +++ b/specs/373-diagnostic-surface-separation/checklists/requirements.md @@ -0,0 +1,60 @@ +# Requirements Checklist: Spec 373 - Diagnostic Surface Separation v1 + +**Purpose**: Validate preparation readiness only. This checklist does not certify implementation, runtime tests, or browser proof. +**Created**: 2026-06-12 +**Feature**: `specs/373-diagnostic-surface-separation/spec.md` + +## Candidate Selection Gate + +- [x] CHK001 The selected candidate is directly provided by the user and is also present as Spec 368 Candidate D. +- [x] CHK002 Spec 370 explicitly deferred Diagnostic Surface Separation as a follow-up consumer of the global IA contract. +- [x] CHK003 The prepared slice is narrowed to remaining diagnostic/support surfaces after completed Spec 353 provider-readiness work. +- [x] CHK004 Completed Spec 353, Spec 371, and Spec 372 packages are treated as historical/context artifacts only. +- [x] CHK005 The spec does not rewrite, normalize, or remove completed-spec implementation close-out, validation, browser, or checked-task evidence. +- [x] CHK006 Close alternatives are deferred with rationale: Provider Connections, Required Permissions, System panel auth/fixtures, OperationRun detail, and UI bloat guard automation. + +## Scope And Constitution Alignment + +- [x] CHK007 `spec.md` states problem, user value, smallest viable slice, non-goals, acceptance criteria, assumptions, and risks. +- [x] CHK008 `spec.md` includes the mandatory Spec Candidate Check and approval score. +- [x] CHK009 `spec.md` includes UI Surface Impact, UI/Productization Coverage, Shared Pattern Reuse, OperationRun UX Impact, Provider Boundary, Decision-First, Audience-Aware Disclosure, UI/UX Surface Classification, Operator Surface Contract, Proportionality Review, and Testing/Lane impact sections. +- [x] CHK010 No new persisted entity, table, enum/status family, provider framework, permission engine, or cross-domain UI framework is planned. +- [x] CHK011 Existing destructive Environment Diagnostics repair actions are identified and must preserve confirmation, capability gating, server authorization, and audit ownership. +- [x] CHK012 Filament v5 / Livewire v4 compliance, `apps/platform/bootstrap/providers.php` provider location, global search posture, and asset strategy are explicit. +- [x] CHK013 Provider/platform vocabulary keeps provider-specific terms in provider-owned or technical/support detail. + +## Plan Readiness + +- [x] CHK014 `plan.md` names the concrete repo surfaces likely affected. +- [x] CHK015 `plan.md` states no migration, env var, queue, scheduler, storage, package, panel/provider, or global-search change is expected. +- [x] CHK016 `plan.md` identifies the narrow reuse paths and forbids a generic diagnostic framework. +- [x] CHK017 `plan.md` includes a bounded test strategy with Feature/Livewire and Browser lanes. +- [x] CHK018 `plan.md` includes rollout and review controls for browser fixture gaps, completed-spec boundaries, and UI audit artifacts. + +## Task Readiness + +- [x] CHK019 `tasks.md` is ordered by preparation, tests, implementation, browser proof, validation, and close-out. +- [x] CHK020 Tests are listed before runtime implementation tasks. +- [x] CHK021 Tasks include source-audit, affected-files, diagnostic-contracts, screenshot-index, implementation-notes, browser-verification, diagnostic-safety, and validation artifacts. +- [x] CHK022 Tasks explicitly protect Provider Connections and Required Permissions as completed Spec 353 context. +- [x] CHK023 Tasks include targeted validation commands and browser proof without requiring a full suite by default. +- [x] CHK024 Non-goals are listed as checkable implementation constraints. + +## Open Questions And Risks + +- [x] CHK025 No open question blocks safe implementation preparation. +- [x] CHK026 Browser reachability for Environment Diagnostics and support diagnostics modal is intentionally deferred to implementation verification and has a documented blocked-path rule. +- [x] CHK027 `/system` auth/fixture reachability is explicitly deferred and must not be solved in this spec. +- [x] CHK028 Required Permissions reachability is treated as Spec 353-completed unless a fresh shared-code regression appears. + +## Review Outcome + +- [x] CHK029 Candidate Selection Gate result: pass. +- [x] CHK030 Spec Readiness Gate result: pass for implementation handoff. +- [x] CHK031 Preparation scope only: no application implementation was performed by this package. + +## Notes + +- Review outcome class: `acceptable-special-case`. +- Workflow outcome: `keep`. +- Main caveat: the later implementation must prove browser reachability with existing fixtures and must not broaden into fixture/auth infrastructure if a scoped modal/page is blocked. diff --git a/specs/373-diagnostic-surface-separation/plan.md b/specs/373-diagnostic-surface-separation/plan.md new file mode 100644 index 00000000..b289787f --- /dev/null +++ b/specs/373-diagnostic-surface-separation/plan.md @@ -0,0 +1,266 @@ +# Implementation Plan: Spec 373 - Diagnostic Surface Separation v1 + +**Branch**: `373-diagnostic-surface-separation` | **Date**: 2026-06-12 | **Spec**: `specs/373-diagnostic-surface-separation/spec.md` +**Input**: User-provided Spec 373 draft narrowed by repo truth, completed Spec 353, Spec 368 Candidate D, and Spec 370 contract artifacts. + +## Summary + +Productize the remaining diagnostic/support surfaces after the completed provider-readiness work. The implementation should make Environment Diagnostics and support diagnostics modal content lead with: + +- what failed or whether no action is needed +- why it matters +- what the operator/support user should check next +- which existing provider, permission, operation, evidence, or audit context is related +- where technical details remain available + +Provider Connections and Required Permissions are not primary implementation targets. Spec 353 already implemented their readiness-guidance layer and UI reports, so this spec uses them only as completed context and regression constraints. + +## Technical Context + +**Language/Version**: PHP 8.4.15, Laravel 12.x +**Primary Dependencies**: Filament v5, Livewire v4, Pest v4, Laravel Sail +**Storage**: PostgreSQL; no schema change planned +**Testing**: Pest Feature/Livewire and bounded Browser smoke +**Validation Lanes**: confidence + browser + static diff/format checks +**Target Platform**: Laravel monolith under `apps/platform` +**Project Type**: web application +**Performance Goals**: DB-local rendering; no provider/Graph calls during page or modal render +**Constraints**: no new persistence, no provider/permission/backend logic changes, no `/system` auth/fixture repair +**Scale/Scope**: one environment diagnostic page, existing support diagnostics modal/bundle, completed provider-readiness regression context only + +## Current Repo Truth That Constrains The Slice + +- `EnvironmentDiagnostics` is a small Filament page with two existing destructive repair actions: + - `bootstrapOwner` + - `mergeDuplicateMemberships` +- Those actions already use `Action::make(...)->action(...)`, `->requiresConfirmation()`, `UiEnforcement`, `Capabilities::TENANT_MANAGE`, and destructive UI treatment. Implementation must preserve and verify this posture. +- `environment-diagnostics.blade.php` currently renders a simple header, missing-owner card, duplicate-membership card, or "All good" card. It does not yet compose one guided diagnostic summary. +- `SupportDiagnosticBundleBuilder` already derives redacted support context from existing tenant, provider connection, OperationRun, findings, stored reports, reviews, review packs, and audit logs. +- `support-diagnostic-bundle.blade.php` already shows headline, dominant issue, context, redaction, notes, and sections. The intended change is hierarchy/copy/order, not a data model change. +- Spec 353 already completed Provider Connections and Required Permissions readiness guidance: + - `docs/ui-ux-enterprise-audit/page-reports/ui-009-provider-connections.md` + - `docs/ui-ux-enterprise-audit/page-reports/ui-077-required-permissions.md` + - `specs/353-provider-connections-resolution-guidance-v1/tasks.md` has checked tasks and browser-oriented proof. +- `docs/ui-ux-enterprise-audit/route-inventory.md` already has UI-012 Environment Diagnostics, UI-072 Provider Connections, and UI-077 Required Permissions entries. + +## Domain / Model Implications + +- No new model, table, enum, status family, provider health engine, permission engine, or persisted diagnostic artifact is planned. +- Environment diagnostic cases remain derived from existing booleans and service results: + - missing owner + - duplicate memberships for current user + - no known issue +- Support diagnostics remains derived from `SupportDiagnosticBundleBuilder` output and existing related records. +- If implementation needs a helper, prefer a narrow page-local derived summary helper that replaces duplicated Blade conditionals. Do not introduce a generic diagnostic resolver/framework. + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: existing Environment Diagnostics page and existing support diagnostics modal/bundle. +- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: + - `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` + - `openSupportDiagnostics` modal on existing authorized host surfaces + - completed Provider Connections/Required Permissions only if a shared change creates regression risk +- **No-impact class, if applicable**: N/A; existing UI surfaces change. +- **Native vs custom classification summary**: Filament page/action/modal with Blade content; preserve native action primitives. +- **Shared-family relevance**: diagnostics summary, support diagnostics, redaction, contextual help, OperationRun/reference links. +- **State layers in scope**: page derived state and modal/bundle presentation state. +- **Audience modes in scope**: operator-MSP and support-platform. +- **Decision/diagnostic/raw hierarchy plan**: guidance first, technical/support references second, raw/provider/debug data third or unavailable. +- **Raw/support gating plan**: support diagnostics remains capability-gated and default-redacted; technical details remain lower-priority or collapsed where added. +- **One-primary-action / duplicate-truth control**: one top diagnostic summary owns the current blocker/no-action state; lower cards/sections add proof and action detail. +- **Handling modes by drift class or surface**: + - Environment Diagnostics: implementation target. + - Support diagnostics modal: implementation target if reachable with existing actions/fixtures. + - Provider Connections and Required Permissions: report-only/regression context because Spec 353 completed them. + - System panel: deferred, not implementation target. +- **Repository-signal treatment**: completed-spec guardrail is mandatory for Spec 353, 371, and 372. +- **Special surface test profiles**: `standard-native-filament` for Environment Diagnostics actions; `shared-detail-family` for support diagnostics modal. +- **Required tests or manual smoke**: Feature/Livewire tests for page/modal hierarchy; browser smoke for first viewport/modal reachability. +- **Exception path and spread control**: if support diagnostics modal cannot be reached with existing fixture, record blocked status and stop rather than building fixture/auth infrastructure. +- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage. +- **UI/Productization coverage decision**: coverage artifacts should be updated only for UI-012 or support diagnostics if implementation materially changes registry status/evidence. Do not churn UI-009/UI-077 unless a regression is found. +- **Coverage artifacts to update**: + - `docs/ui-ux-enterprise-audit/route-inventory.md` if UI-012 screenshot/report references change + - `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` if created or refreshed + - `docs/ui-ux-enterprise-audit/unresolved-pages.md` only for documented blocked scope +- **Screenshot or page-report need**: yes for Environment Diagnostics; yes for support diagnostics modal if reachable. + +## Shared Pattern And System Fit + +- **Cross-cutting feature marker**: yes. +- **Systems touched**: + - `App\Filament\Pages\EnvironmentDiagnostics` + - `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` + - `App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder` + - `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` + - existing support diagnostics action hosts/tests + - `ManagedEnvironmentLinks` / `OperationRunLinks` only for existing related links +- **Shared abstractions reused**: + - `UiEnforcement` + - `Capabilities` + - `SupportDiagnosticBundleBuilder` + - `RedactionIntegrity` + - `ContextualHelpResolver` + - `OperationRunLinks` + - `ManagedEnvironmentLinks` + - `ProviderReasonTranslator` +- **New abstraction introduced? why?**: none planned. A page-local helper is allowed only if it simplifies Environment Diagnostics view state without becoming a framework. +- **Why the existing abstraction was sufficient or insufficient**: Existing services expose truth and authorization. The missing piece is presentation hierarchy. +- **Bounded deviation / spread control**: no completed Spec 353 code path should be reused as a generic diagnostic engine; use it as pattern/context only. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no start/completion behavior. +- **Central contract reused**: existing `OperationRunLinks` for references only. +- **Delegated UX behaviors**: N/A. +- **Surface-owned behavior kept local**: deciding when to show an existing related operation link as diagnostic context. +- **Queued DB-notification policy**: N/A. +- **Terminal notification path**: N/A. +- **Exception path**: none. + +## Provider Boundary And Portability Fit + +- **Shared provider/platform boundary touched?**: yes, presentation only. +- **Provider-owned seams**: provider connection reason codes, required permissions, Microsoft Graph permission names, provider target identifiers. +- **Platform-core seams**: environment diagnostics, support diagnostics summary, related operation/evidence links, provider-neutral top-level copy. +- **Neutral platform terms / contracts preserved**: provider connection, required permissions, managed environment, operation, evidence, diagnostics. +- **Retained provider-specific semantics and why**: provider-specific permission/reason detail may appear in lower technical/support sections because operators/support users need exact provider context when troubleshooting. +- **Bounded extraction or follow-up path**: no extraction in this spec; deeper provider-health/onboarding redesign remains follow-up. + +## Constitution Check + +*GATE: Must pass before implementation. Re-check after implementation if requirements change.* + +- Inventory-first / snapshots-second: N/A; no inventory/snapshot truth changes. +- Read/write separation: Environment Diagnostics has existing repair actions; preserve confirmation, capability gating, authorization, and audit ownership. No new write action. +- Graph contract path: no Graph calls added; page/modal render must remain DB-local. +- Deterministic capabilities: existing capability checks remain authoritative. +- RBAC-UX: workspace/environment entitlement and support diagnostics capability remain server-side truth; non-member route/context access remains deny-as-not-found. +- Workspace/Tenant isolation: environment route and modal context must remain workspace/environment scoped. +- Run observability: no new OperationRun type/start behavior; existing operation links remain links only. +- OperationRun start UX: N/A beyond existing links. +- Data minimization: no secrets, tokens, raw provider payloads, or unrestricted logs in default-visible content. +- Test governance: confidence + browser lanes are explicit and bounded; no hidden heavy lane default. +- Proportionality: no new persisted truth or framework. +- No premature abstraction: page-local helper only if strictly needed; no diagnostic framework. +- Persisted truth: none added. +- Behavioral state: no new status/reason family. +- UI semantics: direct mapping from existing diagnostic truth to UI. +- Shared pattern first: reuse `SupportDiagnosticBundleBuilder`, redaction, contextual help, OperationRun links, and Filament action helpers. +- Provider boundary: provider-specific values stay in provider-owned or technical/support detail. +- UI-COV-001: reachable UI surface impact is declared; coverage artifact handling is proportional. +- DECIDE-001 / DECIDE-AUD-001: diagnostic/support surfaces are tertiary; default-visible content is guided and raw detail remains secondary. +- Filament v5 / Livewire v4: required; no legacy APIs. +- Panel provider registration: unchanged in `apps/platform/bootstrap/providers.php`. +- Global search: no resource global-search behavior changes; `ProviderConnectionResource` remains disabled as completed Spec 353 context. +- Destructive actions: existing Environment Diagnostics repair actions stay `->action(...)` + `->requiresConfirmation()` + capability-gated + server-authorized. +- Asset strategy: no new asset registration planned; no `filament:assets` deployment change expected. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: + - Environment Diagnostics: Feature/Livewire + Browser. + - Support diagnostics modal: Feature/Livewire + Browser if reachable. + - Completed provider surfaces: regression/source audit only unless shared changes touch them. +- **Affected validation lanes**: confidence and browser. +- **Why this lane mix is the narrowest sufficient proof**: DOM/render behavior and modal action state are provable with Feature/Livewire tests; first-viewport signal hierarchy needs browser proof because the source finding is visual/productization-oriented. +- **Narrowest proving commands**: + - `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=EnvironmentDiagnostics` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics` + - bounded Browser smoke for Spec 373 surfaces + - `git diff --check` + - `cd apps/platform && ./vendor/bin/sail pint --dirty` if PHP changes +- **Fixture / helper / factory / seed / context cost risks**: keep support/browser fixture reuse explicit; do not widen shared smoke-login setup. +- **Expensive defaults or shared helper growth introduced?**: no. +- **Heavy-family additions, promotions, or visibility changes**: one bounded browser smoke only. +- **Surface-class relief / special coverage rule**: standard-native-filament for Environment Diagnostics action safety; shared-detail-family for support diagnostics modal. +- **Closing validation and reviewer handoff**: reviewers should inspect source audit, affected files, browser screenshots/report, diagnostic safety checklist, and completed-spec guardrail notes. +- **Budget / baseline / trend follow-up**: none expected. +- **Review-stop questions**: Does the implementation introduce a framework, persistence, or hidden fixture cost? Does it reopen Spec 353? Does it weaken destructive-action safety? +- **Escalation path**: document-in-feature for bounded fixture limitations; follow-up-spec for system-panel reachability or UI bloat automation. +- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage. +- **Why no dedicated follow-up spec is needed**: Spec 373 is the dedicated diagnostic surface productization slice. Only UI bloat guard and fixture coverage remain separate follow-ups. + +## Project Structure + +### Documentation (this feature) + +```text +specs/373-diagnostic-surface-separation/ +├── spec.md +├── plan.md +├── tasks.md +├── checklists/ +│ └── requirements.md +└── artifacts/ + ├── source-audit-summary.md + ├── affected-files.md + ├── diagnostic-surface-contracts.md + ├── before-after-screenshot-index.md + ├── implementation-notes.md + ├── browser-verification-report.md + ├── diagnostic-safety-checklist.md + ├── validation-report.md + └── screenshots/ +``` + +### Source Code (repository root) + +Likely affected runtime surfaces for later implementation: + +```text +apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php +apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php +apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php +apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php +apps/platform/tests/Feature/TenantRBAC/TenantDiagnosticsAccessTest.php +apps/platform/tests/Feature/SupportDiagnostics/ +apps/platform/tests/Browser/ +docs/ui-ux-enterprise-audit/route-inventory.md +docs/ui-ux-enterprise-audit/page-reports/ +``` + +**Structure Decision**: Single Laravel web application. Implementation should stay in existing page/view/support-diagnostics/test locations and spec-local artifacts. Do not create new base folders. + +## Complexity Tracking + +No constitutional violation is planned. + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|---|---|---| +| N/A | N/A | N/A | + +## Proportionality Review + +- **Current operator problem**: diagnostics/support surfaces need guided first-viewport troubleshooting rather than sparse technical cards or raw references first. +- **Existing structure is insufficient because**: the truth exists but the current presentation does not organize it into outcome, impact, and next check. +- **Narrowest correct implementation**: reorder/copy existing page/modal output and add focused tests/browser proof. +- **Ownership cost created**: focused tests, screenshots, and spec-local artifacts only. +- **Alternative intentionally rejected**: generic diagnostic framework or provider-readiness engine reuse beyond pattern/context. +- **Release truth**: current-release productization of existing surfaces. + +## Implementation Phases + +### Phase 0 - Repo Truth And Source Audit + +Re-read active spec artifacts, Spec 368/370/371/372/353 context, current Environment Diagnostics/support diagnostics code, route inventory, and relevant tests. Create source audit and surface contract artifacts before runtime edits. + +### Phase 1 - Tests First + +Add or update focused tests for Environment Diagnostics states and support diagnostics modal hierarchy. Preserve existing authorization, telemetry, audit, and redaction tests. + +### Phase 2 - Environment Diagnostics Guidance + +Recompose the Environment Diagnostics first viewport around one diagnostic summary. Preserve existing repair actions and avoid new backend logic. + +### Phase 3 - Support Diagnostics Modal Guidance + +Adjust modal/bundle presentation so the dominant issue, redaction/completeness, and first check precede lower reference sections. Keep raw/support detail secondary and redacted. + +### Phase 4 - Browser Smoke And Artifacts + +Use existing browser harness to capture reachable scoped pages/modals. Document blocked surfaces honestly. Populate spec-local artifacts and any proportional UI audit registry updates. + +### Phase 5 - Validation And Close-Out + +Run targeted tests, browser smoke, `git diff --check`, and Pint if PHP changed. The approval/prep report must state when no app implementation happened during that prep step; the later implementation report must include the standard Filament v5/Livewire v4, provider location, global search, destructive action, asset, test, and deployment impact summary. diff --git a/specs/373-diagnostic-surface-separation/spec.md b/specs/373-diagnostic-surface-separation/spec.md new file mode 100644 index 00000000..f43e6762 --- /dev/null +++ b/specs/373-diagnostic-surface-separation/spec.md @@ -0,0 +1,328 @@ +# Feature Specification: Spec 373 - Diagnostic Surface Separation v1 + +**Feature Branch**: `373-diagnostic-surface-separation` +**Created**: 2026-06-12 +**Status**: Draft +**Input**: User-provided Spec 373 draft, Spec 368 Candidate D, Spec 370 Global Surface Information Architecture Contract, and completed Spec 371/372 artifacts. + +## Candidate Selection Summary + +- **Selected candidate**: Diagnostic Surface Separation v1. +- **Source**: User-provided Spec 373 draft and `specs/368-platform-ui-signal-to-noise-browser-audit/spec-candidates.md` Candidate D. +- **Why selected**: Spec 368 identified two remaining diagnostic/configuration gaps after later productization work: Environment Diagnostics needs stronger guidance before raw diagnostic facts, and the blocked/system diagnostic surfaces need explicit handling instead of being silently treated as productized. Spec 370 explicitly deferred Diagnostic Surface Separation as a follow-up once the global surface contract existed. +- **Roadmap relationship**: Supports the roadmap's Product Scalability and Self-Service Foundation lane, especially supportability, provider readiness, guided diagnostics, and customer-safe separation of diagnostic/support detail. +- **Smallest viable slice**: Productize the existing Environment Diagnostics page and support-diagnostics modal/bundle default hierarchy so they answer what failed, why it matters, and what to check next before technical detail. Provider Connections and Required Permissions are regression/context surfaces only because Spec 353 already implemented their readiness-guidance slice. +- **Deferred close alternatives**: + - Provider Connections readiness productization: completed by Spec 353; use as regression context only. + - Required Permissions readiness productization: completed by Spec 353; use as regression context only. + - System panel auth/fixture reachability: deferred; do not fix `/system` auth or fixture coverage in this spec. + - OperationRun detail metadata/proof separation: covered by earlier OperationRun specs; do not reopen in this slice. + - UI Bloat Regression Guard v1: recommended follow-up after this consumer spec. +- **Completed-spec guardrail result**: + - Spec 353 has implementation status, checked implementation tasks, browser-oriented coverage, and UI reports for Provider Connections and Required Permissions. It is treated as completed historical context and is not rewritten. + - Specs 371 and 372 have implementation/browser validation artifacts and are treated as completed context only. + - Spec 370 is the completed preparation contract consumed by this package. + - Spec 368 is an audit/source artifact and is not modified. + +## Spec Candidate Check *(mandatory - SPEC-GATE-001)* + +- **Problem**: Diagnostic and support surfaces still risk starting with sparse technical facts, repair-action labels, raw context, or generic "all good" language instead of clear diagnostic guidance. +- **Today's failure**: On Environment Diagnostics, operators see a small technical page that says configuration issues exist, but it does not consistently lead with the failed condition, impact, recommended next check, and related provider/permission/operation context. Support diagnostics has good redaction and context foundations, but its default modal still needs a stronger "what to check first" hierarchy. +- **User-visible improvement**: Operators and support users can open diagnostic surfaces and understand the primary blocker or no-action state in the first viewport, while raw/provider/debug detail remains available only after the guided summary. +- **Smallest enterprise-capable version**: Update only existing diagnostic/support UI presentation over existing stored truth. Preserve current RBAC, destructive repair actions, provider/permission logic, OperationRun links, and support-diagnostics redaction. Add focused tests, browser smoke, and spec-local implementation artifacts during the later implementation step. +- **Explicit non-goals**: No ProviderGateway changes, no permission calculation changes, no Provider Connections or Required Permissions reimplementation, no `/system` auth/fixture repair, no OperationRun logic changes, no new provider connectors, no migrations, no new data models, no new provider-health engine, no new support/PSA workflow, no AI/automation feature, and no broad UI framework. +- **Permanent complexity imported**: Focused feature/browser tests, spec-local audit artifacts, and possibly page-local derived view data for Environment Diagnostics only if existing direct booleans are insufficient. No new persisted truth, enum/status family, provider framework, cross-domain UI taxonomy, or queue family is expected. +- **Why now**: Specs 370, 371, 372, and 353 have already handled the global contract, operator backup surfaces, customer/auditor surfaces, and provider-readiness destinations. The remaining diagnostic surface gap is now narrow enough to prepare without reopening completed work. +- **Why not local**: A copy-only change would not prove diagnostic hierarchy, repair-action safety, support/raw separation, browser reachability, and completed-spec boundaries together. A broad diagnostic framework would overbuild. The narrow correct slice is a bounded diagnostic UI separation pass. +- **Approval class**: Workflow Compression. +- **Red flags triggered**: Multiple diagnostic/support surfaces and a "surface separation" theme. Defense: scope is narrowed to existing surfaces, no new runtime framework or persistence is introduced, and completed provider/customer/operator specs are explicitly excluded from rework. +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12** +- **Decision**: approve. + +## Spec Scope Fields *(mandatory)* + +- **Scope**: environment-bound diagnostics plus workspace/environment support-diagnostic modal context. +- **Primary Routes / Surfaces**: + - `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` + - `openSupportDiagnostics` action/modal on existing environment and operation surfaces when already available + - Provider Connections and Required Permissions only as completed Spec 353 regression/context surfaces +- **Data Ownership**: No data ownership change. `ManagedEnvironment` remains the environment context. Diagnostics continue to read existing `ManagedEnvironmentDiagnosticsService`, `SupportDiagnosticBundleBuilder`, `ProviderConnection`, `OperationRun`, `Finding`, `StoredReport`, `EnvironmentReview`, `ReviewPack`, and `AuditLog` truth. +- **RBAC**: + - Environment Diagnostics remains workspace + environment scoped. + - Non-members or non-entitled actors remain deny-as-not-found where route/context ownership applies. + - Existing repair actions remain capability-gated with `TENANT_MANAGE`, destructive styling, confirmation, and server-side authorization through current services. + - Support diagnostics remains capability-gated through existing support-diagnostics policies/actions. + +For canonical or mixed context surfaces: + +- **Default filter behavior when tenant-context is active**: N/A. Environment Diagnostics is route-environment scoped; support diagnostics is opened from an already authorized environment or operation context. +- **Explicit entitlement checks preventing cross-tenant leakage**: Existing route model binding, workspace membership, environment entitlement, and support diagnostics authorization must remain authoritative. This spec must not add query-string shortcuts or hidden remembered-environment scope. + +## UI Surface Impact *(mandatory - UI-COV-001)* + +Does this spec add, remove, rename, or materially change any reachable UI surface? + +- [ ] No UI surface impact +- [x] Existing page changed +- [ ] New page/route added +- [ ] Navigation changed +- [ ] Filament panel/provider surface changed +- [x] Existing modal/action presentation changed +- [ ] New table/form/state added +- [ ] Customer-facing surface changed +- [ ] Dangerous action changed +- [x] Status/evidence/review presentation changed +- [x] Workspace/environment context presentation changed + +## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact")* + +| Route/page/surface | Page archetype | Design depth | Repo-truth level | Existing pattern reused | Screenshot/page audit need | Customer/dangerous review | +|---|---|---|---|---|---|---| +| Environment Diagnostics (`UI-012`) | Support / Diagnostics, Provider / Integration | Domain Pattern Surface | repo-verified + Spec 368 browser-verified before screenshot | Spec 370 diagnostic contract, Spec 353 readiness guidance, current Filament page/action patterns | after screenshot required; update `docs/ui-ux-enterprise-audit/route-inventory.md` and create/update page report only if implementation changes registry status | internal/support only; existing destructive repair actions must retain confirmation, authorization, and audit ownership | +| Support diagnostics modal/bundle | Support / Diagnostics modal | Domain Pattern Surface | repo-verified action/modal and feature tests | `SupportDiagnosticBundleBuilder`, contextual help, redaction integrity, OperationRun links | browser/modal screenshot required if reachable in existing fixture; otherwise document blocked | support-only; raw/support data remains redacted and capability-gated | +| Provider Connections (`UI-009`) | Provider / Integration | Strategic Surface | completed Spec 353 context | Spec 353 readiness guidance | no new screenshot required unless shared changes regress it | preserve completed action safety | +| Required Permissions (`UI-077`) | Provider / Integration | Domain Pattern Surface | completed Spec 353 context | Spec 353 readiness guidance | no new screenshot required unless shared changes regress it | no new permission/consent behavior | + +Coverage files to review during implementation: + +- `docs/ui-ux-enterprise-audit/route-inventory.md` +- `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` only if classification/counts change +- `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` if absent or stale after implementation +- `docs/ui-ux-enterprise-audit/unresolved-pages.md` only if a scoped route/modal remains unreachable + +## Cross-Cutting / Shared Pattern Reuse + +- **Cross-cutting feature?**: yes. +- **Interaction class(es)**: diagnostics summary, support diagnostics modal, status messaging, action links, technical-details disclosure, provider/permission/operation/evidence links. +- **Systems touched**: `EnvironmentDiagnostics`, `environment-diagnostics.blade.php`, `SupportDiagnosticBundleBuilder`, `support-diagnostic-bundle.blade.php`, `ManagedEnvironmentLinks`, `OperationRunLinks`, `ProviderReasonTranslator`, `ContextualHelpResolver`, current support-diagnostics actions/tests, and UI audit artifacts. +- **Existing pattern(s) to extend**: Spec 370 diagnostic contract, Spec 353 provider-readiness hierarchy, existing support diagnostics redaction/contextual-help pattern, current Filament action confirmation/RBAC helpers. +- **Shared contract / presenter / builder / renderer to reuse**: `SupportDiagnosticBundleBuilder`, `RedactionIntegrity`, `ContextualHelpResolver`, `ManagedEnvironmentLinks`, `OperationRunLinks`, and existing action-surface declarations before adding any helper. +- **Why the existing shared path is sufficient or insufficient**: Existing support diagnostics and provider-readiness paths already provide most needed truth. The gap is Environment Diagnostics first-viewport hierarchy and modal ordering, not a missing data model or resolver. +- **Allowed deviation and why**: A page-local Environment Diagnostics view model is allowed only if it replaces duplicated Blade conditionals and stays derived from existing booleans. No generic diagnostic framework is allowed. +- **Consistency impact**: "what failed", "impact", "recommended next check", "related context", and "technical details" language must align across diagnostics and support modal surfaces without creating a parallel provider readiness vocabulary. +- **Review focus**: Confirm no completed Spec 353 surface is reopened, no raw provider payloads/secrets become default-visible, and no local action bypasses existing RBAC/audit patterns. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: No new OperationRun start/completion behavior. Existing support diagnostics may link to OperationRun proof. +- **Shared OperationRun UX contract/layer reused**: Existing `OperationRunLinks` and current OperationRun detail/navigation paths only. +- **Delegated start/completion UX behaviors**: N/A. +- **Local surface-owned behavior that remains**: Displaying a related operation link as diagnostic context when the existing bundle or page already has a repo-backed operation. +- **Queued DB-notification policy**: unchanged. +- **Terminal notification path**: unchanged. +- **Exception required?**: none. + +## Provider Boundary / Platform Core Check + +- **Shared provider/platform boundary touched?**: yes, presentation only. +- **Boundary classification**: mixed. Environment Diagnostics is platform/environment owned; provider and Microsoft permission facts remain provider-owned detail. +- **Seams affected**: provider connection references, permission references, repair-action copy, support diagnostic dominant issue copy, technical details disclosure. +- **Neutral platform terms preserved or introduced**: provider connection, required permissions, operation, evidence, diagnostics, support context, managed environment. +- **Provider-specific semantics retained and why**: Microsoft Graph permission names, provider error reason codes, and external tenant/provider identifiers may remain in technical details or required-permission destinations because they are real provider-owned diagnostics. +- **Why this does not deepen provider coupling accidentally**: Primary copy uses provider-neutral "provider connection", "required access", "permission", and "operation" terms. Provider-specific values are not promoted to platform-core truth or new status families. +- **Follow-up path**: deeper provider-health engine or provider-specific onboarding redesign remains a follow-up spec, not part of Spec 373. + +## UI / Surface Guardrail Impact + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| Environment Diagnostics first viewport | yes | Filament page + Blade content | diagnostics summary, destructive repair-action context | page/view derived state | no | Existing route only | +| Support diagnostics modal ordering | yes | Filament action modal + Blade content | support diagnostics, redaction, contextual help | modal/view derived state | no | Existing actions only | +| Provider Connections / Required Permissions | no primary change planned | Native Filament/resource/page | completed readiness guidance | regression context only | no | Spec 353 completed; do not rework | + +## Decision-First Surface Role + +| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction | +|---|---|---|---|---|---|---|---| +| Environment Diagnostics | Tertiary Evidence / Diagnostics Surface | Diagnose missing owner, duplicate membership, provider/permission link, or no-action state after an environment issue is suspected | diagnostic outcome, failed condition, impact, recommended next check/action, related provider/permission/operation link if repo-backed | repair context, raw identifiers, audit/support detail, technical details | Tertiary because it supports troubleshooting after a primary operator surface points to a problem | follows contextual environment troubleshooting | removes translation from "All good" or isolated repair cards | +| Support diagnostics modal | Tertiary Evidence / Diagnostics Surface | Support user decides what context to inspect first before opening raw references | dominant issue, likely area when repo-backed, impact, recommended next check, redaction/completeness note | references, raw/support notes, redaction markers, audit history | Tertiary because it explains selected tenant/run context for support | follows support investigation workflow | reduces raw-section scanning before first support action | + +## Audience-Aware Disclosure + +| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention | +|---|---|---|---|---|---|---|---| +| Environment Diagnostics | operator-MSP, support-platform | current diagnostic outcome, failed condition, impact, recommended next check | missing owner, duplicate membership, provider/permission/operation links, existing repair affordances | raw IDs and detailed repair context only if added under Technical Details | bootstrap owner, merge duplicate scopes, open required permissions, open provider connection, or no action required depending on state | no raw provider payloads/secrets; repair actions remain capability-gated | one top diagnostic summary owns state; cards below add proof/action only | +| Support diagnostics modal | support-platform, operator-MSP with capability | headline, dominant issue, freshness/completeness/redaction note, recommended first check | provider connection, operation context, findings, reports, review pack, audit sections | references and redaction markers in lower sections | open the most relevant existing reference when available | modal/action remains capability-gated; redaction stays default | summary states dominant issue once; sections add evidence instead of repeating it | + +## UI/UX Surface Classification + +| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Environment Diagnostics | Utility / Diagnostic | Singleton diagnostic page | resolve the visible diagnostic blocker or open related provider/permission context | same page; contextual action/link | forbidden | secondary links/details below summary | existing destructive repair actions remain header actions with confirmation/capability gating | N/A | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | workspace + managed environment | Environment diagnostics | what failed, impact, next check | diagnostic singleton exemption remains valid | +| Support diagnostics modal | Utility / Support | Support diagnostic modal | inspect the first related context or copy support-safe summary | modal content plus existing reference links | forbidden | references inside lower sections | none added | source page/action | source page/action | workspace + environment/run context | Support diagnostics | dominant issue, freshness, redaction, first check | support modal exemption remains valid | + +## Operator Surface Contract + +| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions | +|---|---|---|---|---|---|---|---|---|---|---| +| Environment Diagnostics | TenantPilot operator or support user | Decide which environment diagnostic blocker to resolve or whether no action is required | Tertiary diagnostics | What failed in this environment, why does it matter, and what should I check next? | outcome, failed condition, impact, next check, safe related links | raw IDs, internal repair context, exact timestamps if later added | diagnostic readiness, membership consistency, supportability | existing TenantPilot-only membership repair actions | Bootstrap owner, Merge duplicate access scopes, or related navigation depending on state | existing repair actions only, confirmation and capability gated | +| Support diagnostics modal | Support user | Decide what context to inspect first in a redacted support bundle | Tertiary support diagnostics | What should support check first, and what context is available? | dominant issue, freshness, completeness, redaction, first related context | detailed references, audit history, redaction markers | availability, freshness, completeness, redaction mode | read-only support inspection | open existing related context if available | none | + +## Proportionality Review + +- **New source of truth?**: no. +- **New persisted entity/table/artifact?**: no domain persistence. Spec-local implementation artifacts are expected during implementation only. +- **New abstraction?**: no generic abstraction planned. A page-local derived summary helper is allowed only if it replaces duplicated view conditionals. +- **New enum/state/reason family?**: no. +- **New cross-domain UI framework/taxonomy?**: no. +- **Current operator problem**: The current diagnostic page and support modal do not consistently lead with the troubleshooting answer before technical/support detail. +- **Existing structure is insufficient because**: Existing booleans and bundle arrays provide facts, but the presentation does not always compose them into one guided diagnostic case with impact and next action. +- **Narrowest correct implementation**: Reorder and clarify the existing Environment Diagnostics view and support diagnostics modal over existing truth, with focused tests/browser evidence. +- **Ownership cost**: Focused tests, browser smoke, and spec-local artifacts. No new service family, persistence, or provider engine. +- **Alternative intentionally rejected**: Reusing Spec 353's provider-readiness adapter as a generic diagnostic engine, or building a new diagnostic framework. Both are broader than the current release needs. +- **Release truth**: current-release UI productization over existing diagnostic/support truth. + +### Compatibility posture + +This feature assumes the repo's pre-production posture. It introduces no migration, compatibility shim, legacy alias, or backfill. + +## Testing / Lane / Runtime Impact + +- **Test purpose / classification**: Feature/Livewire for Environment Diagnostics and support diagnostics modal rendering; Unit/Feature only where derived summary helpers are added; Browser for first-viewport hierarchy and modal reachability. +- **Validation lane(s)**: confidence + browser + static diff/format checks. +- **Why this classification and these lanes are sufficient**: The change is UI hierarchy over existing DB-backed truth. Feature/Livewire tests prove rendered state and action safety; Browser smoke proves visual hierarchy, collapsed/support detail, and no console/runtime failures. +- **New or expanded test families**: one bounded Spec 373 feature/browser family only if no existing diagnostic tests can be extended cleanly. +- **Fixture / helper cost impact**: Reuse existing workspace/environment/member/provider/support diagnostics factories and smoke-login fixture. Do not widen global browser setup. +- **Heavy-family visibility / justification**: Browser coverage is explicit because the source issue is browser-verified UI signal-to-noise. +- **Special surface test profile**: shared-detail-family for support modal; standard-native-filament for Environment Diagnostics page action safety. +- **Standard-native relief or required special coverage**: Environment Diagnostics uses existing Filament page/action patterns; special proof is only first-viewport hierarchy and repair-action preservation. +- **Reviewer handoff**: Verify completed Spec 353 boundaries, no raw/default provider payload exposure, support diagnostics redaction, destructive action confirmation/RBAC/audit, and screenshot/report artifacts. +- **Budget / baseline / trend impact**: none expected beyond one bounded browser smoke. +- **Escalation needed**: document-in-feature for contained fixture gaps; follow-up-spec for `/system` fixture/auth, durable bloat guard, or broad support-diagnostics workflow changes. +- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage. +- **Planned validation commands**: + - `git diff --check` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=EnvironmentDiagnostics` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics` + - `cd apps/platform && ./vendor/bin/sail pint --dirty` if PHP files change + - bounded browser smoke for Environment Diagnostics and support diagnostics modal if UI changes are implemented + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Diagnose Environment Blockers First (Priority: P1) + +An operator opens Environment Diagnostics and immediately sees the current diagnostic outcome, what failed, why it matters, and what to check or repair next. + +**Why this priority**: Environment Diagnostics is the remaining browser-verified diagnostic page from Spec 368 that still needs stronger guidance first. + +**Independent Test**: Render the page with missing owner, duplicate memberships, both blockers, and no blocker states. Each state must show one top diagnostic summary before technical/support detail, the both-blocker state must expose one dominant next action with any second repair/context path demoted, and existing repair actions must remain preserved. + +**Acceptance Scenarios**: + +1. **Given** an environment has no owner, **When** an authorized operator opens Environment Diagnostics, **Then** the first viewport states the missing-owner blocker, impact, and next check/action before lower detail. +2. **Given** the current user has duplicate membership rows, **When** the page renders, **Then** duplicate membership is framed as a supportability/access-scope issue with the existing merge action preserved. +3. **Given** no known diagnostic issue exists, **When** the page renders, **Then** one calm no-action summary appears without zero-card spam or false-positive calmness. + +### User Story 2 - Open Support Diagnostics With Guided First Check (Priority: P2) + +A support user opens support diagnostics from an existing tenant or OperationRun surface and sees the dominant issue, likely area when repo-backed, redaction state, and recommended first context before raw references. + +**Why this priority**: Support diagnostics is repo-real and audit/redaction-sensitive; it should help support users start with the right context without leaking raw data or making raw sections the default path. + +**Independent Test**: Mount the existing support diagnostics action/modal for tenant and OperationRun contexts. Verify the modal summary precedes reference sections, redaction note is visible, and raw/support markers remain lower-priority. + +**Acceptance Scenarios**: + +1. **Given** a tenant has a provider connection blocker, **When** support diagnostics opens, **Then** the modal leads with the provider/support issue and a relevant first context. +2. **Given** an OperationRun failed or was blocked, **When** support diagnostics opens, **Then** the modal leads with the run issue and keeps audit/references secondary. +3. **Given** an entitled user lacks support diagnostics capability, **When** the action is rendered or invoked, **Then** existing disabled/403 behavior remains unchanged. + +### User Story 3 - Preserve Completed Provider Readiness Surfaces (Priority: P3) + +An implementer uses Provider Connections and Required Permissions as completed Spec 353 regression/context surfaces and does not rework them unless a shared helper change creates a confirmed regression. + +**Why this priority**: The user draft named these surfaces, but repo truth says Spec 353 already completed their provider-readiness guidance. Reopening them would violate the completed-spec guardrail. + +**Independent Test**: Review the final diff and browser/source audit artifacts. Provider Connections and Required Permissions should appear only as context/regression checks unless a bounded shared-change side effect is documented. + +**Acceptance Scenarios**: + +1. **Given** Spec 353 artifacts exist, **When** Spec 373 implementation starts, **Then** Provider Connections and Required Permissions are treated as completed context. +2. **Given** implementation touches a shared diagnostic/support component used by Spec 353 surfaces, **When** validation runs, **Then** targeted regression proof is recorded or the change is narrowed. + +### Edge Cases + +- Both Environment Diagnostics blockers can be true at once; the summary must not hide either blocker, must expose one dominant next action for the first viewport, and must demote the second repair/context path instead of creating two competing primary actions. +- The support diagnostics bundle may have no provider connection, no OperationRun, or no findings; the modal must show an honest completeness/no-action statement instead of implying unavailable proof exists. +- Required Permissions and System panel may be unreachable in a given browser fixture; document blocked status and do not fix auth/routing inside this spec. +- Provider-specific reason codes may not translate; show neutral "recommended next check" language instead of unsupported likely cause. +- Technical terms may be necessary in support sections, but not as the first answer for operator diagnostics. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-373-001**: Environment Diagnostics MUST show a single first-viewport diagnostic summary before individual blocker cards or technical detail. +- **FR-373-002**: The Environment Diagnostics summary MUST state the current diagnostic outcome, failed condition(s), operator impact, and recommended next check/action for each repo-backed state while exposing only one dominant next action in the first viewport. +- **FR-373-003**: Missing-owner and duplicate-membership states MUST remain visible and actionable; the implementation MUST NOT hide real blockers behind calm or healthy copy. +- **FR-373-004**: Existing Environment Diagnostics repair actions MUST retain `->action(...)`, `->requiresConfirmation()`, destructive/action styling, capability gating, server-side authorization, and audit ownership. +- **FR-373-005**: The no-issue Environment Diagnostics state MUST show one calm no-action message and MUST NOT show repeated zero metrics or unsupported "healthy" claims. +- **FR-373-006**: Related provider, required-permission, operation, or evidence links MAY be shown only when repo-backed by existing helpers or bundle data; unavailable or non-repo-backed context MUST render as unavailable/support text rather than a fake link. +- **FR-373-007**: Support diagnostics modal/bundle presentation MUST lead with the support outcome, dominant issue, freshness/completeness/redaction state, and recommended first check before lower reference sections. +- **FR-373-008**: Support diagnostics MUST keep redaction markers, audit references, raw/provider/support details, and technical context secondary, collapsed, or lower-priority by default. +- **FR-373-009**: The feature MUST preserve existing support diagnostics authorization, telemetry, audit, and redaction behavior. +- **FR-373-010**: Provider Connections and Required Permissions MUST NOT be reimplemented by this spec; they are completed Spec 353 context unless a shared helper change creates a documented regression. +- **FR-373-011**: Required Permissions and System panel reachability gaps MUST be documented if observed; this spec MUST NOT fix broad auth/routing or system-panel fixture coverage. +- **FR-373-012**: The implementation MUST NOT change provider health resolution, permission calculation, ProviderGateway behavior, OperationRun lifecycle behavior, Graph contracts, provider credentials, or persisted diagnostic truth. +- **FR-373-013**: Likely cause language MUST be shown only when repo-backed by existing reason translation, provider reason, OperationRun failure summary, or stored diagnostic truth; otherwise the copy MUST use neutral recommended-next-check language without inventing a likely cause. +- **FR-373-014**: Spec-local implementation artifacts MUST document source audit, affected files, diagnostic contracts, screenshot index, implementation notes, browser verification, diagnostic safety checklist, and validation results. + +### Non-Functional Requirements + +- **NFR-373-001**: Render paths MUST remain DB-local and MUST NOT introduce Microsoft Graph or provider HTTP calls during page render or modal render. +- **NFR-373-002**: No migration, new table, new persisted projection, enum/status family, provider framework, queue family, scheduler change, storage change, package, or environment variable is planned. +- **NFR-373-003**: Filament v5 and Livewire v4 compatibility MUST be preserved. No Livewire v3 or Filament v3/v4 APIs are allowed. +- **NFR-373-004**: Panel provider registration MUST remain in `apps/platform/bootstrap/providers.php`; this spec must not move provider registration. +- **NFR-373-005**: `ProviderConnectionResource` global search remains disabled; no globally searchable resource is added or changed unless the implementation updates the spec first. +- **NFR-373-006**: No new Filament asset registration is expected. If an implementation unexpectedly registers assets, deployment notes must include `cd apps/platform && php artisan filament:assets`. +- **NFR-373-007**: Browser proof must use the existing smoke-login/browser harness where available; do not invent a new auth flow. + +## UI Action Matrix *(mandatory when Filament is changed)* + +| Surface | Location | Header Actions | Inspect Affordance | Row Actions | Bulk Actions | Empty-State CTA(s) | View/Header Actions | Audit log? | Notes / Exemptions | +|---|---|---|---|---|---|---|---|---|---| +| Environment Diagnostics | `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` and `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` | Existing `Bootstrap owner`, `Merge duplicate access scopes` only when applicable | singleton page; same-route context | none | none | no list empty state; no-action summary when clean | existing repair actions only | existing service audit ownership must be preserved/verified | destructive repair actions require confirmation, `TENANT_MANAGE`, and server-side enforcement | +| Support diagnostics modal | `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` and existing action hosts | Existing `openSupportDiagnostics` action on host pages/resources only | modal references link to existing authorized targets | none | none | unavailable references render as unavailable, not fake links | no new mutation | existing support diagnostics audit/telemetry preserved | read-only support modal, capability-gated | + +### Key Entities *(include if feature involves data)* + +- **Environment diagnostic case**: derived page presentation over existing missing-owner and duplicate-membership facts. +- **Support diagnostic bundle**: existing redacted bundle with context, summary, sections, redaction markers, and notes. +- **Related diagnostic reference**: existing provider, permission, operation, evidence, review, report, or audit context link when repo-backed. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-373-001**: Environment Diagnostics reaches a browser-reviewed target of at least 4.0 using the Spec 368 score model, or a documented blocker explains the remaining gap. +- **SC-373-002**: Environment Diagnostics feature/browser proof covers missing owner, duplicate memberships, both blockers, and no-action states. +- **SC-373-003**: Support diagnostics feature/browser proof covers tenant and OperationRun contexts where existing fixtures make them reachable. +- **SC-373-004**: Default-visible diagnostic/support content includes what failed, impact, and next check/action, while raw/provider/debug detail remains secondary. +- **SC-373-005**: Provider Connections and Required Permissions remain aligned with Spec 353 and are not reopened except for documented regression checks. +- **SC-373-006**: `git diff --check` passes; targeted tests and browser smoke pass or limitations are documented. +- **SC-373-007**: Final implementation artifacts include source audit, affected files, diagnostic contracts, screenshot index, implementation notes, browser report, safety checklist, and validation report. + +## Risks + +- **Hiding useful diagnostic data**: Mitigate by demoting technical detail, not deleting it, and keeping support/reference sections accessible. +- **Inventing likely causes**: Mitigate by using likely-cause language only when backed by existing reason translation or stored failure/diagnostic truth. +- **Reopening completed specs**: Mitigate by treating Spec 353, 371, and 372 as context/regression only. +- **Destructive repair-action drift**: Mitigate by verifying confirmation, capability gating, server-side authorization, and audit ownership before changing presentation. +- **Fixture gaps**: Mitigate by documenting blocked pages/routes and recommending Browser Audit Fixture Coverage instead of fixing auth/routing here. + +## Assumptions + +- Spec 353 is completed enough to exclude Provider Connections and Required Permissions from primary Spec 373 implementation. +- Existing smoke-login/browser harness can reach Environment Diagnostics and at least one support diagnostics modal; if not, the implementation documents the blocker. +- Support diagnostics remains a modal/action surface, not a new standalone page. +- No new provider or permission truth is needed to productize the first viewport. + +## Open Questions + +None blocking preparation. During implementation, verify exact browser reachability for Environment Diagnostics and support diagnostics modal using current fixtures. + +## Follow-up Spec Candidates + +- Spec 374 - UI Bloat Regression Guard v1. +- Browser Audit Fixture Coverage for `/system`, Required Permissions fallback contexts, and any support diagnostics route/modal that remains unreachable. +- Secondary-action density cleanup for Provider Connections if Spec 353 follow-up review still finds action overload. diff --git a/specs/373-diagnostic-surface-separation/tasks.md b/specs/373-diagnostic-surface-separation/tasks.md new file mode 100644 index 00000000..160bccdc --- /dev/null +++ b/specs/373-diagnostic-surface-separation/tasks.md @@ -0,0 +1,182 @@ +# Tasks: Spec 373 - Diagnostic Surface Separation v1 + +**Input**: `specs/373-diagnostic-surface-separation/spec.md`, `plan.md`, `checklists/requirements.md`, Spec 368 audit artifacts, Spec 370 contract artifacts, and completed Spec 353/371/372 context. + +**Tests**: Required for later implementation. This spec changes existing operator/support-facing diagnostic UI hierarchy. + +## Test Governance Checklist + +- [x] Lane assignment is named and narrow: Feature/Livewire for page/modal rendering, Browser for first-viewport/modal hierarchy, static checks for artifact quality. +- [x] New or changed tests stay in the smallest honest family; any browser coverage is explicit and bounded to Spec 373 surfaces. +- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any browser fixture gap is documented instead of broadened silently. +- [x] Planned validation commands cover the changed behavior without pulling in unrelated lane cost. +- [x] `standard-native-filament` relief is explicit for Environment Diagnostics action safety; `shared-detail-family` coverage is explicit for support diagnostics modal. +- [x] Any material fixture, browser, or follow-up note is recorded in the active spec artifacts. + +## Phase 1: Preparation And Repo Truth + +**Purpose**: Confirm the implementation target is the remaining diagnostics gap and not completed provider/customer/operator productization work. + +- [x] T001 Re-read `specs/373-diagnostic-surface-separation/spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`. +- [x] T002 Re-read Spec 368 diagnostic/configuration source inputs: + - `specs/368-platform-ui-signal-to-noise-browser-audit/audit.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/findings.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/page-scorecard.csv` + - `specs/368-platform-ui-signal-to-noise-browser-audit/spec-candidates.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/raw/browser-notes.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/015-diagnostic-surface-diagnostics-environment-diagnostics.png` + - `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/blocked-or-error/016-configuration-surface-settings-required-permissions-error.png` + - `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/blocked-or-error/031-system-surface-dashboard-system-dashboard-error.png` +- [x] T003 Re-read Spec 370 contract artifacts: + - `specs/370-global-surface-information-architecture-contract/artifacts/surface-contract.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/surface-type-matrix.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/page-assessment-checklist.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/copy-and-terminology-rules.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/follow-up-spec-map.md` +- [x] T004 Re-read completed Spec 353 boundaries and artifacts: + - `specs/353-provider-connections-resolution-guidance-v1/spec.md` + - `specs/353-provider-connections-resolution-guidance-v1/tasks.md` + - `docs/ui-ux-enterprise-audit/page-reports/ui-009-provider-connections.md` + - `docs/ui-ux-enterprise-audit/page-reports/ui-077-required-permissions.md` +- [x] T005 Re-read completed Spec 371 and Spec 372 artifact summaries for patterns and guardrails: + - `specs/371-core-operator-view-surfaces-productization/artifacts/source-audit-summary.md` + - `specs/371-core-operator-view-surfaces-productization/artifacts/browser-verification-report.md` + - `specs/371-core-operator-view-surfaces-productization/artifacts/validation-report.md` + - `specs/372-customer-auditor-surface-safety-pass/artifacts/source-audit-summary.md` + - `specs/372-customer-auditor-surface-safety-pass/artifacts/browser-verification-report.md` + - `specs/372-customer-auditor-surface-safety-pass/artifacts/validation-report.md` +- [x] T006 Re-verify current runtime truth in: + - `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` + - `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` + - `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` + - `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` + - existing support diagnostics action host pages/resources +- [x] T007 Confirm no migration, package, env var, queue family, scheduler, storage, panel/provider, global-search, provider gateway, permission engine, or OperationRun lifecycle change is required. + +## Phase 2: Spec-Local Artifacts Before Runtime Edits + +**Purpose**: Make the later implementation auditable and prevent accidental scope growth. + +- [x] T008 Create `specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md` with Spec 368 findings, Spec 370 inputs, Spec 371/372/353 completed-context notes, and reachability expectations. +- [x] T009 Create `specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md` covering Environment Diagnostics, support diagnostics modal, Provider Connections context, Required Permissions context, and `/system` deferred status. +- [x] T010 Create `specs/373-diagnostic-surface-separation/artifacts/affected-files.md` and populate planned file rows before runtime edits. +- [x] T011 Create `specs/373-diagnostic-surface-separation/artifacts/before-after-screenshot-index.md` with Spec 368 before screenshots and planned after/blocked screenshot slots. +- [x] T012 Create `specs/373-diagnostic-surface-separation/artifacts/diagnostic-safety-checklist.md` with one checklist row per scoped/referenced surface. +- [x] T013 Create `specs/373-diagnostic-surface-separation/artifacts/validation-report.md` with branch, HEAD, clean/dirty state before implementation, and planned commands. +- [x] T014 Create `specs/373-diagnostic-surface-separation/artifacts/implementation-notes.md` and `specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md`, then record the completed-spec guardrail decision and planned browser checks before runtime edits. + +## Phase 3: Tests First - Environment Diagnostics + +**Purpose**: Lock diagnostic hierarchy and existing repair-action safety before changing page output. + +- [x] T015 Add or update focused Feature/Livewire coverage for Environment Diagnostics missing-owner state. +- [x] T016 Add or update focused Feature/Livewire coverage for Environment Diagnostics duplicate-membership state. +- [x] T017 Add or update focused Feature/Livewire coverage for both blockers shown together with one top summary, one dominant next action, and any second repair/context path demoted instead of competing summaries or primary actions. +- [x] T018 Add or update focused Feature/Livewire coverage for the no-action state: one calm diagnostic summary and no zero-card spam. +- [x] T019 Add or update assertions that `bootstrapOwner` and `mergeDuplicateMemberships` remain visible only when applicable. +- [x] T020 Add or update assertions that the existing repair actions keep confirmation, capability gating, destructive treatment, and server-side authorization behavior. +- [x] T021 Add or update assertions that Environment Diagnostics render paths do not call Graph/provider HTTP and use existing DB-local truth only. + +## Phase 4: Tests First - Support Diagnostics Modal + +**Purpose**: Preserve authorization, redaction, telemetry, audit, and modal support value while improving hierarchy. + +- [x] T022 Add or update tenant support diagnostics modal coverage so the summary/dominant issue, redaction note, and recommended first check appear before reference sections. +- [x] T023 Add or update OperationRun support diagnostics modal coverage so failed/blocked run context appears before lower audit/reference sections. +- [x] T024 Add or update support diagnostics authorization coverage for entitled users without support diagnostics capability. +- [x] T025 Add or update assertions that redaction markers and raw/support detail remain lower-priority, redacted, or unavailable rather than default-visible raw payloads. +- [x] T026 Add or update assertions that support diagnostics telemetry and audit behavior remain unchanged when the modal opens. +- [x] T027 Add or update tests for missing provider connection / missing OperationRun / untranslated provider reason / no dominant issue fallback copy, including assertions that unavailable context is not rendered as a fake link and likely-cause copy falls back to neutral recommended-next-check language. + +## Phase 5: Environment Diagnostics Implementation + +**Purpose**: Productize the existing diagnostic page without backend or provider behavior changes. + +- [x] T028 Update `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` so one diagnostic summary leads the first viewport. +- [x] T029 Ensure missing-owner copy includes failed condition, impact, and the existing next action. +- [x] T030 Ensure duplicate-membership copy includes failed condition, impact, and the existing next action. +- [x] T031 Ensure the no-action copy says one calm message such as "No diagnostic action is required" and avoids unsupported broad health claims. +- [x] T032 If needed, update `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` with the smallest page-local derived summary data; do not create a generic diagnostic framework. +- [x] T033 Preserve existing `ActionSurfaceDeclaration`, `UiEnforcement`, `Capabilities::TENANT_MANAGE`, confirmation, action handlers, and repair service ownership. +- [x] T034 Keep technical/provider/ID detail out of the first viewport unless it directly explains the current diagnostic blocker. + +## Phase 6: Support Diagnostics Modal Implementation + +**Purpose**: Make support diagnostics guide the first support check while preserving full redacted depth. + +- [x] T035 Update `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` so headline, dominant issue, freshness/completeness, redaction note, and recommended first check precede lower sections. +- [x] T036 If existing bundle data is insufficient for "recommended first check", update `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` narrowly using existing provider reason, OperationRun, contextual help, and reference truth; do not infer links or likely causes when no repo-backed proof exists. +- [x] T037 Keep workspace/environment/run context visible without promoting raw record IDs as primary UI copy. +- [x] T038 Keep section references, audit history, redaction markers, and technical/support details lower-priority and redacted. +- [x] T039 Do not add new support request, PSA, AI, export, provider, permission, or OperationRun behavior. + +## Phase 7: Completed-Spec Regression And UI Audit Handling + +**Purpose**: Protect completed Spec 353/371/372 surfaces while keeping coverage artifacts proportional. + +- [x] T040 Confirm Provider Connections and Required Permissions are unchanged unless a shared helper change requires a targeted regression note. +- [x] T041 If Provider Connections or Required Permissions are touched by shared code, run focused Spec 353 regression tests and document why the touch was unavoidable. +- [x] T042 Update or create `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` only if implementation materially changes UI-012 evidence/status. +- [x] T043 Update `docs/ui-ux-enterprise-audit/route-inventory.md` only if screenshot/report references or UI-012 status changes. +- [x] T044 Update `docs/ui-ux-enterprise-audit/unresolved-pages.md` only if a scoped route/modal remains unreachable and needs durable tracking. +- [x] T045 Leave `docs/ui-ux-enterprise-audit/page-reports/ui-009-provider-connections.md` and `docs/ui-ux-enterprise-audit/page-reports/ui-077-required-permissions.md` untouched unless a documented regression requires it. + +## Phase 8: Browser Smoke And Screenshots + +**Purpose**: Prove the source browser finding has been addressed or document fixture limitations honestly. + +- [x] T046 Start the local platform stack using Sail or the repo's platform dev command. +- [x] T047 Resolve/open the Environment Diagnostics route with the existing smoke-login/browser fixture. +- [x] T048 Capture `specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png` if reachable. +- [x] T049 Browser-verify missing-owner or duplicate-membership state if the fixture can create it safely; otherwise document fixture limitation. +- [x] T050 Browser-verify no-action Environment Diagnostics state if reachable. +- [x] T051 Open support diagnostics modal from an existing tenant/environment or OperationRun host surface. +- [x] T052 Capture `specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png` if reachable, or a blocked screenshot/reason if not. +- [x] T053 Verify browser console has no new JavaScript/runtime errors for the scoped flow. +- [x] T054 Verify Provider Connections and Required Permissions are not recaptured unless shared changes require targeted regression proof. + +## Phase 9: Validation And Close-Out Artifacts + +**Purpose**: Finish the implementation with focused proof and complete spec-local evidence. + +Execution notes: + +- T055 exact filter result: `--filter=EnvironmentDiagnostics` returned `No tests found`; effective Environment Diagnostics validation used `tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` and passed. +- T057 was not applicable because no Provider Connections or Required Permissions runtime files were touched; Spec 353 reports remained unchanged. + +- [x] T055 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=EnvironmentDiagnostics`. +- [x] T056 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics`. +- [x] T057 Run focused Spec 353 regression tests only if Provider Connections or Required Permissions were touched by shared code. +- [x] T058 Run `cd apps/platform && ./vendor/bin/sail pint --dirty` if PHP files changed. +- [x] T059 Run `git diff --check`. +- [x] T060 Complete `specs/373-diagnostic-surface-separation/artifacts/affected-files.md` with final touched files, risk, verification class, and out-of-scope side effects. +- [x] T061 Complete `specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md` with URLs, fixture, screenshots, reachability, before/after scores, blocked pages, and remaining issues. +- [x] T062 Complete `specs/373-diagnostic-surface-separation/artifacts/before-after-screenshot-index.md`. +- [x] T063 Complete `specs/373-diagnostic-surface-separation/artifacts/diagnostic-safety-checklist.md`. +- [x] T064 Complete `specs/373-diagnostic-surface-separation/artifacts/implementation-notes.md`. +- [x] T065 Complete `specs/373-diagnostic-surface-separation/artifacts/validation-report.md` with tests, browser results, dirty state, runtime files changed, and recommended next spec. +- [x] T066 Confirm final implementation report includes Livewire v4 compliance, provider registration location, global search status, destructive action safety, asset strategy, tests, and deployment impact. + +## Non-Goals Checklist + +- [x] NT001 Do not reimplement Provider Connections or Required Permissions readiness guidance; Spec 353 owns that work. +- [x] NT002 Do not solve `/system` auth or browser fixture reachability. +- [x] NT003 Do not change ProviderGateway, provider health resolver, provider credential, or Microsoft Graph permission calculation behavior. +- [x] NT004 Do not add migrations, new models, persisted diagnostic truth, enum/status families, or provider/onboarding frameworks. +- [x] NT005 Do not add new Graph calls or provider HTTP calls during render. +- [x] NT006 Do not add support request lifecycle, external PSA handoff, AI, automation, billing, or entitlement behavior. +- [x] NT007 Do not intentionally refactor customer/auditor/operator surfaces from Specs 371/372. +- [x] NT008 Do not rewrite completed historical specs or remove implementation close-out/validation evidence. + +## Dependencies And Execution Order + +- Phase 1 must complete before runtime edits. +- Phase 2 artifacts should be created before tests and implementation so scope drift is visible. +- Phases 3 and 4 test work should precede Phases 5 and 6 implementation. +- Phase 7 runs after any shared-code touch and before browser close-out. +- Phase 8 browser smoke runs after targeted tests are green enough to make rendered proof meaningful. +- Phase 9 closes the implementation package. + +## Recommended Implementation Strategy + +Deliver User Story 1 first: Environment Diagnostics guidance. It is the browser-verified Spec 368 gap and can be tested independently. Add support diagnostics modal hierarchy next if reachable with existing fixtures. Treat Provider Connections and Required Permissions as regression context only.