diff --git a/.codex/config.toml b/.codex/config.toml index 4ff1a7ec..acb84d43 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -3,6 +3,12 @@ command = "./scripts/platform-sail" args = ["artisan", "boost:mcp"] cwd = "/Users/ahmeddarrazi/Documents/projects/wt-plattform" +[mcp_servers.laravel-boost.tools.search-docs] +approval_mode = "approve" + +[mcp_servers.laravel-boost.tools.tinker] +approval_mode = "approve" + [mcp_servers.gitea] command = "bash" args = [ diff --git a/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php b/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php index 00cc81f7..89eff682 100644 --- a/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php +++ b/apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php @@ -5,19 +5,25 @@ namespace App\Filament\Pages; use App\Filament\Concerns\ResolvesPanelTenantContext; +use App\Models\ManagedEnvironment; use App\Models\User; +use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Auth\ManagedEnvironmentDiagnosticsService; use App\Services\Auth\ManagedEnvironmentMembershipManager; use App\Support\Auth\Capabilities; +use App\Support\ProductTelemetry\ProductTelemetryRecorder; +use App\Support\ProductTelemetry\ProductUsageEventCatalog; use App\Support\Rbac\Actions\ResolvesUiActionContext; use App\Support\Rbac\Actions\UiActionContext; use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiTooltips; +use App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot; use Filament\Actions\Action; use Filament\Pages\Page; +use Illuminate\Contracts\View\View; class EnvironmentDiagnostics extends Page { @@ -30,11 +36,21 @@ class EnvironmentDiagnostics extends Page protected string $view = 'filament.pages.environment-diagnostics'; + public function getTitle(): string + { + return 'Repair diagnostics'; + } + + public function getSubheading(): string + { + return 'Checks supported TenantPilot access and membership repair conditions only.'; + } + public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly) - ->satisfy(ActionSurfaceSlot::ListHeader, 'Header exposes capability-gated tenant repair actions when inconsistent membership state is detected.') - ->exempt(ActionSurfaceSlot::InspectAffordance, 'ManagedEnvironment diagnostics is already the singleton diagnostic surface for the active tenant.') + ->satisfy(ActionSurfaceSlot::ListHeader, 'Header exposes a read-only support diagnostics modal plus capability-gated tenant repair actions when inconsistent membership state is detected.') + ->exempt(ActionSurfaceSlot::InspectAffordance, 'Repair diagnostics is already the singleton repair diagnostics surface for the active tenant.') ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The diagnostics page does not render row-level secondary actions.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The diagnostics page does not expose bulk actions.') ->exempt(ActionSurfaceSlot::ListEmptyState, 'Diagnostics content is always rendered instead of a list-style empty state.'); @@ -44,6 +60,11 @@ public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration public bool $hasDuplicateMembershipsForCurrentUser = false; + /** + * @var list + */ + public array $supportDiagnosticsAuditKeys = []; + public function mount(): void { $tenant = static::resolveTenantContextForCurrentPanelOrFail(); @@ -111,12 +132,12 @@ public function diagnosticSummary(): array 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', + 'headline' => 'No repair diagnostics are active', + 'body' => 'No supported access or membership repair is active for this managed environment.', + 'status' => 'No repair action', '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.', + 'impact' => 'Repair diagnostics only checks existing TenantPilot access and membership repair conditions; it is not a generic environment health hub.', + 'next_check' => 'Use Open support diagnostics for broader provider, operation, evidence, or audit context.', 'primary_action_label' => null, 'secondary_action_label' => null, 'blockers' => [], @@ -128,9 +149,9 @@ public function diagnosticSummary(): array 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.', + ? '1 repair diagnostic needs attention' + : $blockerCount.' repair diagnostics need attention', + 'body' => 'Repair diagnostics checks supported TenantPilot access and membership defects only. Resolve the highest-impact blocker first; lower repair paths remain visible for context.', 'status' => $blockerCount === 1 ? 'Action needed' : $blockerCount.' blockers', 'color' => 'warning', 'impact' => $primaryBlocker['impact'], @@ -147,6 +168,8 @@ public function diagnosticSummary(): array protected function getHeaderActions(): array { return [ + $this->openSupportDiagnosticsAction(), + UiEnforcement::forScopedAction( Action::make('bootstrapOwner') ->label('Bootstrap owner') @@ -175,6 +198,98 @@ protected function getHeaderActions(): array ]; } + private function openSupportDiagnosticsAction(): Action + { + $action = Action::make('openSupportDiagnostics') + ->label('Open support diagnostics') + ->icon('heroicon-o-lifebuoy') + ->color('gray') + ->modal() + ->slideOver() + ->stickyModalHeader() + ->modalHeading(__('localization.dashboard.support_diagnostics')) + ->modalDescription(__('localization.dashboard.support_diagnostics_description')) + ->modalSubmitAction(false) + ->modalCancelAction(fn (Action $action): Action => $action->label(__('localization.dashboard.close'))) + ->mountUsing(function (): void { + $this->auditTenantSupportDiagnosticsOpen(); + }) + ->modalContent(fn (): View => view('filament.modals.support-diagnostic-bundle', [ + 'bundle' => $this->tenantSupportDiagnosticBundle(), + ])); + + return UiEnforcement::forScopedAction( + $action, + fn (): UiActionContext => static::tenantUiActionContext(), + ) + ->requireCapability(Capabilities::SUPPORT_DIAGNOSTICS_VIEW) + ->apply(); + } + + /** + * @return array + */ + public function tenantSupportDiagnosticBundle(): array + { + $tenant = static::resolveTenantContextForCurrentPanelOrFail(); + $user = auth()->user(); + + if (! $user instanceof User) { + abort(403, 'Not allowed'); + } + + return app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant, $user); + } + + private function auditTenantSupportDiagnosticsOpen(): void + { + $tenant = static::resolveTenantContextForCurrentPanelOrFail(); + $user = auth()->user(); + + if (! $user instanceof User) { + abort(403, 'Not allowed'); + } + + $this->recordSupportDiagnosticsOpened( + tenant: $tenant, + bundle: $this->tenantSupportDiagnosticBundle(), + user: $user, + ); + } + + /** + * @param array $bundle + */ + private function recordSupportDiagnosticsOpened(ManagedEnvironment $tenant, array $bundle, User $user): void + { + $auditKey = 'tenant:'.$tenant->getKey(); + + if (in_array($auditKey, $this->supportDiagnosticsAuditKeys, true)) { + return; + } + + app(WorkspaceAuditLogger::class)->logSupportDiagnosticsOpened( + tenant: $tenant, + contextType: 'tenant', + bundle: $bundle, + actor: $user, + ); + + app(ProductTelemetryRecorder::class)->record( + eventName: ProductUsageEventCatalog::SUPPORT_DIAGNOSTICS_OPENED, + workspaceId: (int) $tenant->workspace_id, + tenantId: (int) $tenant->getKey(), + userId: (int) $user->getKey(), + subjectType: 'tenant', + subjectId: (int) $tenant->getKey(), + metadata: [ + 'source_surface' => 'repair_diagnostics', + ], + ); + + $this->supportDiagnosticsAuditKeys[] = $auditKey; + } + public function bootstrapOwner(): void { $tenant = static::resolveTenantContextForCurrentPanelOrFail(); diff --git a/apps/platform/app/Support/RedactionIntegrity.php b/apps/platform/app/Support/RedactionIntegrity.php index 63fef58f..30e7f095 100644 --- a/apps/platform/app/Support/RedactionIntegrity.php +++ b/apps/platform/app/Support/RedactionIntegrity.php @@ -17,7 +17,7 @@ public static function protectedValueNote(): string public static function supportDiagnosticsNote(): string { - return 'Support diagnostics are default-redacted. Secrets, credentials, raw provider payloads, full response bodies, and unrestricted log excerpts are intentionally excluded.'; + return 'Support diagnostics use a redacted support view. Secrets, credentials, raw provider payloads, full response bodies, and unrestricted log excerpts are intentionally excluded.'; } /** diff --git a/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php b/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php index 76a492ce..754af70e 100644 --- a/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php +++ b/apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php @@ -108,6 +108,7 @@ public function forOperationRun(OperationRun $run, ?User $actor = null): array $auditLogs = $this->operationAuditLogs($run); $runSummary = $this->runSummaryBuilder->build($run); $runSummaryArray = $runSummary?->toArray(); + $dominantIssue = data_get($runSummaryArray, 'dominantCause.explanation'); return $this->bundle( contextType: 'operation_run', @@ -115,11 +116,13 @@ public function forOperationRun(OperationRun $run, ?User $actor = null): array tenant: $tenant, providerConnection: $providerConnection, operationRun: $run, - headline: $runSummaryArray['headline'] ?? OperationRunLinks::identifier($run).' support diagnostics', - dominantIssue: (string) data_get( - $runSummaryArray, - 'dominantCause.explanation', - $this->operationDominantIssue($run), + headline: $this->supportSafeOperationHeadline( + $run, + is_string($runSummaryArray['headline'] ?? null) ? $runSummaryArray['headline'] : null, + ), + dominantIssue: $this->supportSafeOperationIssue( + $run, + is_string($dominantIssue) ? $dominantIssue : $this->operationDominantIssue($run), ), sections: [ $this->overviewSection($workspace, $tenant, $run), @@ -288,7 +291,7 @@ private function recommendedFirstCheck( 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.', + 'body' => 'Start with workspace and environment scope, then inspect lower sections only if another surface reported a blocker.', 'reference' => $this->firstSectionReference($sections, 'overview'), ]; } @@ -598,9 +601,36 @@ private function operationDominantIssue(OperationRun $run): string ->first(static fn (mixed $item): bool => is_array($item) && trim((string) ($item['message'] ?? '')) !== ''); if (is_array($failure)) { - return trim((string) $failure['message']); + return $this->supportSafeOperationIssue($run, trim((string) $failure['message'])); } + return $this->operationOutcomeIssue($run); + } + + private function supportSafeOperationHeadline(OperationRun $run, ?string $candidate): string + { + $candidate = is_string($candidate) ? trim($candidate) : ''; + + if ($candidate === '' || $this->containsRestrictedTechnicalDetail($candidate)) { + return OperationRunLinks::identifier($run).' support diagnostics'; + } + + return $candidate; + } + + private function supportSafeOperationIssue(OperationRun $run, ?string $candidate): string + { + $candidate = is_string($candidate) ? trim($candidate) : ''; + + if ($candidate === '' || $this->containsRestrictedTechnicalDetail($candidate)) { + return $this->operationOutcomeIssue($run); + } + + return $candidate; + } + + private function operationOutcomeIssue(OperationRun $run): string + { return match ((string) $run->outcome) { 'failed' => 'The operation failed and needs follow-up.', 'blocked' => 'The operation was blocked by a prerequisite or policy condition.', @@ -609,6 +639,14 @@ private function operationDominantIssue(OperationRun $run): string }; } + private function containsRestrictedTechnicalDetail(string $candidate): bool + { + return preg_match( + '/(SQLSTATE\[[^\]]+\]|PDOException|QueryException|Illuminate\\\\Database|Integrity constraint violation|duplicate key value violates|violates .*constraint|constraint\s+"[^"]+"|\bERROR:\s|Stack trace|#\d+\s+|\/vendor\/|\\\\vendor\\\\)/i', + $candidate, + ) === 1; + } + private function providerIssue(ProviderConnection $connection): ?string { $reasonCode = trim((string) $connection->last_error_reason_code); @@ -725,7 +763,10 @@ private function operationContextSection(?OperationRun $operationRun, ?ManagedEn key: 'operation_context', label: 'Operation context', availability: 'available', - summary: (string) ($runSummary['headline'] ?? $this->operationDominantIssue($operationRun)), + summary: $this->supportSafeOperationIssue( + $operationRun, + is_string($runSummary['headline'] ?? null) ? $runSummary['headline'] : $this->operationDominantIssue($operationRun), + ), freshnessNote: $this->freshnessNote($operationRun->completed_at ?? $operationRun->updated_at, 'Last run update'), references: [ $this->operationReference($operationRun, $tenant), @@ -823,18 +864,18 @@ private function environmentReviewSection(?EnvironmentReview $review, ?ManagedEn if (! $review instanceof EnvironmentReview) { return $this->section( key: 'environment_review', - label: 'ManagedEnvironment review', + label: 'Environment review', availability: 'missing', summary: 'No environment review was found for this support context.', references: [ - $this->missingReference('environment_review', 'ManagedEnvironment review not yet observed', 'Open environment review'), + $this->missingReference('environment_review', 'Environment review not yet observed', 'Open environment review'), ], ); } return $this->section( key: 'environment_review', - label: 'ManagedEnvironment review', + label: 'Environment review', availability: 'available', summary: sprintf('Latest environment review is %s with %s completeness.', (string) $review->status, (string) $review->completeness_state), freshnessNote: $this->freshnessNote($review->generated_at, 'Generated'), @@ -842,7 +883,7 @@ private function environmentReviewSection(?EnvironmentReview $review, ?ManagedEn $this->modelReference( type: 'environment_review', record: $review, - label: 'ManagedEnvironment review #'.$review->getKey(), + label: 'Environment review #'.$review->getKey(), actionLabel: 'Open environment review', url: $tenant instanceof ManagedEnvironment ? EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review], $tenant) @@ -1139,6 +1180,10 @@ private function completenessNote(array $sections): ?string return null; } - return 'Missing context: '.implode(', ', $missing).'.'; + if ($missing === ['Findings']) { + return 'No related finding observed.'; + } + + return 'Some related support context is not available: '.implode(', ', $missing).'.'; } } diff --git a/apps/platform/app/Support/Ui/ActionSurface/ActionSurfaceExemptions.php b/apps/platform/app/Support/Ui/ActionSurface/ActionSurfaceExemptions.php index 023706cf..44af777d 100644 --- a/apps/platform/app/Support/Ui/ActionSurface/ActionSurfaceExemptions.php +++ b/apps/platform/app/Support/Ui/ActionSurface/ActionSurfaceExemptions.php @@ -4,11 +4,13 @@ namespace App\Support\Ui\ActionSurface; +use App\Filament\Pages\BaselineCompareLanding; +use App\Filament\Pages\BaselineCompareMatrix; use App\Filament\Pages\BreakGlassRecovery; use App\Filament\Pages\ChooseEnvironment; use App\Filament\Pages\ChooseWorkspace; -use App\Filament\Pages\BaselineCompareLanding; -use App\Filament\Pages\BaselineCompareMatrix; +use App\Filament\Pages\EnvironmentDashboard; +use App\Filament\Pages\EnvironmentDiagnostics; use App\Filament\Pages\Monitoring\Alerts; use App\Filament\Pages\Monitoring\AuditLog; use App\Filament\Pages\Monitoring\EvidenceOverview; @@ -16,8 +18,6 @@ use App\Filament\Pages\Monitoring\Operations; use App\Filament\Pages\Operations\TenantlessOperationRunViewer; use App\Filament\Pages\Reviews\ReviewRegister; -use App\Filament\Pages\EnvironmentDashboard; -use App\Filament\Pages\EnvironmentDiagnostics; use App\Filament\Pages\Tenancy\RegisterTenant; use App\Filament\Pages\Workspaces\ManagedEnvironmentOnboardingWizard; use App\Filament\Pages\Workspaces\ManagedEnvironmentsLanding; @@ -26,15 +26,15 @@ use App\Filament\Resources\BackupSetResource\Pages\ViewBackupSet; use App\Filament\Resources\BaselineProfileResource\Pages\ViewBaselineProfile; use App\Filament\Resources\BaselineSnapshotResource\Pages\ViewBaselineSnapshot; +use App\Filament\Resources\EnvironmentReviewResource\Pages\ViewEnvironmentReview; use App\Filament\Resources\EvidenceSnapshotResource\Pages\ViewEvidenceSnapshot; use App\Filament\Resources\FindingExceptionResource\Pages\ViewFindingException; use App\Filament\Resources\FindingResource\Pages\ViewFinding; +use App\Filament\Resources\ManagedEnvironmentResource\Pages\EditManagedEnvironment; +use App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment; use App\Filament\Resources\PolicyVersionResource\Pages\ViewPolicyVersion; use App\Filament\Resources\ProviderConnectionResource\Pages\ViewProviderConnection; use App\Filament\Resources\ReviewPackResource\Pages\ViewReviewPack; -use App\Filament\Resources\ManagedEnvironmentResource\Pages\EditManagedEnvironment; -use App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment; -use App\Filament\Resources\EnvironmentReviewResource\Pages\ViewEnvironmentReview; use App\Filament\Resources\Workspaces\Pages\ViewWorkspace; use App\Filament\System\Pages\Dashboard as SystemDashboard; use App\Filament\System\Pages\Directory\ViewTenant as SystemDirectoryViewTenant; @@ -497,7 +497,7 @@ public static function spec193MonitoringSurfaceInventory(): array EnvironmentDiagnostics::class => [ 'surfaceKey' => 'tenant_diagnostics', 'classification' => 'special_type_acceptable', - 'canonicalNoun' => 'ManagedEnvironment diagnostics', + 'canonicalNoun' => 'Repair diagnostics', 'panelScope' => 'tenant', 'ownerScope' => 'tenant-owned', 'surfaceKind' => 'diagnostic_exception', @@ -505,7 +505,7 @@ public static function spec193MonitoringSurfaceInventory(): array 'sharedPattern' => 'none', 'requiresHeaderRemediation' => false, 'requiresExplicitDeclaration' => true, - 'exceptionReason' => 'ManagedEnvironment diagnostics is already the focused diagnostic surface for the active tenant and may expose repair actions only when a real defect exists.', + 'exceptionReason' => 'Repair diagnostics is the focused membership/access repair surface for the active tenant and may expose repair actions only when a real defect exists.', 'browserSmokeRequired' => true, ], ]; diff --git a/apps/platform/lang/de/localization.php b/apps/platform/lang/de/localization.php index 0570341b..50f2de2b 100644 --- a/apps/platform/lang/de/localization.php +++ b/apps/platform/lang/de/localization.php @@ -153,7 +153,7 @@ 'open_external_ticket' => 'Externes Ticket öffnen', 'open_support_diagnostics' => 'Supportdiagnosen öffnen', 'support_diagnostics' => 'Supportdiagnosen', - 'support_diagnostics_description' => 'Redaktionell bereinigter Umgebungskontext aus bestehenden Datensätzen.', + 'support_diagnostics_description' => 'Schneller Supportkontext aus bestehenden Datensätzen. Beginne mit der empfohlenen ersten Prüfung vor den tieferen technischen Abschnitten.', 'close' => 'Schließen', 'time_window' => 'Zeitfenster', 'window' => 'Fenster', diff --git a/apps/platform/lang/en/localization.php b/apps/platform/lang/en/localization.php index 58ecaa4f..acee12b1 100644 --- a/apps/platform/lang/en/localization.php +++ b/apps/platform/lang/en/localization.php @@ -153,7 +153,7 @@ 'open_external_ticket' => 'Open external ticket', 'open_support_diagnostics' => 'Open support diagnostics', 'support_diagnostics' => 'Support diagnostics', - 'support_diagnostics_description' => 'Redacted environment context from existing records.', + 'support_diagnostics_description' => 'Quick support context from existing records. Start with the recommended first check before lower technical sections.', 'close' => 'Close', 'time_window' => 'Time window', 'window' => 'Window', 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 bff578ca..cfee0c04 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 @@ -10,6 +10,27 @@ $recommendedFirstCheck = is_array(data_get($summary, 'recommended_first_check')) ? data_get($summary, 'recommended_first_check') : null; + $contextNotes = collect($notes) + ->filter(static fn (mixed $note): bool => is_string($note) && trim($note) !== '') + ->reject(static fn (string $note): bool => str_contains($note, 'redacted support view')) + ->values() + ->all(); + + $availabilityLabel = static function (?string $availability): string { + return match ($availability) { + 'available' => 'Available', + 'current' => 'Current', + 'fresh' => 'Fresh', + 'ready' => 'Ready', + 'partial' => 'Partial', + 'stale' => 'Stale', + 'error' => 'Error', + 'missing' => 'Not observed', + 'unavailable' => 'Unavailable', + 'redacted' => 'Redacted', + default => filled($availability) ? Str::headline(str_replace('_', ' ', (string) $availability)) : 'Unknown', + }; + }; $availabilityColor = static function (?string $availability): string { return match ($availability) { @@ -20,14 +41,48 @@ }; }; - $referenceDescription = static function (array $reference): string { + $contextTypeLabel = static function (?string $type): string { + return match ($type) { + 'operation_run' => 'Operation context', + 'tenant' => 'Environment context', + default => filled($type) ? Str::headline(str_replace('_', ' ', (string) $type)) : 'Environment context', + }; + }; + + $freshnessStateLabel = static function (?string $state): string { + return match ($state) { + 'fresh' => 'Fresh', + 'mixed' => 'Mixed freshness', + 'missing_context' => 'Missing context', + default => filled($state) ? Str::headline(str_replace('_', ' ', (string) $state)) : 'Unknown freshness', + }; + }; + + $redactionModeLabel = static function (?string $mode): string { + return match ($mode) { + 'default_redacted', 'default-redacted' => 'Redacted support view', + default => filled($mode) ? Str::headline(str_replace('_', ' ', (string) $mode)) : 'Redacted support view', + }; + }; + + $referenceTypeLabel = static function (?string $type): string { + return match ($type) { + 'audit_log' => 'Audit event', + 'environment_review' => 'Environment review', + 'finding' => 'Finding', + 'operation_run' => 'Operation', + 'provider_connection' => 'Provider connection', + 'review_pack' => 'Review pack', + 'stored_report' => 'Stored report', + 'tenant' => 'Environment', + default => filled($type) ? Str::headline(str_replace('_', ' ', (string) $type)) : 'Reference', + }; + }; + + $referenceDescription = static function (array $reference) use ($availabilityLabel, $referenceTypeLabel): string { $parts = [ - is_string($reference['type'] ?? null) && trim((string) $reference['type']) !== '' - ? (string) $reference['type'] - : 'reference', - is_string($reference['availability'] ?? null) && trim((string) $reference['availability']) !== '' - ? (string) $reference['availability'] - : 'missing', + $referenceTypeLabel(is_string($reference['type'] ?? null) ? (string) $reference['type'] : null), + $availabilityLabel(is_string($reference['availability'] ?? null) ? (string) $reference['availability'] : 'missing'), ]; if (is_string($reference['freshness_note'] ?? null) && trim((string) $reference['freshness_note']) !== '') { @@ -36,6 +91,22 @@ return implode(' - ', $parts); }; + + $sectionObserved = static function (array $section, array $references): string { + if (is_string($section['freshness_note'] ?? null) && trim((string) $section['freshness_note']) !== '') { + return (string) $section['freshness_note']; + } + + $availableCount = collect($references) + ->filter(static fn (array $reference): bool => ($reference['availability'] ?? null) === 'available') + ->count(); + + if ($availableCount > 0) { + return $availableCount === 1 ? '1 related reference observed.' : $availableCount.' related references observed.'; + } + + return 'No related record observed in this support context.'; + }; @endphp
@@ -46,18 +117,22 @@
- {{ data_get($context, 'type', data_get($bundle, 'context_type', 'tenant')) }} + {{ $contextTypeLabel(data_get($context, 'type', data_get($bundle, 'context_type', 'tenant'))) }} - {{ data_get($summary, 'freshness_state', data_get($bundle, 'freshness_state', 'mixed')) }} + {{ $freshnessStateLabel(data_get($summary, 'freshness_state', data_get($bundle, 'freshness_state', 'mixed'))) }} - {{ str_replace('_', '-', (string) data_get($redaction, 'mode', data_get($bundle, 'redaction_mode', 'default-redacted'))) }} + {{ $redactionModeLabel(data_get($redaction, 'mode', data_get($bundle, 'redaction_mode', 'default_redacted'))) }}
+

+ Use this quick support context to start with the recommended first check before opening lower technical sections. +

+ @if ($recommendedFirstCheck !== null) @php $firstCheckReference = is_array($recommendedFirstCheck['reference'] ?? null) @@ -70,17 +145,20 @@ $firstCheckReferenceActionLabel = $firstCheckReference['action_label'] ?? ($firstCheckReferenceUrl ? 'Open' : 'Unavailable'); @endphp - - +
+
+
+

+ Recommended first check +

+

+ {{ data_get($recommendedFirstCheck, 'body', 'Start with the highest-confidence support reference before inspecting lower sections.') }} +

+
{{ data_get($recommendedFirstCheck, 'label', 'Review source first') }} - +
@if ($firstCheckReference !== null)
@@ -89,7 +167,12 @@ @if ($firstCheckReferenceUrl) - + {{ $firstCheckReferenceActionLabel }} @else @@ -99,7 +182,7 @@ @endif
@endif - +
@endif
@@ -108,8 +191,8 @@
{{ data_get($context, 'workspace_label', 'Workspace unavailable') }}
-
ManagedEnvironment
-
{{ data_get($context, 'tenant_label', 'ManagedEnvironment unavailable') }}
+
Environment
+
{{ data_get($context, 'tenant_label', 'Environment unavailable') }}
@@ -120,22 +203,28 @@ @endif @if ($notes !== []) - -
- @foreach ($notes as $note) - @if (is_string($note) && trim($note) !== '') -
- Note -

{{ $note }}

-
- @endif - @endforeach +
+
+
+

+ Support scope +

+

+ Read-only, redacted support view. Restricted provider details are excluded. +

+
+ + @if ($contextNotes !== []) +
+ @foreach ($contextNotes as $note) + + {{ $note }} + + @endforeach +
+ @endif
- +
@endif
@@ -148,26 +237,67 @@ : 'missing'; $sectionLabel = $section['label'] ?? $section['key'] ?? 'Section'; $sectionSummary = $section['summary'] ?? 'No summary available.'; + $actionReference = collect($references) + ->first(static fn (array $reference): bool => is_string($reference['url'] ?? null) && trim((string) $reference['url']) !== ''); + $actionReference = is_array($actionReference) + ? $actionReference + : (is_array($references[0] ?? null) ? $references[0] : null); + $sectionActionUrl = is_array($actionReference) && is_string($actionReference['url'] ?? null) && trim((string) $actionReference['url']) !== '' + ? (string) $actionReference['url'] + : null; + $sectionActionLabel = is_array($actionReference) + ? (string) ($actionReference['action_label'] ?? ($sectionActionUrl ? 'Open' : 'No action available')) + : 'No action available'; + $sectionObservedText = $sectionObserved($section, $references); @endphp - {{ $availability }} + {{ $availabilityLabel($availability) }}
- @if (is_string($section['freshness_note'] ?? null) && trim((string) $section['freshness_note']) !== '') -

{{ $section['freshness_note'] }}

- @endif +
+
+
Status
+
{{ $availabilityLabel($availability) }}
+
+
+
Why relevant
+
{{ $sectionSummary }}
+
+
+
Observed
+
{{ $sectionObservedText }}
+
+
+
Action
+
+ @if ($sectionActionUrl) + + {{ $sectionActionLabel }} + + @else + + {{ $sectionActionLabel }} + + @endif +
+
+
@if ($references !== []) -
+
@foreach ($references as $reference) @php $referenceLabel = $reference['label'] ?? 'Reference unavailable'; @@ -177,15 +307,24 @@ $referenceActionLabel = $reference['action_label'] ?? ($referenceUrl ? 'Open' : 'Unavailable'); @endphp - - +
+
+

+ {{ $referenceLabel }} +

+

+ {{ $referenceDescription($reference) }} +

+
+ +
@if ($referenceUrl) - + {{ $referenceActionLabel }} @else @@ -193,8 +332,8 @@ {{ $referenceActionLabel }} @endif - - +
+
@endforeach
@endif diff --git a/apps/platform/resources/views/filament/pages/monitoring/audit-log.blade.php b/apps/platform/resources/views/filament/pages/monitoring/audit-log.blade.php index 395ca41a..6033f20f 100644 --- a/apps/platform/resources/views/filament/pages/monitoring/audit-log.blade.php +++ b/apps/platform/resources/views/filament/pages/monitoring/audit-log.blade.php @@ -61,30 +61,30 @@ Event proof
-

+

{{ $focus['summary'] }}

-
-
+
+
Actor
-
{{ $focus['actor']['label'] }}
-
{{ $focus['actor']['type'] }}
+
{{ $focus['actor']['label'] }}
+
{{ $focus['actor']['type'] }}
-
+
Action
-
{{ $focus['action'] }}
+
{{ $focus['action'] }}
-
+
Target
-
{{ $focus['target']['label'] }}
-
{{ $focus['target']['type'] }}
+
{{ $focus['target']['label'] }}
+
{{ $focus['target']['type'] }}
-
+
Outcome
@@ -93,9 +93,9 @@
-
+
Time
-
{{ $focus['time'] }}
+
{{ $focus['time'] }}
@@ -159,7 +159,7 @@ @if ($focus && $focus['related_links'] !== []) @foreach ($focus['related_links'] as $link) - {{ $link['label'] }} + {{ $link['label'] }} Open related proof for this event. @endforeach diff --git a/apps/platform/resources/views/filament/pages/monitoring/partials/audit-log-inspect-event.blade.php b/apps/platform/resources/views/filament/pages/monitoring/partials/audit-log-inspect-event.blade.php index e299f604..ef2beac9 100644 --- a/apps/platform/resources/views/filament/pages/monitoring/partials/audit-log-inspect-event.blade.php +++ b/apps/platform/resources/views/filament/pages/monitoring/partials/audit-log-inspect-event.blade.php @@ -27,46 +27,46 @@ class="inline-flex items-center rounded-lg border border-gray-300 px-3 py-2 text @endif
-
-
+
+
Actor
-
+
{{ $selectedAudit->actorDisplayLabel() }}
-
+
{{ $selectedAudit->actorSnapshot()->type->label() }}
@if ($selectedAudit->actorSnapshot()->email) -
+
{{ $selectedAudit->actorSnapshot()->email }}
@endif
-
+
Action
-
+
{{ $selectedAuditProof['action'] ?? \App\Support\Audit\AuditActionId::labelFor((string) $selectedAudit->action) }}
-
+
Target
-
+
{{ $selectedAudit->targetDisplayLabel() ?? 'No target snapshot' }}
-
+
{{ $selectedAudit->resource_type ? ucfirst(str_replace('_', ' ', $selectedAudit->resource_type)) : 'Workspace event' }}
-
+
Outcome
@@ -77,23 +77,23 @@ class="inline-flex items-center rounded-lg border border-gray-300 px-3 py-2 text
-
+
Time
-
+
{{ $selectedAuditProof['time'] ?? $selectedAudit->recorded_at?->toDayDateTimeString() ?? 'Time unavailable' }}
-
+
Scope
-
+
{{ $selectedAudit->tenant?->name ?? 'Workspace-wide event' }}
-
+
Workspace #{{ $selectedAudit->workspace_id }}
@@ -118,7 +118,7 @@ class="inline-flex items-center rounded-lg border border-gray-300 px-3 py-2 text
{{ $item['label'] }}
-
+
{{ is_bool($item['value']) ? ($item['value'] ? 'true' : 'false') : $item['value'] }}
diff --git a/apps/platform/tests/Browser/Spec329EvidenceAuditDisclosureSmokeTest.php b/apps/platform/tests/Browser/Spec329EvidenceAuditDisclosureSmokeTest.php index 987575c2..52228c11 100644 --- a/apps/platform/tests/Browser/Spec329EvidenceAuditDisclosureSmokeTest.php +++ b/apps/platform/tests/Browser/Spec329EvidenceAuditDisclosureSmokeTest.php @@ -204,6 +204,35 @@ ->assertDontSee('all tenants') ->assertScript('document.querySelector("[data-testid=\"audit-disclosure-diagnostics\"]")?.open === false', true) ->assertScript('document.querySelector("[data-testid=\"audit-event-diagnostics\"]")?.open === false', true) + ->assertScript('(() => { + const selectedGrid = document.querySelector("[data-testid=\"audit-selected-proof-cards\"]"); + + if (! selectedGrid) { + return false; + } + + const cards = Array.from(selectedGrid.children); + + return selectedGrid.classList.contains("sm:grid-cols-2") + && selectedGrid.classList.contains("xl:grid-cols-3") + && selectedGrid.classList.contains("2xl:grid-cols-5") + && cards.length >= 5 + && cards.every((card) => getComputedStyle(card).minWidth === "0px"); + })()', true) + ->assertScript('(() => { + const selectedGrid = document.querySelector("[data-testid=\"audit-selected-proof-cards\"]"); + + if (! selectedGrid) { + return false; + } + + const wrappedNodes = Array.from(selectedGrid.querySelectorAll(".wrap-anywhere")); + const cards = Array.from(selectedGrid.children); + + return wrappedNodes.length >= 8 + && wrappedNodes.every((node) => node.closest(".rounded-2xl") instanceof HTMLElement) + && cards.every((card) => card.scrollWidth <= Math.ceil(card.clientWidth) + 1); + })()', true) ->assertScript('(() => { const grid = document.querySelector("[data-testid=\"audit-disclosure-workbench\"]"); const main = document.querySelector("[data-testid=\"audit-proof-primary\"]"); @@ -379,7 +408,7 @@ function spec329BrowserDisclosureFixture(): array 'workspace_id' => (int) $environmentA->workspace_id, 'managed_environment_id' => (int) $environmentA->getKey(), 'operation_run_id' => (int) $runA->getKey(), - 'actor_email' => 'spec329-a@example.test', + 'actor_email' => 'spec329-browser-operator-with-a-very-long-address+wrap-proof@example.test', 'actor_name' => 'Spec329 Browser Operator A', 'actor_type' => 'human', 'action' => 'permission_posture.checked', diff --git a/apps/platform/tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php b/apps/platform/tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php new file mode 100644 index 00000000..b058fd0e --- /dev/null +++ b/apps/platform/tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php @@ -0,0 +1,120 @@ +browser()->timeout(30_000); + +uses(RefreshDatabase::class); + +it('Spec374 smokes support and repair diagnostics entrypoints', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); + $tenant->forceFill(['name' => 'Spec374 Diagnostics Environment'])->save(); + + $dashboardPath = spec374BrowserPath(EnvironmentDashboard::getUrl(panel: 'admin', tenant: $tenant)); + $repairDiagnosticsPath = spec374BrowserPath(ManagedEnvironmentLinks::diagnosticsUrl($tenant)); + + $page = visit(spec374BrowserLoginUrl($user, $tenant, $dashboardPath)) + ->resize(1440, 1100) + ->waitForText($tenant->name) + ->click('[aria-label="More"]') + ->assertSee('Open support diagnostics') + ->assertDontSee('Repair diagnostics') + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs() + ->screenshot(true, '001-environment-dashboard-diagnostic-entry-after'); + spec374BrowserCopyScreenshot('001-environment-dashboard-diagnostic-entry-after'); + + $page + ->click('Open support diagnostics') + ->waitForText('Support diagnostics') + ->assertSee('Quick support context') + ->assertSee('Recommended first check') + ->assertSee('Redacted support view') + ->assertSee('Environment context') + ->assertSee('Support scope') + ->assertSourceHas('target="_blank"') + ->assertSourceHas('rel="noopener noreferrer"') + ->assertDontSee('Boundary') + ->assertDontSee('Repair diagnostics') + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs() + ->screenshot(true, '002-support-diagnostics-modal-after'); + spec374BrowserCopyScreenshot('002-support-diagnostics-modal-after'); + + $repairPage = visit($repairDiagnosticsPath) + ->resize(1440, 1100) + ->waitForText('Repair diagnostics') + ->assertSee('No repair diagnostics are active') + ->assertSee('No supported access or membership repair is active') + ->assertSee('not a generic environment health hub') + ->assertSee('Use Open support diagnostics') + ->assertSee('Open support diagnostics') + ->assertDontSee('Provider connection blocked') + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs() + ->screenshot(true, '003-environment-repair-diagnostics-after'); + spec374BrowserCopyScreenshot('003-environment-repair-diagnostics-after'); + + $repairPage + ->click('Open support diagnostics') + ->waitForText('Support diagnostics') + ->assertSee('Recommended first check') + ->assertSee('Redacted support view') + ->assertSee('Environment') + ->assertSee('Support scope') + ->assertSourceHas('target="_blank"') + ->assertSourceHas('rel="noopener noreferrer"') + ->assertDontSee('Boundary') + ->assertNoJavaScriptErrors() + ->assertNoConsoleLogs() + ->screenshot(true, '004-environment-repair-diagnostics-support-modal-after'); + spec374BrowserCopyScreenshot('004-environment-repair-diagnostics-support-modal-after'); +}); + +function spec374BrowserLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect): string +{ + return route('admin.local.smoke-login', [ + 'email' => $user->email, + 'tenant' => $tenant->external_id, + 'workspace' => $tenant->workspace->slug, + 'redirect' => $redirect, + ]); +} + +function spec374BrowserPath(string $url): string +{ + $path = parse_url($url, PHP_URL_PATH) ?: '/admin'; + $query = parse_url($url, PHP_URL_QUERY); + + return is_string($query) && $query !== '' ? $path.'?'.$query : $path; +} + +function spec374BrowserCopyScreenshot(string $name): void +{ + $filename = $name.'.png'; + $source = base_path('tests/Browser/Screenshots/'.$filename); + $targetDirectory = repo_path('specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots'); + + if (! is_dir($targetDirectory)) { + @mkdir($targetDirectory, 0755, true); + } + + if (! is_file($source)) { + $source = \Pest\Browser\Support\Screenshot::path($filename); + } + + for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) { + usleep(100_000); + clearstatcache(true, $source); + } + + if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) { + @copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$filename); + } +} diff --git a/apps/platform/tests/Feature/Filament/AuditLogDetailInspectionTest.php b/apps/platform/tests/Feature/Filament/AuditLogDetailInspectionTest.php index 83611e93..a0f12b6c 100644 --- a/apps/platform/tests/Feature/Filament/AuditLogDetailInspectionTest.php +++ b/apps/platform/tests/Feature/Filament/AuditLogDetailInspectionTest.php @@ -53,6 +53,7 @@ function auditLogDetailTestRecord(ManagedEnvironment $tenant, array $attributes ]); $audit = auditLogDetailTestRecord($tenant, [ + 'actor_email' => 'audit-operator-with-an-unusually-long-enterprise-identifier+proof@example.test', 'resource_id' => (string) $backupSet->getKey(), 'target_label' => $backupSet->name, 'summary' => 'Backup set created for Nightly iOS backup', @@ -65,6 +66,11 @@ function auditLogDetailTestRecord(ManagedEnvironment $tenant, array $attributes ->assertSee('Diagnostics - Collapsed') ->assertSee('Technical metadata') ->assertSee('Nightly iOS backup') + ->assertSee('audit-operator-with-an-unusually-long-enterprise-identifier+proof@example.test') + ->assertSeeHtml('data-testid="audit-selected-proof-cards"') + ->assertSeeHtml('sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-5') + ->assertSeeHtml('break-words') + ->assertSeeHtml('max-w-full') ->assertSee('Open backup set'); }); diff --git a/apps/platform/tests/Feature/Filament/AuditLogPageTest.php b/apps/platform/tests/Feature/Filament/AuditLogPageTest.php index f80c8c00..64c11850 100644 --- a/apps/platform/tests/Feature/Filament/AuditLogPageTest.php +++ b/apps/platform/tests/Feature/Filament/AuditLogPageTest.php @@ -70,6 +70,8 @@ function auditLogPageTestRecord(?ManagedEnvironment $tenant, array $attributes = ->assertSee('Which event proves what happened?') ->assertSee('Selected event proof') ->assertSee('Preselected audit detail') + ->assertSee('data-testid="audit-focus-proof-cards"', false) + ->assertSee('break-words', false) ->assertDontSee('Focused review lane'); }); diff --git a/apps/platform/tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php b/apps/platform/tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php new file mode 100644 index 00000000..e7bc5e1c --- /dev/null +++ b/apps/platform/tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php @@ -0,0 +1,144 @@ +actingAs($user); + setAdminEnvironmentContext($tenant); + bindFailHardGraphClient(); + + return [ + $user, + $tenant, + Livewire::actingAs($user)->test(EnvironmentDashboard::class), + ]; +} + +/** + * @return array + */ +function spec374DashboardHeaderActions(Testable $component): array +{ + $instance = $component->instance(); + + if ($instance->getCachedHeaderActions() === []) { + $instance->cacheInteractsWithHeaderActions(); + } + + return $instance->getCachedHeaderActions(); +} + +/** + * @return list + */ +function spec374DashboardPrimaryActionNames(Testable $component): array +{ + return collect(spec374DashboardHeaderActions($component)) + ->reject(static fn ($action): bool => $action instanceof ActionGroup) + ->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null) + ->filter() + ->values() + ->all(); +} + +/** + * @return list + */ +function spec374DashboardMoreActionNames(Testable $component): array +{ + $moreGroup = collect(spec374DashboardHeaderActions($component)) + ->first(static fn ($action): bool => $action instanceof ActionGroup && in_array($action->getLabel(), ['More', 'Mehr'], true)); + + return collect($moreGroup?->getActions() ?? []) + ->map(static fn ($action): ?string => $action instanceof Action ? $action->getName() : null) + ->filter() + ->values() + ->all(); +} + +it('keeps support diagnostics as the dashboard quick diagnostics entrypoint', function (): void { + [, $tenant, $component] = spec374DashboardComponent(); + + $component + ->assertActionVisible('openSupportDiagnostics') + ->assertActionEnabled('openSupportDiagnostics') + ->assertActionExists('openSupportDiagnostics', fn (Action $action): bool => $action->getLabel() === 'Open support diagnostics') + ->mountAction('openSupportDiagnostics') + ->assertMountedActionModalSee('Support diagnostics') + ->assertMountedActionModalSee('Quick support context') + ->assertMountedActionModalSee('Recommended first check') + ->assertMountedActionModalSee('Redacted support view') + ->assertMountedActionModalSee('Environment context') + ->assertMountedActionModalSee('Support scope') + ->assertMountedActionModalDontSee('Boundary') + ->assertMountedActionModalDontSee('Repair diagnostics'); + + expect(spec374DashboardPrimaryActionNames($component)) + ->not->toContain('openSupportDiagnostics') + ->not->toContain('openRepairDiagnostics') + ->and(spec374DashboardMoreActionNames($component)) + ->toContain('openSupportDiagnostics') + ->not->toContain('openRepairDiagnostics') + ->and(ManagedEnvironmentLinks::diagnosticsUrl($tenant))->toEndWith('/diagnostics'); +}); + +it('frames the direct diagnostics route as repair-only in the no-action state', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); + + $this->actingAs($user); + setAdminEnvironmentContext($tenant); + bindFailHardGraphClient(); + + Livewire::test(EnvironmentDiagnostics::class) + ->assertSee('Repair diagnostics') + ->assertSee('Checks supported TenantPilot access and membership repair conditions only') + ->assertSee('No repair diagnostics are active') + ->assertSee('No supported access or membership repair is active') + ->assertSee('not a generic environment health hub') + ->assertSee('Use Open support diagnostics') + ->assertActionVisible('openSupportDiagnostics') + ->assertActionEnabled('openSupportDiagnostics') + ->assertActionExists('openSupportDiagnostics', fn (Action $action): bool => $action->getLabel() === 'Open support diagnostics') + ->assertDontSee('All good') + ->assertDontSee('No known issues detected') + ->assertDontSee('provider connection blocked') + ->assertActionHidden('bootstrapOwner') + ->assertActionHidden('mergeDuplicateMemberships') + ->mountAction('openSupportDiagnostics') + ->assertMountedActionModalSee('Support diagnostics') + ->assertMountedActionModalSee('Recommended first check') + ->assertMountedActionModalSee('Redacted support view') + ->assertMountedActionModalSee('Support scope') + ->assertMountedActionModalDontSee('Boundary') + ->assertMountedActionModalDontSee('default-redacted'); +}); + +it('keeps repair diagnostics action-surface metadata aligned with the rendered noun', function (): void { + $surface = ActionSurfaceExemptions::spec193MonitoringSurface(EnvironmentDiagnostics::class); + $declaration = EnvironmentDiagnostics::actionSurfaceDeclaration(); + + expect($surface['canonicalNoun'] ?? null)->toBe('Repair diagnostics') + ->and($surface['exceptionReason'] ?? null)->toContain('membership/access repair surface') + ->and((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? '')) + ->toContain('Repair diagnostics') + ->toContain('singleton repair diagnostics surface'); +}); diff --git a/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php b/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php index 4018908e..201df175 100644 --- a/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php +++ b/apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php @@ -11,7 +11,6 @@ use Filament\Facades\Filament; use Illuminate\Database\Schema\Blueprint; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Livewire\Livewire; @@ -27,7 +26,11 @@ bindFailHardGraphClient(); Livewire::test(EnvironmentDiagnostics::class) - ->assertSee('No diagnostic action is required') + ->assertSee('No repair diagnostics are active') + ->assertSee('No supported access or membership repair is active') + ->assertSee('Use Open support diagnostics') + ->assertActionVisible('openSupportDiagnostics') + ->assertActionEnabled('openSupportDiagnostics') ->assertDontSee('All good') ->assertDontSee('No known issues detected') ->assertActionHidden('bootstrapOwner') @@ -72,11 +75,11 @@ ]); Livewire::test(EnvironmentDiagnostics::class) - ->assertSee('1 diagnostic blocker needs attention') + ->assertSee('1 repair diagnostic needs attention') ->assertSee('Recommended first check') ->assertSee('Duplicate memberships') ->assertSee('Merge duplicate access scopes') - ->assertDontSee('No diagnostic action is required') + ->assertDontSee('No repair diagnostics are active') ->assertActionVisible('mergeDuplicateMemberships') ->assertActionEnabled('mergeDuplicateMemberships') ->assertActionExists('mergeDuplicateMemberships', fn (Action $action): bool => $action->isConfirmationRequired()); @@ -94,7 +97,7 @@ ->set('missingOwner', true) ->set('hasDuplicateMembershipsForCurrentUser', true) ->assertSeeInOrder([ - '2 diagnostic blockers need attention', + '2 repair diagnostics need attention', 'Bootstrap owner', 'Duplicate memberships', 'Merge duplicate access scopes', diff --git a/apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php b/apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php index c736f826..8e1d0eb3 100644 --- a/apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php +++ b/apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php @@ -1733,7 +1733,7 @@ function actionSurfaceSystemPanelContext(array $capabilities): PlatformUser expect((string) ($declaration->slot(ActionSurfaceSlot::ListHeader)?->details ?? '')) ->toContain('repair actions') ->and((string) ($declaration->exemption(ActionSurfaceSlot::InspectAffordance)?->reason ?? '')) - ->toContain('singleton diagnostic surface'); + ->toContain('singleton repair diagnostics surface'); }); it('keeps the no-access page as a singleton recovery surface with a header action', function (): void { diff --git a/apps/platform/tests/Feature/Monitoring/Spec329EvidenceAuditDisclosureProductizationTest.php b/apps/platform/tests/Feature/Monitoring/Spec329EvidenceAuditDisclosureProductizationTest.php index a1370929..0b4ceca8 100644 --- a/apps/platform/tests/Feature/Monitoring/Spec329EvidenceAuditDisclosureProductizationTest.php +++ b/apps/platform/tests/Feature/Monitoring/Spec329EvidenceAuditDisclosureProductizationTest.php @@ -113,6 +113,9 @@ ->assertSee('Spec329 Operator') ->assertSee('Permission posture checked') ->assertSee('Permission posture report') + ->assertSee('data-testid="audit-selected-proof-cards"', false) + ->assertSee('sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-5', false) + ->assertSee('wrap-anywhere', false) ->assertSee(\App\Support\OperationRunLinks::tenantlessView($run), false) ->assertDontSee('raw payload should stay hidden') ->assertDontSee('provider secret should stay hidden') diff --git a/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php b/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php index 5d571bdc..d06b903a 100644 --- a/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php +++ b/apps/platform/tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php @@ -4,15 +4,15 @@ use App\Filament\Pages\Operations\TenantlessOperationRunViewer; use App\Models\AuditLog; +use App\Models\EnvironmentReview; use App\Models\EvidenceSnapshot; use App\Models\Finding; +use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironmentMembership; use App\Models\OperationRun; use App\Models\ProviderConnection; use App\Models\ReviewPack; use App\Models\StoredReport; -use App\Models\ManagedEnvironment; -use App\Models\EnvironmentReview; use App\Models\User; use App\Models\Workspace; use App\Models\WorkspaceMembership; @@ -150,6 +150,7 @@ function operationSupportDiagnosticsComponent(User $user, OperationRun $run): \L ->mountAction('openSupportDiagnostics') ->assertMountedActionModalSee('Support diagnostics') ->assertMountedActionModalSee('Recommended first check') + ->assertMountedActionModalSee('Operation context') ->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)) @@ -157,11 +158,16 @@ function operationSupportDiagnosticsComponent(User $user, OperationRun $run): \L ->assertMountedActionModalSee('Contoso Microsoft connection') ->assertMountedActionModalSee('High finding #'.$finding->getKey()) ->assertMountedActionModalSee('permission posture report') - ->assertMountedActionModalSee('ManagedEnvironment review #'.$review->getKey()) + ->assertMountedActionModalSee('Environment review #'.$review->getKey()) ->assertMountedActionModalSee('Review pack #'.$pack->getKey()) ->assertMountedActionModalSee('Operation failed') - ->assertMountedActionModalSee('default-redacted') + ->assertMountedActionModalSee('Redacted support view') + ->assertMountedActionModalSee('Support scope') + ->assertMountedActionModalSee('Read-only, redacted support view. Restricted provider details are excluded.') ->assertMountedActionModalSee('[REDACTED]') + ->assertMountedActionModalDontSee('Boundary') + ->assertMountedActionModalDontSee('Support diagnostics use a redacted support view. Secrets') + ->assertMountedActionModalDontSee('default-redacted') ->assertMountedActionModalDontSee('raw-provider-secret-message') ->assertMountedActionModalDontSee('secret-provider-body') ->assertMountedActionModalDontSee('stored-report-secret-body') diff --git a/apps/platform/tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php b/apps/platform/tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php index 80f95941..85ebe5ee 100644 --- a/apps/platform/tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php +++ b/apps/platform/tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php @@ -2,11 +2,12 @@ declare(strict_types=1); -use App\Filament\Pages\Operations\TenantlessOperationRunViewer; use App\Filament\Pages\EnvironmentDashboard; +use App\Filament\Pages\EnvironmentDiagnostics; +use App\Filament\Pages\Operations\TenantlessOperationRunViewer; +use App\Models\ManagedEnvironment; use App\Models\OperationRun; use App\Models\ProductUsageEvent; -use App\Models\ManagedEnvironment; use App\Models\User; use App\Support\OperationRunOutcome; use App\Support\OperationRunStatus; @@ -37,6 +38,14 @@ function operationDiagnosticsTelemetryComponent(User $user, OperationRun $run): return Livewire::actingAs($user)->test(TenantlessOperationRunViewer::class, ['run' => $run]); } +function repairDiagnosticsTelemetryComponent(User $user, ManagedEnvironment $tenant): \Livewire\Features\SupportTesting\Testable +{ + test()->actingAs($user); + setAdminEnvironmentContext($tenant); + + return Livewire::actingAs($user)->test(EnvironmentDiagnostics::class); +} + it('records telemetry when support diagnostics are opened from the tenant dashboard', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator'); @@ -62,6 +71,31 @@ function operationDiagnosticsTelemetryComponent(User $user, OperationRun $run): ->and($serializedEvent)->not->toContain((string) $tenant->name); }); +it('records telemetry when support diagnostics are opened from repair diagnostics', function (): void { + $tenant = ManagedEnvironment::factory()->create(); + [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator'); + + repairDiagnosticsTelemetryComponent($user, $tenant) + ->mountAction('openSupportDiagnostics') + ->assertMountedActionModalSee('Support diagnostics'); + + $event = ProductUsageEvent::query()->sole(); + $serializedEvent = json_encode($event->toArray(), JSON_THROW_ON_ERROR); + + expect($event->event_name)->toBe(ProductUsageEventCatalog::SUPPORT_DIAGNOSTICS_OPENED) + ->and($event->managed_environment_id)->toBe((int) $tenant->getKey()) + ->and($event->workspace_id)->toBe((int) $tenant->workspace_id) + ->and($event->user_id)->toBe((int) $user->getKey()) + ->and($event->subject_type)->toBe('tenant') + ->and($event->subject_id)->toBe((string) $tenant->getKey()) + ->and($event->metadata)->toBe([ + 'source_surface' => 'repair_diagnostics', + ]) + ->and($serializedEvent)->not->toContain('@') + ->and($serializedEvent)->not->toContain($user->name) + ->and($serializedEvent)->not->toContain((string) $tenant->name); +}); + it('records telemetry when support diagnostics are opened from the canonical operation viewer', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'operator'); @@ -95,4 +129,4 @@ function operationDiagnosticsTelemetryComponent(User $user, OperationRun $run): ->and($serializedEvent)->not->toContain('@') ->and($serializedEvent)->not->toContain($user->name) ->and($serializedEvent)->not->toContain((string) $tenant->name); -}); \ No newline at end of file +}); diff --git a/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php b/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php index c4b5d693..c899e999 100644 --- a/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php +++ b/apps/platform/tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php @@ -4,24 +4,24 @@ use App\Filament\Pages\EnvironmentDashboard; use App\Models\AuditLog; +use App\Models\EnvironmentReview; use App\Models\EvidenceSnapshot; use App\Models\Finding; +use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironmentMembership; use App\Models\OperationRun; use App\Models\ProviderConnection; use App\Models\ReviewPack; use App\Models\StoredReport; -use App\Models\ManagedEnvironment; -use App\Models\EnvironmentReview; use App\Models\User; use App\Models\WorkspaceMembership; use App\Support\Auth\UiTooltips; +use App\Support\EnvironmentReviewStatus; use App\Support\OperationRunOutcome; use App\Support\OperationRunStatus; use App\Support\OperationRunType; use App\Support\Providers\ProviderReasonCodes; use App\Support\Providers\ProviderVerificationStatus; -use App\Support\EnvironmentReviewStatus; use App\Support\Workspaces\WorkspaceContext; use Filament\Actions\Action; use Livewire\Livewire; @@ -145,6 +145,7 @@ function tenantSupportDiagnosticsComponent(User $user, ManagedEnvironment $tenan ->mountAction('openSupportDiagnostics') ->assertMountedActionModalSee('Support diagnostics') ->assertMountedActionModalSee('Recommended first check') + ->assertMountedActionModalSee('Environment context') ->assertMountedActionModalSee('Check provider connection first') ->assertMountedActionModalSee('Start with the provider connection and required access') ->assertMountedActionModalSee('Contoso Support ManagedEnvironment') @@ -153,11 +154,16 @@ function tenantSupportDiagnosticsComponent(User $user, ManagedEnvironment $tenan ->assertMountedActionModalSee('Operation #'.$run->getKey()) ->assertMountedActionModalSee('High finding #'.$finding->getKey()) ->assertMountedActionModalSee('permission posture report') - ->assertMountedActionModalSee('ManagedEnvironment review #'.$review->getKey()) + ->assertMountedActionModalSee('Environment review #'.$review->getKey()) ->assertMountedActionModalSee('Review pack #'.$pack->getKey()) ->assertMountedActionModalSee('Operation failed') - ->assertMountedActionModalSee('default-redacted') + ->assertMountedActionModalSee('Redacted support view') + ->assertMountedActionModalSee('Support scope') + ->assertMountedActionModalSee('Read-only, redacted support view. Restricted provider details are excluded.') ->assertMountedActionModalSee('[REDACTED]') + ->assertMountedActionModalDontSee('Boundary') + ->assertMountedActionModalDontSee('Support diagnostics use a redacted support view. Secrets') + ->assertMountedActionModalDontSee('default-redacted') ->assertMountedActionModalDontSee('raw-provider-secret-message') ->assertMountedActionModalDontSee('secret-provider-body') ->assertMountedActionModalDontSee('stored-report-secret-body') diff --git a/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php b/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php index 8a6b35c6..f8e193ec 100644 --- a/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php +++ b/apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php @@ -3,9 +3,9 @@ declare(strict_types=1); use App\Models\Finding; +use App\Models\ManagedEnvironment; use App\Models\OperationRun; use App\Models\ProviderConnection; -use App\Models\ManagedEnvironment; use App\Models\Workspace; use App\Support\OperationRunOutcome; use App\Support\OperationRunStatus; @@ -119,6 +119,27 @@ ->and($firstCheck['reference']['url'])->toBeNull(); }); +it('uses calm support-note copy when only findings are not observed', function (): void { + $method = new \ReflectionMethod(SupportDiagnosticBundleBuilder::class, 'completenessNote'); + + $note = $method->invoke(app(SupportDiagnosticBundleBuilder::class), [ + [ + 'label' => 'Overview', + 'availability' => 'available', + ], + [ + 'label' => 'Findings', + 'availability' => 'missing', + ], + [ + 'label' => 'Audit history', + 'availability' => 'redacted', + ], + ]); + + expect($note)->toBe('No related finding observed.'); +}); + 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']); @@ -137,12 +158,53 @@ 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') + ->toContain('Start with the provider connection and required access') ->and($bundle['summary']['recommended_first_check']['reference']['label']) - ->toBe('Untranslated reason connection') + ->toBe('Untranslated reason connection') ->and($bundleJson)->not->toContain('ext.support.manual_lookup_needed'); }); +it('keeps raw database exception details out of support diagnostic bundle copy', function (): void { + $tenant = ManagedEnvironment::factory()->create(['name' => 'Raw Exception ManagedEnvironment']); + $rawException = 'SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "environment_reviews_fingerp"'; + + $run = OperationRun::factory() + ->forTenant($tenant) + ->create([ + 'type' => OperationRunType::EnvironmentReviewCompose->value, + 'status' => OperationRunStatus::Completed->value, + 'outcome' => OperationRunOutcome::Failed->value, + 'failure_summary' => [[ + 'message' => $rawException, + ]], + 'completed_at' => now()->subMinutes(3), + ]); + + $builder = app(SupportDiagnosticBundleBuilder::class); + $tenantBundle = $builder->forTenant($tenant); + $runBundle = $builder->forOperationRun($run); + $tenantOperationSection = collect($tenantBundle['sections'])->firstWhere('key', 'operation_context'); + $runOperationSection = collect($runBundle['sections'])->firstWhere('key', 'operation_context'); + $bundleJson = json_encode([$tenantBundle, $runBundle], JSON_THROW_ON_ERROR); + + expect($tenantBundle['dominant_issue']) + ->toBe('The operation failed and needs follow-up.'); + + foreach ([ + $tenantBundle['dominant_issue'], + $tenantOperationSection['summary'], + $runBundle['headline'], + $runBundle['dominant_issue'], + $runOperationSection['summary'], + $bundleJson, + ] as $supportCopy) { + expect((string) $supportCopy) + ->not->toContain('SQLSTATE') + ->not->toContain('duplicate key value violates') + ->not->toContain('environment_reviews_fingerp'); + } +}); + it('marks references without canonical destinations as inaccessible', function (): void { $tenant = ManagedEnvironment::factory()->create(); $connection = ProviderConnection::factory()->create([ @@ -166,3 +228,15 @@ ->and($reference['availability'])->toBe('inaccessible') ->and($reference['access_reason'])->toBe('Canonical destination is not available from this context.'); }); + +it('renders support diagnostic reference links as safe modal drilldowns', function (): void { + $tenant = ManagedEnvironment::factory()->create(['name' => 'Linked Modal ManagedEnvironment']); + + $bundle = app(SupportDiagnosticBundleBuilder::class)->forTenant($tenant); + $html = view('filament.modals.support-diagnostic-bundle', ['bundle' => $bundle])->render(); + + expect($html) + ->toContain('Open environment') + ->toContain('target="_blank"') + ->toContain('rel="noopener noreferrer"'); +}); 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 index b5b37bfa..b8b1e2ea 100644 --- 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 @@ -1,33 +1,34 @@ -# UI-012 Environment Diagnostics +# UI-012 Environment Repair Diagnostics | Field | Value | | --- | --- | | Route | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | | Source | `EnvironmentDiagnostics` | -| Area / scope | Support diagnostics / environment | +| Area / scope | Repair 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. | +| Screenshot | `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png` planned after Spec 374 browser smoke; previous proof remains `specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png`. | +| Browser status | Spec 373 smoke-login fixture reached no-action state. Spec 374 narrows the canonical noun and copy to Repair diagnostics and adds secondary support-modal proof. | ## 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. +The page now leads with one repair-diagnostics summary: current blocker count or no-action state, impact, recommended first check, and the dominant repair path when a repair is applicable. A secondary `Open support diagnostics` action is available for broader read-only support context. ## Productization Review -- Decision-first: improved; the page no longer opens with a generic "all good" card or competing repair messages. +- Decision-first: improved; the page no longer opens with a generic "all good" card, broad health claim, 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. +- Context: environment-owned repair 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. +- Diagnostics: provider, evidence, system, and technical details stay out of the repair page 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 calm repair-only no-action summary when no membership repair applies +- one secondary read-only `Open support diagnostics` modal handoff for broader provider, operation, evidence, or audit context - 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 @@ -45,9 +46,9 @@ ## Scores ## 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. +2. Broader support workflows remain outside UI-012; the page can hand off to the support modal, but provider/evidence/system remediation should stay on separate 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. +Implemented in Spec 373 as a bounded hierarchy pass over the existing diagnostics page, then narrowed by Spec 374 into the Repair diagnostics entrypoint contract. Future work should address broader support workflows only through separate provider, evidence, system, or support-handoff specs. diff --git a/docs/ui-ux-enterprise-audit/route-inventory.md b/docs/ui-ux-enterprise-audit/route-inventory.md index 547ea3b2..ec0aab63 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 | [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-012 | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | route + page | Environment Repair Diagnostics | Support | environment-bound | route exists | environment entitlement | Support / Diagnostics | Provider / Integration | Domain Pattern Surface | repo-verified | [desktop](../../specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png) planned; prior [Spec 373 proof](../../specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png) | [report](page-reports/ui-012-environment-diagnostics.md) | Repair diagnostics must remain secondary and limited to supported access or membership repair cases; its read-only support modal is a handoff, while Environment Dashboard remains the official quick diagnostics entrypoint. | | 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/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-clear.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-clear.png index 36492378..7ffa153c 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-clear.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-clear.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-reload.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-reload.png index 36492378..7ffa153c 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-reload.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--after-reload.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--clean.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--clean.png index c9eb3d5b..971b2c0d 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--clean.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--clean.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--filtered.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--filtered.png index af99444c..7d2ec8a1 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--filtered.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-audit-log--filtered.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-clear.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-clear.png index ff5e830a..55dfebe7 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-clear.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-clear.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-reload.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-reload.png index ff5e830a..55dfebe7 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-reload.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--after-reload.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--clean.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--clean.png index a57e8af0..2655531c 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--clean.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--clean.png differ diff --git a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--filtered.png b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--filtered.png index e583b7e8..896758e8 100644 Binary files a/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--filtered.png and b/specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots/spec329-evidence-overview--filtered.png differ diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/affected-files.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/affected-files.md new file mode 100644 index 00000000..e2536a1d --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/affected-files.md @@ -0,0 +1,37 @@ +# Affected Files + +Status: final implementation inventory. + +| File | Purpose | Change type | Surface affected | Runtime/tooling/spec | Verification level | Risk | Out-of-scope side effects | +|---|---|---|---|---|---|---|---| +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md` | Governing feature specification | created | N/A | spec | repo-created | low | none | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/plan.md` | Implementation plan | created | N/A | spec | repo-created | low | none | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/tasks.md` | Implementation task list | created | N/A | spec | repo-created | low | none | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/checklists/requirements.md` | Preparation/readiness checklist | created | N/A | spec | repo-created | low | none | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/source-audit-summary.md` | Repo truth audit | created | N/A | spec artifact | repo-created | low | none | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/diagnostic-entrypoint-matrix.md` | Entrypoint decision matrix | created | N/A | spec artifact | repo-created | low | none | +| `apps/platform/app/Filament/Pages/EnvironmentDashboard.php` | Visible quick diagnostics entry | unchanged | Environment Dashboard | runtime | repo-verified existing | low | no hub/nav/repair link added | +| `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` | Repair diagnostics page state/actions | updated | Repair Diagnostics | runtime | repo-verified existing | medium | title/subheading, repair-only summary copy, and secondary read-only support modal; repair action safety preserved | +| `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` | Repair/no-action presentation | unchanged | Repair Diagnostics | runtime | repo-verified existing | low | existing summary Blade already fit the new state payload | +| `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` | Support modal purpose/first-check presentation | updated | Support Diagnostics modal | runtime | repo-verified existing | medium | quick support context purpose, redacted support label, and status/why/observed/action structure added; no raw leakage | +| `apps/platform/app/Support/ManagedEnvironmentLinks.php` | Repair diagnostics helper | unchanged | link helper | runtime | repo-verified existing | low | retained as canonical deeplink helper | +| `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` | Support diagnostics bundle copy/redaction | updated | Support Diagnostics modal | runtime | repo-verified existing | medium | operation-run technical exception fragments and internal environment-review wording are normalized out of header/summary/modal copy | +| `apps/platform/app/Support/RedactionIntegrity.php` | Shared redaction note copy | updated | Support Diagnostics modal | runtime | repo-verified existing | low | visible support note says redacted support view while internal redaction mode remains `default_redacted` | +| `apps/platform/app/Support/Ui/ActionSurface/ActionSurfaceExemptions.php` | Action-surface canonical noun | updated | Repair Diagnostics | runtime | repo-verified existing | medium | metadata aligned from ManagedEnvironment diagnostics to Repair diagnostics | +| `apps/platform/lang/en/localization.php` | Dashboard modal description | updated | Support Diagnostics modal | runtime | repo-verified existing | low | quick support context purpose copy | +| `apps/platform/lang/de/localization.php` | Dashboard modal description | updated | Support Diagnostics modal | runtime | repo-verified existing | low | localized quick support context purpose copy | +| `apps/platform/tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php` | Spec 374 feature coverage | added | Dashboard / Support modal / Repair Diagnostics | test | new | low | focused Feature/Livewire proof | +| `apps/platform/tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php` | Spec 374 browser smoke | added | Dashboard / Support modal / Repair Diagnostics | test | new | medium | bounded browser coverage and screenshots | +| `apps/platform/tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php` | Support diagnostics bundle regression coverage | updated | Support Diagnostics modal | test | existing | low | asserts SQLSTATE/constraint fragments stay out of support bundle copy | +| `apps/platform/tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` | Existing repair diagnostics regression | updated | Repair Diagnostics | test | existing | low | expected copy updated | +| `apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php` | Existing action-surface guard | updated | Repair Diagnostics | test | existing | low | expected metadata wording updated | +| `apps/platform/tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php` | Support diagnostics telemetry coverage | updated | Repair Diagnostics / Support Diagnostics modal | test | existing | low | asserts repair-page modal opens record `repair_diagnostics` source telemetry | +| `docs/ui-ux-enterprise-audit/page-reports/ui-002-environment-dashboard.md` | UI audit evidence | unchanged | Environment Dashboard | documentation | existing | low | dashboard semantics unchanged; no page-report churn | +| `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` | UI audit evidence | updated | Repair Diagnostics | documentation | existing | low | canonical noun and Spec 374 screenshot plan updated | +| `docs/ui-ux-enterprise-audit/route-inventory.md` | UI coverage registry | updated | Repair Diagnostics | documentation | existing | low | UI-012 noun/screenshot reference updated | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/*.png` | Browser proof | added | Dashboard / Support modal / Repair Diagnostics | artifact | browser-generated | low | four screenshots copied from Pest browser run | + +## Out-Of-Scope Side Effects + +- No Provider Connections, Required Permissions, System panel, Evidence Snapshot, Customer Review, OperationRun lifecycle, provider gateway, Graph contract, migration, package, env, queue, scheduler, storage, panel provider, global search, or navigation runtime files were changed. +- `.codex/config.toml` had an unrelated local change in the worktree and was not edited as part of Spec 374. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/before-after-screenshot-index.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/before-after-screenshot-index.md new file mode 100644 index 00000000..6a3e80c6 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/before-after-screenshot-index.md @@ -0,0 +1,25 @@ +# Before / After Screenshot Index + +Status: complete. + +## Source Evidence + +| Surface | Before / baseline source | Notes | +|---|---|---| +| Environment Dashboard diagnostic entry | Spec 373 and existing Environment Dashboard screenshots | Dashboard already exposed `Open support diagnostics` in More actions. | +| Support Diagnostics modal | `specs/373-diagnostic-surface-separation/artifacts/screenshots/002-support-diagnostics-after-or-blocked.png` | Spec 374 adds explicit quick support context purpose copy, redacted support labeling, compact support scope copy, status/why/observed/action section structure, and safe reference drilldowns. | +| Environment Diagnostics / Repair Diagnostics | `specs/373-diagnostic-surface-separation/artifacts/screenshots/001-environment-diagnostics-after.png` | Spec 374 narrows canonical noun and no-action copy to Repair diagnostics. | + +## Spec 374 After Evidence + +| Screenshot | Surface | Verification | +|---|---|---| +| `artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png` | Environment Dashboard More action | Support Diagnostics is visible as the dashboard quick diagnostic entry; Repair Diagnostics is not promoted. | +| `artifacts/screenshots/002-support-diagnostics-modal-after.png` | Support Diagnostics modal | Modal shows quick support context purpose, recommended first check, `Redacted support view`, compact `Support scope`, no prominent internal `Boundary` note, and safe reference drilldown links before lower sections. | +| `artifacts/screenshots/003-environment-repair-diagnostics-after.png` | Direct Repair Diagnostics route | Page title and first viewport use Repair diagnostics copy. | +| `artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png` | Repair Diagnostics support handoff | No supported repair diagnostics active; secondary `Open support diagnostics` action opens the redacted support modal with compact support scope copy. | + +## Blocked / Not Captured + +- Repair/blocker browser state: not captured because the current browser fixture does not safely create duplicate membership or missing-owner states. +- Provider Connections, Required Permissions, System panel, Evidence Snapshot, and Customer Review surfaces: not captured because they are out of scope and no shared runtime files changed them. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/browser-verification-report.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/browser-verification-report.md new file mode 100644 index 00000000..4738c7d2 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/browser-verification-report.md @@ -0,0 +1,37 @@ +# Browser Verification Report + +Status: complete. + +## Fixture + +- Browser test: `apps/platform/tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php` +- Fixture creation: existing `createUserWithTenant(role: 'owner', workspaceRole: 'manager')` +- User: generated by factory during the browser test +- Environment: `Spec374 Diagnostics Environment` +- Login path: existing `/admin/local/smoke-login` +- Browser command: `php artisan test --compact tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php` +- Result: passed, 1 test, 36 assertions + +## Tested Paths + +| Target | Result | Evidence | +|---|---|---| +| Environment Dashboard diagnostic entry | PASS | Opened dashboard, expanded More actions, saw `Open support diagnostics`, did not see `Repair diagnostics` as a dashboard action, screenshot `001-environment-dashboard-diagnostic-entry-after.png` | +| Support Diagnostics modal | PASS | Opened modal from dashboard action, saw `Support diagnostics`, `Quick support context`, `Recommended first check`, `Redacted support view`, and compact `Support scope` copy; did not see the internal `Boundary` note; reference links expose safe drilldown attributes, screenshot `002-support-diagnostics-modal-after.png` | +| Support Diagnostics technical exception redaction | PASS | Reopened the user-commented `Spec 352 Audit Provider Blocker` modal after the fix; the header showed `The operation failed and needs follow-up.` and did not show `SQLSTATE`, duplicate-key text, or `environment_reviews_fingerp`. | +| Direct Repair Diagnostics route | PASS | Opened `/admin/workspaces/{workspace}/environments/{environment}/diagnostics`, saw `Repair diagnostics` and no-action repair-only copy, screenshot `003-environment-repair-diagnostics-after.png` | +| Repair Diagnostics support handoff | PASS | Confirmed no supported access or membership repair was active, opened `Open support diagnostics` from the repair page, and saw the redacted support modal with compact `Support scope`; reference links expose safe drilldown attributes, screenshot `004-environment-repair-diagnostics-support-modal-after.png` | +| Integrated Browser current fixture | PASS | Opened `Spec 352 Audit Provider Blocker` Repair Diagnostics in the Integrated Browser, opened `Open support diagnostics`, saw `Redacted support view` and `Support scope`, confirmed no visible `default-redacted`, `SQLSTATE`, `environment_reviews_fingerp`, or internal `Boundary` support-note block, confirmed Status / Why relevant / Observed / Action labels were present, and clicked `Inspect event` through to Audit Log selected-event detail. | +| Browser console/runtime | PASS | Browser assertions reported no JavaScript errors and no console logs on the scoped flow | + +## Screenshot Outputs + +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/002-support-diagnostics-modal-after.png` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png` + +## Limitations + +- A repair/blocker browser state was not created. The existing smoke fixture does not safely create duplicate membership or missing-owner states, and Spec 374 forbids broad fixture/auth repair. +- Missing-owner and duplicate-membership states remain covered by Feature/Livewire tests in `TenantDiagnosticsRepairsTest.php`. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/diagnostic-entrypoint-matrix.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/diagnostic-entrypoint-matrix.md new file mode 100644 index 00000000..7b4501cc --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/diagnostic-entrypoint-matrix.md @@ -0,0 +1,38 @@ +# Diagnostic Entrypoint Matrix + +Status: implementation matrix complete. + +| Diagnostic need | Official entrypoint | Secondary entrypoint | Surface type | Audience | Reason | In scope? | Follow-up if deferred | +|---|---|---|---|---|---|---|---| +| Support needs quick environment context | Support Diagnostics modal from Environment Dashboard | OperationRun support diagnostics when run context owns the question | support-only, official-entrypoint | operator/support | Existing modal and bundle are visible and repo-backed | yes | none | +| Environment has repairable membership issue | Environment Repair Diagnostics page | Secondary read-only Support Diagnostics modal from the repair page | repair-only, secondary-entrypoint | operator/support | Existing `EnvironmentDiagnostics` repair actions own repair truth; Support Diagnostics owns broader context | yes | none | +| Environment has no owner | Environment Repair Diagnostics page | Environment Dashboard secondary repair link only if repo-backed and not primary | repair-only | operator/support | Existing `bootstrapOwner` action is a repair path | yes | none | +| Current user has duplicate membership | Environment Repair Diagnostics page | Environment Dashboard secondary repair link only if repo-backed and not primary | repair-only | operator/support | Existing `mergeDuplicateMemberships` action is a repair path | yes | none | +| No repair issue is active | Environment Repair Diagnostics no-action state | Secondary read-only Support Diagnostics modal from the repair page | repair-only, no-action | operator/support | The page should not pretend to check all health, but it can hand off to support context | yes | none | +| Provider connection blocked | Provider Connections / provider readiness destination | Support Diagnostics modal can show related context | deferred / provider-owned | operator/support | Provider readiness is not membership repair diagnostics | no | Provider/Permission Diagnostic Productization if fresh gap exists | +| Required permissions missing | Required Permissions / provider readiness destination | Support Diagnostics modal can show related context | deferred / provider-owned | operator/support | Permission remediation belongs to provider/permissions surfaces | no | Provider/Permission Diagnostic Productization if fresh gap exists | +| Evidence snapshot not reachable | Evidence Snapshot / Stored Report / Review Pack surfaces | Support Diagnostics modal can show availability context if repo-backed | deferred / evidence-owned | operator/auditor/support | Evidence is proof, not repair diagnostics | no | Evidence browser fixture or evidence productization follow-up | +| System-level operation issue | `/system` platform surfaces | OperationRun detail where applicable | deferred / system-owned | platform_admin/support | System auth/reachability is not environment repair scope | no | System panel browser fixture/auth follow-up | +| Customer asks for review evidence | Customer Review Workspace / Review Pack | Evidence or Stored Report detail | customer/auditor-owned | customer/operator/auditor | Customer-safe review consumption is not support diagnostics | no | Customer review/evidence productization specs | +| OperationRun failed or blocked | OperationRun detail / Operations hub | Support Diagnostics modal from run detail when available | diagnostics-secondary | operator/support | OperationRun detail owns execution truth | no runtime change expected | OperationRun follow-up only if link/action regression appears | +| Billing or entitlement diagnostics | Commercial entitlement surfaces | Support diagnostics only as redacted support context when existing | deferred / commercial-owned | operator/support | Not an environment repair concern | no | Commercial lifecycle follow-up | + +## Entrypoint Class Definitions + +- `official-entrypoint`: the product-preferred starting path for the diagnostic need. +- `secondary-entrypoint`: allowed but not the main path. +- `deeplink-only`: route can be used from targeted context but is not broadly promoted. +- `repair-only`: surface handles concrete repairable membership/access problems only. +- `support-only`: surface provides redacted operator/support context, not customer-default content. +- `deferred`: explicitly outside Spec 374 implementation. + +## Implementation Rule + +If a need maps to `deferred`, the implementation may mention or link only when existing repo truth already provides a safe destination. It must not implement the deferred diagnostic domain inside Spec 374. + +## Implementation Outcome + +- Dashboard: `Open support diagnostics` remains the only dashboard diagnostic action in the More group. +- Repair page: `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` now renders as `Repair diagnostics` and exposes one secondary read-only `Open support diagnostics` modal action. +- Helper: `ManagedEnvironmentLinks::diagnosticsUrl()` is documented as a retained deeplink utility; no dashboard runtime caller was added. +- Browser proof: no-action repair state and the repair-page Support Diagnostics modal were reachable; repair/blocker browser state was not created because the existing smoke fixture does not safely create duplicate membership or missing-owner states. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/follow-up-recommendations.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/follow-up-recommendations.md new file mode 100644 index 00000000..eee7b9ab --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/follow-up-recommendations.md @@ -0,0 +1,24 @@ +# Follow-Up Recommendations + +Status: complete. + +## No Immediate Follow-Up Required + +Spec 374 closes the active entrypoint ambiguity without adding a hub, navigation branch, provider checks, repair logic, or new framework. + +## Deferred Candidates + +| Candidate | Trigger | Recommended Handling | +|---|---|---| +| Provider/Permission Diagnostic Productization | Fresh evidence shows Provider Connections or Required Permissions no longer answer provider readiness questions clearly. | Separate follow-up spec; do not place provider readiness inside Repair Diagnostics. | +| Evidence/System Browser Fixture Coverage | Browser reachability remains blocked for Evidence Snapshot or `/system` surfaces and product priority warrants proof. | Separate fixture/auth coverage spec. | +| UI Bloat Regression Guard | Repeated specs keep adding diagnostic/support entrypoints without shared review automation. | Separate guard/spec after one more confirmed recurring pattern. | +| Support Desk / PSA Handoff Productization | Support diagnostics needs lifecycle or external handoff behavior beyond current redacted context. | Separate support workflow spec; do not expand Support Diagnostics modal inside this slice. | +| Repair-Blocker Browser Fixture | Manual review requires screenshot proof for duplicate membership or missing-owner repair states. | Add only if an existing fixture pattern can create the state safely; otherwise keep Feature/Livewire coverage. | + +## Explicitly Not Recommended From This Spec + +- Do not build a Diagnostic Hub. +- Do not add Environment Repair Diagnostics to top-level navigation. +- Do not move provider, permission, evidence, customer review, system, or OperationRun truth into the repair page. +- Do not add Graph/provider HTTP calls to render paths. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/implementation-notes.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/implementation-notes.md new file mode 100644 index 00000000..7fc81dce --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/implementation-notes.md @@ -0,0 +1,50 @@ +# Implementation Notes + +Status: complete. + +## Product Decisions + +- Support Diagnostics remains the official quick diagnostics entrypoint from Environment Dashboard. +- Environment `/diagnostics` remains a secondary route and now renders as `Repair diagnostics`. +- Repair Diagnostics provides one secondary read-only `Open support diagnostics` action for broader support context without promoting the repair page as a diagnostic hub. +- No dashboard repair diagnostics link was added because the no-action state has no repo-backed repair value to promote. +- `ManagedEnvironmentLinks::diagnosticsUrl()` remains a retained canonical deeplink utility for future targeted repair contexts and route-contract tests. + +## Runtime Changes + +- `EnvironmentDiagnostics` now sets the page title to `Repair diagnostics` and adds a repair-only subheading. +- The no-action state now says no repair diagnostics are active and points broader provider, operation, evidence, and audit context to `Open support diagnostics`. +- `EnvironmentDiagnostics` now exposes `Open support diagnostics` as a secondary read-only modal action with support-diagnostics capability gating, audit logging, and `repair_diagnostics` telemetry. +- Blocker headings now use repair-diagnostics language instead of generic diagnostic blocker language. +- `ActionSurfaceExemptions` now uses `Repair diagnostics` as the canonical noun for the singleton diagnostic exception. +- The support diagnostics modal now states that it is quick support context before the recommended first check and lower sections, renders the redaction state as `Redacted support view`, demotes the redaction boundary to a compact `Support scope` line, structures lower sections by status, why relevant, observed, and action, and renders reference drilldowns as safe links that leave the modal context. +- Support diagnostics bundle copy now normalizes raw OperationRun technical exception fragments, including SQLSTATE and database constraint text, out of header and summary copy while preserving the operation reference. +- The dashboard support diagnostics action stayed in the existing More action group; no new navigation or hub card was added. + +## Safety Preservation + +- Existing `bootstrapOwner` and `mergeDuplicateMemberships` actions remain `Action::make(...)->action(...)`, `->requiresConfirmation()`, `UiEnforcement`-controlled, `Capabilities::TENANT_MANAGE`-gated, service-owned, and audit-covered. +- Page and modal rendering remain DB-local. Feature tests bind the fail-hard Graph client on the dashboard and repair diagnostics paths. +- Support bundle redaction now explicitly covers database exception fragments in addition to provider payloads, credentials, unrestricted log excerpts, and raw report payloads. +- No provider, permission, system, evidence, OperationRun lifecycle, support request, PSA, AI, export, migration, package, route, or panel-provider behavior was added. + +## UI / Native Primitive Notes + +- Existing Filament Page, Action, ActionGroup, modal, section, badge, and link primitives were reused. +- The Blade changes stay inside the existing support diagnostics modal and use the same Tailwind text, grid, section, badge, link, and dark-mode conventions already present in the file. +- Support modal reference links use Filament link output with explicit safe-drilldown attributes (`target="_blank"` and `rel="noopener noreferrer"`). +- No ad-hoc status colors, button system, card framework, or diagnostic resolver was introduced. + +## Test Evidence + +- `php artisan test --compact tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php`: 3 passed, 32 assertions. +- `php artisan test --compact --filter=DiagnosticEntry`: 4 passed, 51 assertions. +- `php artisan test --compact tests/Feature/Filament/TenantDiagnosticsRepairsTest.php`: 6 passed, 51 assertions. +- `php artisan test --compact --filter=SupportDiagnostics`: 39 passed, 228 assertions. +- `php artisan test --compact tests/Feature/Guards/ActionSurfaceContractTest.php --filter='tenant diagnostics'`: 1 passed, 3 assertions. +- `php artisan test --compact tests/Feature/Guards/Spec193MonitoringSurfaceHierarchyGuardTest.php`: 4 passed, 35 assertions. +- `php artisan test --compact tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php tests/Feature/Navigation/Spec322EnvironmentCtaUrlContractTest.php`: 3 passed, 181 assertions. +- `php artisan test --compact tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php tests/Feature/Filament/TenantDiagnosticsRepairsTest.php tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleRedactionTest.php`: 27 passed, 269 assertions. +- `php artisan test --compact tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php`: 1 passed, 36 assertions. +- `php vendor/bin/pint --dirty`: passed. +- `git diff --check`: passed. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png new file mode 100644 index 00000000..af77e608 Binary files /dev/null and b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png differ diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/002-support-diagnostics-modal-after.png b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/002-support-diagnostics-modal-after.png new file mode 100644 index 00000000..58b1b378 Binary files /dev/null and b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/002-support-diagnostics-modal-after.png differ diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png new file mode 100644 index 00000000..7a02a92e Binary files /dev/null and b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png differ diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-no-action-after.png b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-no-action-after.png new file mode 100644 index 00000000..aeabc90b Binary files /dev/null and b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-no-action-after.png differ diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png new file mode 100644 index 00000000..5d58002d Binary files /dev/null and b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png differ diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/source-audit-summary.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/source-audit-summary.md new file mode 100644 index 00000000..a6bdf427 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/source-audit-summary.md @@ -0,0 +1,71 @@ +# Source Audit Summary + +Status: implementation audit complete. + +## Repo Safety + +- Active branch before Spec Kit execution: `platform-dev`. +- Active branch after Spec Kit execution: `374-diagnostic-entry-point-support-diagnostics-consolidation`. +- Base HEAD before preparation: `94877c9a feat(ui): implement diagnostic surface separation (#444)`. +- Initial dirty state: clean. +- Initial implementation dirty state included the untracked active Spec 374 package and an unrelated `.codex/config.toml` local change. The `.codex/config.toml` change was left untouched. +- Runtime implementation stayed inside the scoped dashboard/support/repair diagnostics files, focused tests, proportional UI audit docs, and Spec 374 artifacts. + +## Source Inputs + +| Source | Availability | Verification Class | Use In Spec 374 | +|---|---|---|---| +| User-provided Spec 374 draft | available | user-provided | Primary candidate scope | +| Spec 373 `spec.md`, `plan.md`, `tasks.md` | available | repo-verified completed context | Predecessor scope and boundaries | +| Spec 373 artifacts | available | repo-verified / browser-verified where recorded | Implementation truth for diagnostic hierarchy | +| Spec 370 IA artifacts | available | repo-verified completed contract | Decision-first and diagnostic-surface rules | +| Current routes in `apps/platform/routes/web.php` | available | repo-verified | Confirms dashboard and diagnostics routes | +| `EnvironmentDiagnostics` page | available | repo-verified | Confirms direct route, non-navigation registration, repair actions | +| `EnvironmentDashboard` page | available | repo-verified | Confirms `openSupportDiagnosticsAction()` exists | +| Support diagnostics modal view | available | repo-verified | Confirms recommended first check rendering exists | +| `ManagedEnvironmentLinks::diagnosticsUrl()` | available | repo-verified | Helper exists; implementation search found route-contract tests plus the helper definition, no runtime dashboard caller | + +## Completed-Spec Guardrail + +| Related spec | Completion signal | Treatment | +|---|---|---| +| Spec 370 Global Surface IA Contract | completed contract artifacts | consumed as contract only | +| Spec 371 Core Operator View Surfaces Productization | active artifact references and browser/validation context in repo | pattern/context only | +| Spec 372 Customer Auditor Surface Safety Pass | active artifact references and browser/validation context in repo | pattern/context only | +| Spec 373 Diagnostic Surface Separation | checked tasks, validation report, browser report, screenshots, and implementation artifacts | completed predecessor; do not rewrite | + +## Current Runtime Truth Observed During Preparation + +- `web.php` registers `/admin/workspaces/{workspace}/environments/{environment:slug}` for `EnvironmentDashboard`. +- `web.php` registers `/admin/workspaces/{workspace}/environments/{environment:slug}/diagnostics` for `EnvironmentDiagnostics`. +- `EnvironmentDiagnostics` sets `protected static bool $shouldRegisterNavigation = false`. +- `EnvironmentDiagnostics` exposes repair actions `bootstrapOwner` and `mergeDuplicateMemberships` only when applicable. +- Existing repair actions use `Action::make(...)->action(...)`, `->requiresConfirmation()`, `UiEnforcement`, and `Capabilities::TENANT_MANAGE`. +- `EnvironmentDiagnostics::diagnosticSummary()` now provides Spec 374 repair-only summary values for blocker and no-action states. +- `EnvironmentDiagnostics` now renders the title `Repair diagnostics` with a repair-only subheading. +- `EnvironmentDiagnostics` now exposes one secondary read-only `Open support diagnostics` modal action with support-diagnostics capability gating, audit logging, and `repair_diagnostics` telemetry. +- `environment-diagnostics.blade.php` continues to lead with the summary and lower blocker sections; no provider/system/evidence checks were added. +- `EnvironmentDashboard` contains `openSupportDiagnosticsAction()`. +- `support-diagnostic-bundle.blade.php` now explicitly states the quick support context purpose before `Recommended first check`, renders `Redacted support view`, and organizes lower sections by status, why relevant, observed, and action. +- Implementation search for `diagnosticsUrl(` found the helper plus route-contract tests; no runtime dashboard caller was added. The helper is retained as a canonical deeplink utility. + +## Product Decision Summary + +Spec 374 should define the official diagnostic IA: + +1. **Support Diagnostics** is the official quick support diagnostics entrypoint. +2. **Environment Repair Diagnostics** is a secondary/deeplink page for supported repairable membership/access issues. +3. **Provider/Permission Diagnostics** remain in Provider Connections / Required Permissions or a future provider-readiness follow-up. +4. **System Diagnostics** remain `/system`/platform-admin concerns and are not part of this spec. +5. **Evidence/Customer Review questions** remain evidence/review-pack/customer-review surfaces, not diagnostics entrypoint scope. + +## Scope Risk + +The main risk is treating "diagnostics" as a broad product platform. The implementation must not add a diagnostic hub, new navigation, provider checks, evidence reachability fixes, or system auth fixture work. + +## Implementation Decision + +- No dashboard repair diagnostics link was added. Existing repo truth does not require promoting the direct repair page from the dashboard no-action state. +- `ManagedEnvironmentLinks::diagnosticsUrl()` remains a retained canonical deeplink helper used by route-contract tests and available to future repo-backed repair contexts. +- The direct `/diagnostics` route was renamed in rendered copy and action-surface metadata to `Repair diagnostics`. +- Support Diagnostics remains the official quick diagnostics entrypoint from the dashboard; Repair Diagnostics now has one secondary read-only handoff action to the same redacted support modal. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/validation-report.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/validation-report.md new file mode 100644 index 00000000..1fccb597 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/validation-report.md @@ -0,0 +1,107 @@ +# Validation Report + +Status: implementation validation and post-implementation analysis complete. + +## Repo Safety + +- Branch before Spec Kit execution: `platform-dev`. +- Branch after Spec Kit execution: `374-diagnostic-entry-point-support-diagnostics-consolidation`. +- Base HEAD: `94877c9a feat(ui): implement diagnostic surface separation (#444)`. +- Dirty state before preparation: clean. +- Dirty state after preparation: new untracked Spec 374 package expected. +- Dirty state before runtime edits: untracked active Spec 374 package plus unrelated `.codex/config.toml` local change. The `.codex/config.toml` change was not edited for Spec 374. + +## Spec Kit Commands + +| Command | Purpose | Result | +|---|---|---| +| `./.specify/scripts/bash/create-new-feature.sh --json --number 374 --short-name diagnostic-entry-point-support-diagnostics-consolidation "..."` | Create Spec 374 branch and spec scaffold | passed after approved Git-ref write escalation | +| `./.specify/scripts/bash/setup-plan.sh --json` | Copy plan template for current feature branch | passed | + +## Preparation Checks + +| Check | Result | +|---|---| +| Worktree clean before feature creation | passed | +| Existing `specs/374-*` package absent before creation | passed | +| Related completed specs treated as context only | passed | +| Application/runtime code left unchanged | passed | +| Provider/permission/system/evidence scope kept deferred | passed | +| Preparation analysis placeholder scan | passed; no template placeholders found | +| Preparation analysis consistency scan | one low-severity UI impact wording issue fixed in `spec.md` | + +## Implementation Commands + +| Command | Purpose | Result | +|---|---|---| +| `php artisan test --compact tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php` | Spec 374 dashboard/support/repair entrypoint Feature coverage | passed: 3 tests, 32 assertions | +| `php artisan test --compact --filter=DiagnosticEntry` | Planned Spec 374 diagnostic-entry filter | passed: 4 tests, 51 assertions | +| `php artisan test --compact tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` | Existing repair diagnostics hierarchy/action safety regression | passed: 6 tests, 51 assertions | +| `php artisan test --compact --filter=SupportDiagnostics` | Existing support diagnostics modal/redaction/auth/audit coverage | passed: 39 tests, 228 assertions | +| `php artisan test --compact tests/Feature/Guards/ActionSurfaceContractTest.php --filter='tenant diagnostics'` | Focused action-surface guard for repair diagnostics | passed: 1 test, 3 assertions | +| `php artisan test --compact tests/Feature/Guards/Spec193MonitoringSurfaceHierarchyGuardTest.php` | Monitoring/action-surface inventory guard | passed: 4 tests, 35 assertions | +| `php artisan test --compact tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php tests/Feature/Navigation/Spec322EnvironmentCtaUrlContractTest.php` | Managed environment route/helper contract regression | passed: 3 tests, 181 assertions | +| `php artisan test --compact tests/Feature/Guards/Spec193MonitoringSurfaceHierarchyGuardTest.php tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php tests/Feature/Navigation/Spec322EnvironmentCtaUrlContractTest.php` | Final combined route/monitoring guard regression | passed: 7 tests, 216 assertions | +| `php artisan test --compact tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php tests/Feature/Filament/TenantDiagnosticsRepairsTest.php tests/Feature/Guards/ActionSurfaceContractTest.php --filter='Spec374\|ManagedEnvironment diagnostics repairs\|tenant diagnostics'` | Final affected feature/guard re-run after Pint | passed: 10 tests, 86 assertions | +| `php artisan test --compact tests/Feature/Filament/Spec374DiagnosticEntrypointTest.php tests/Feature/Filament/TenantDiagnosticsRepairsTest.php tests/Feature/SupportDiagnostics/TenantSupportDiagnosticActionTest.php tests/Feature/SupportDiagnostics/OperationRunSupportDiagnosticActionTest.php tests/Feature/SupportDiagnostics/ProductTelemetrySupportDiagnosticsCaptureTest.php tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleBuilderTest.php tests/Unit/Support/SupportDiagnostics/SupportDiagnosticBundleRedactionTest.php` | Regression coverage for repair diagnostics, support diagnostics modal copy/redaction, operation-run support diagnostics, telemetry, safe modal drilldown links, and bundle builder behavior | passed: 27 tests, 269 assertions | +| `php artisan test --compact tests/Browser/Spec374DiagnosticEntrypointSmokeTest.php` | Browser smoke for dashboard entry, support modal, direct repair diagnostics route, repair-page support handoff, and safe modal drilldown link attributes | passed: 1 test, 36 assertions | +| `php vendor/bin/pint --dirty` | Formatting after PHP changes | passed | +| `git diff --check` | Static diff whitespace check | passed | + +All planned implementation validation commands have passed. Browser smoke was re-run after formatting and passed. + +## Browser Results + +- Environment Dashboard diagnostic entry: PASS. +- Support Diagnostics modal: PASS. The modal uses compact `Support scope` copy instead of a prominent internal boundary note, and reference drilldown links render as safe links with `target="_blank"` and `rel="noopener noreferrer"`. +- Support Diagnostics SQLSTATE/constraint redaction: PASS. Manual Integrated Browser check on the `Spec 352 Audit Provider Blocker` fixture showed `The operation failed and needs follow-up.`, `Support scope`, and no `SQLSTATE`, duplicate-key, `environment_reviews_fingerp`, or visible `Boundary` support-note block. +- Support Diagnostics reference drilldown: PASS. Manual Integrated Browser check clicked `Inspect event` and landed on `/admin/audit-log?event=767&environment_id=51` with the Audit Log selected-event detail visible. +- Direct Environment Repair Diagnostics page: PASS. +- Repair Diagnostics support handoff: PASS. +- Browser console/runtime errors: PASS. +- Repair/blocker state: not available through current fixture; kept Feature/Livewire-only per spec. + +Screenshots: + +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/002-support-diagnostics-modal-after.png` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png` + +## Gates + +| Gate | Result | Notes | +|---|---|---| +| Spec Readiness Gate | PASS | Preparation checklist passed; implementation loop accepted Draft status because checklist records readiness and user invoked implementation on active branch. | +| Implementation Scope Gate | PASS | Runtime changes trace to tasks and plan; no hub/nav/provider/backend expansion. | +| Test Gate | PASS | Targeted Feature/Livewire, guard, route, support diagnostics, browser, Pint dirty, and diff checks passed. | +| Browser Smoke Test Gate | PASS | Bounded Pest browser smoke passed with screenshots. | +| Post-Implementation Analysis Gate | PASS | Final repo scan found no confirmed in-scope defects. | +| Merge Readiness Gate | PASS WITH CAVEAT | Spec 374 scoped files are ready for review; unrelated `.codex/config.toml` local change must stay excluded from the Spec 374 commit/PR. | + +## Post-Implementation Analysis + +- In-scope findings: none confirmed. +- Runtime scope stayed bounded to the existing dashboard support diagnostics action and direct repair diagnostics page. +- No diagnostic hub, new navigation item, provider health check, Graph contract, OperationRun flow, migration, queue, scheduler, customer/auditor surface, evidence surface, or system panel change was introduced. +- `ManagedEnvironmentLinks::diagnosticsUrl()` remains a canonical deeplink helper and is not promoted to a dashboard runtime action. +- All Spec 374 tasks are checked off; tasks that were satisfied by preserving existing behavior are documented in `tasks.md` implementation notes. + +## Deployment Impact + +Implementation 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 deploy reminder remains `cd apps/platform && php artisan filament:assets` only when registered Filament assets change. + +## Known Limitations + +- Repair/blocker browser screenshot was not captured because current browser fixture does not safely create duplicate membership or missing-owner states. +- `ManagedEnvironmentLinks::diagnosticsUrl()` caller status was re-verified during implementation and remains a retained deeplink utility rather than a dashboard runtime action. +- Unrelated `.codex/config.toml` local change remains outside Spec 374 scope. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/checklists/requirements.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/checklists/requirements.md new file mode 100644 index 00000000..eeff9504 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/checklists/requirements.md @@ -0,0 +1,45 @@ +# Requirements Checklist: Spec 374 - Diagnostic Entry Point and Support Diagnostics Consolidation v1 + +**Purpose**: Validate preparation readiness for Spec 374 before implementation. +**Created**: 2026-06-12 +**Feature**: `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md` + +## Spec Quality + +- [x] CHK001 The selected candidate is directly provided by the user and aligned with current repo truth. +- [x] CHK002 The completed-spec guardrail excludes Spec 373 as a refresh target and treats it as context. +- [x] CHK003 The spec states the concrete operator workflow: start diagnostics from Environment Dashboard and distinguish Support Diagnostics from Repair Diagnostics. +- [x] CHK004 The spec defines the smallest enterprise-capable slice and rejects a diagnostic hub. +- [x] CHK005 Functional requirements are testable and avoid implementation-only wording where product behavior is enough. +- [x] CHK006 Out-of-scope boundaries are explicit for provider, permission, evidence, system, OperationRun, customer/auditor, and bloat-guard work. +- [x] CHK007 Risks, assumptions, and non-blocking open questions are recorded. + +## Constitution And Guardrails + +- [x] CHK008 UI Surface Impact is completed and does not conflict with no-impact handling. +- [x] CHK009 UI/Productization Coverage classifies the dashboard, support modal, repair diagnostics page, and deferred destinations. +- [x] CHK010 Cross-cutting shared pattern reuse names existing builders/helpers/actions before any new local work. +- [x] CHK011 OperationRun UX impact states no new start/completion behavior and limits usage to existing links. +- [x] CHK012 Provider boundary treatment keeps provider/permission diagnostics out of repair diagnostics. +- [x] CHK013 Proportionality review states no new persisted truth, abstraction, enum/state family, or diagnostic framework. +- [x] CHK014 RBAC and destructive-action safety requirements are explicit for existing repair actions. +- [x] CHK015 Test governance names the narrowest proving lanes and fixture cost controls. + +## Task Readiness + +- [x] CHK016 `tasks.md` includes repo-truth and source-audit tasks before runtime edits. +- [x] CHK017 `tasks.md` includes tests before implementation for dashboard entry, repair diagnostics, and support modal behavior. +- [x] CHK018 `tasks.md` includes browser smoke and screenshot tasks for reachable surfaces. +- [x] CHK019 `tasks.md` includes final validation and artifact close-out tasks. +- [x] CHK020 `tasks.md` includes explicit non-goals to prevent scope creep. + +## Preparation Outcome + +- [x] CHK021 Candidate Selection Gate result: pass. +- [x] CHK022 Spec Readiness Gate result: pass for preparation. +- [x] CHK023 Review outcome class: acceptable-special-case. +- [x] CHK024 Workflow outcome: keep. + +## Notes + +This checklist validates preparation only. It does not claim runtime implementation, browser smoke, or test execution. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/plan.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/plan.md new file mode 100644 index 00000000..ef461951 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/plan.md @@ -0,0 +1,287 @@ +# Implementation Plan: Spec 374 - Diagnostic Entry Point and Support Diagnostics Consolidation v1 + +**Branch**: `374-diagnostic-entry-point-support-diagnostics-consolidation` | **Date**: 2026-06-12 | **Spec**: `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md` +**Input**: User-provided Spec 374 draft, completed Spec 373 artifacts, Spec 370 IA contract, and current repo routes/surfaces. + +## Summary + +Productize the official environment diagnostics entrypoint decision without building a diagnostic hub. + +The implementation should make these product truths explicit: + +- Environment Dashboard remains the primary operator starting point. +- Support Diagnostics modal is the official quick diagnostics/support context entry. +- Environment Diagnostics is a secondary repair diagnostics page/deeplink for supported membership/access repair cases. +- Provider/Permission Diagnostics, System Diagnostics, Evidence questions, and Customer Review evidence stay in their existing or future owned surfaces. + +The work is presentation, discoverability, and documentation over existing routes/actions. It must not add new diagnostic backend checks, Graph calls, provider logic, migrations, navigation trees, OperationRun behavior, or customer/auditor changes. + +## Technical Context + +**Language/Version**: PHP 8.4.15, Laravel 12.52 +**Primary Dependencies**: Filament v5.2.1, Livewire v4.1.4, Pest v4.3.1, Laravel Sail +**Storage**: PostgreSQL; no schema change planned +**Testing**: Pest Feature/Livewire plus 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 render; no provider/Graph calls during page or modal render +**Constraints**: no new persistence, no new provider/permission/system/evidence runtime logic, no new navigation hub, no panel provider/global-search changes +**Scale/Scope**: one environment dashboard action area, one support diagnostics modal, one repair diagnostics page, and spec-local artifacts + +## Current Repo Truth That Constrains The Slice + +- `apps/platform/routes/web.php` defines: + - `/admin/workspaces/{workspace}/environments/{environment:slug}` -> `EnvironmentDashboard` + - `/admin/workspaces/{workspace}/environments/{environment:slug}/diagnostics` -> `EnvironmentDiagnostics` +- `EnvironmentDiagnostics` has `protected static bool $shouldRegisterNavigation = false`. +- `EnvironmentDiagnostics` currently has existing repair actions: + - `bootstrapOwner` + - `mergeDuplicateMemberships` +- The repair actions already use `Action::make(...)->action(...)`, `->requiresConfirmation()`, `UiEnforcement`, `Capabilities::TENANT_MANAGE`, and service-owned repair methods. +- `ManagedEnvironmentDiagnosticsService::tenantHasNoOwners()` currently returns `false`; missing-owner repair is an existing presentation/test seam, not repo-active detection. Spec 374 must not add missing-owner detection or owner-recovery logic. +- `environment-diagnostics.blade.php` already uses a first-viewport summary from Spec 373, but the page still reads as "Environment Diagnostics" rather than a clearly bounded repair diagnostics destination. +- `EnvironmentDashboard` already exposes `openSupportDiagnosticsAction()` from header/More actions. +- `support-diagnostic-bundle.blade.php` already presents recommended first check before lower support sections from Spec 373. +- `ManagedEnvironmentLinks::diagnosticsUrl()` exists; current preparation search found only its method definition. Implementation must re-run usage search before deciding whether to keep it as deeplink utility or adopt it for one secondary link. +- `ActionSurfaceExemptions` and `EnvironmentDiagnostics::actionSurfaceDeclaration()` currently use ManagedEnvironment diagnostics terminology; if runtime copy adopts Repair Diagnostics as the canonical page noun, those metadata paths must be audited for consistency. +- No `specs/374-*` package existed before this preparation. + +## Source Inputs + +Required implementation reads: + +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/plan.md` +- `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/tasks.md` +- `specs/373-diagnostic-surface-separation/spec.md` +- `specs/373-diagnostic-surface-separation/plan.md` +- `specs/373-diagnostic-surface-separation/tasks.md` +- `specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md` +- `specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md` +- `specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md` +- `specs/373-diagnostic-surface-separation/artifacts/validation-report.md` +- `specs/370-global-surface-information-architecture-contract/artifacts/surface-contract.md` +- `specs/370-global-surface-information-architecture-contract/artifacts/surface-type-matrix.md` +- `docs/ui-ux-enterprise-audit/page-reports/ui-002-environment-dashboard.md` +- `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` +- `docs/ui-ux-enterprise-audit/route-inventory.md` + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: changed existing environment dashboard action, support diagnostics modal purpose/copy, and repair diagnostics page framing. +- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: + - `/admin/workspaces/{workspace}/environments/{environment}` + - `openSupportDiagnostics` action/modal + - `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` +- **No-impact class, if applicable**: N/A. +- **Native vs custom classification summary**: native Filament page/action/modal with existing Blade content; do not create a custom diagnostic UI system. +- **Shared-family relevance**: diagnostics entrypoints, support diagnostic bundle, repair diagnostics, action links, status/no-action messaging. +- **State layers in scope**: page/action/modal presentation only. +- **Audience modes in scope**: operator-MSP and support-platform. +- **Decision/diagnostic/raw hierarchy plan**: dashboard entrypoint decision first; support diagnostics first check second; repair diagnostics only when appropriate; raw/support detail lower or gated. +- **Raw/support gating plan**: support diagnostics remains capability-gated and rendered as a redacted support view while retaining the internal `default_redacted` mode; repair diagnostics does not expose raw provider data. +- **One-primary-action / duplicate-truth control**: "Open support diagnostics" remains the primary quick diagnostics path; repair diagnostics is secondary and conditional if surfaced. +- **Handling modes by drift class or surface**: + - Environment Dashboard: implementation target. + - Support Diagnostics modal: implementation target for purpose/first-check clarity if current copy is insufficient. + - Environment Diagnostics: implementation target for repair-only/no-action framing. + - Provider/Permission/System/Evidence/Customer review diagnostics: report-only or follow-up-spec. +- **Repository-signal treatment**: completed Spec 373 is context, not a rewrite target. +- **Special surface test profiles**: `standard-native-filament`, `shared-detail-family`, and `exception-coded-surface` for partial repair-state browser fixtures. +- **Required tests or manual smoke**: Feature/Livewire plus browser smoke. +- **Exception path and spread control**: fixture gaps are documented in feature artifacts; do not create auth/seed infrastructure inside this spec. +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage. +- **UI/Productization coverage decision**: update coverage artifacts only for changed Environment Dashboard, support diagnostics modal, or Environment Diagnostics evidence/status. Do not churn Provider Connections/Required Permissions reports. +- **Coverage artifacts to update**: + - `docs/ui-ux-enterprise-audit/route-inventory.md` if screenshot/report references or classification materially change. + - `docs/ui-ux-enterprise-audit/page-reports/ui-002-environment-dashboard.md` if dashboard action evidence changes. + - `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` if page framing/evidence changes. + - `docs/ui-ux-enterprise-audit/unresolved-pages.md` only for scoped blocked reachability. +- **Navigation / Filament provider-panel handling**: no panel provider change; do not register `/diagnostics` as a top-level navigation item. +- **Screenshot or page-report need**: yes for dashboard action/modal and direct repair diagnostics page if reachable; document blocked repair-state screenshots honestly. + +## Shared Pattern And System Fit + +- **Cross-cutting feature marker**: yes. +- **Systems touched**: + - `App\Filament\Pages\EnvironmentDashboard` + - `App\Filament\Pages\EnvironmentDiagnostics` + - `App\Support\Ui\ActionSurface\ActionSurfaceExemptions` only if canonical action-surface terminology changes + - `App\Support\ManagedEnvironmentLinks` + - `App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder` only if existing bundle copy/data is insufficient + - `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` + - `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` + - focused feature/browser tests and spec-local artifacts +- **Shared abstractions reused**: + - `SupportDiagnosticBundleBuilder` + - `ManagedEnvironmentLinks` + - `ActionSurfaceDeclaration` / `ActionSurfaceExemptions` metadata where existing surface terminology is material + - `OperationRunLinks` for existing support context only + - `UiEnforcement` + - `Capabilities` + - Filament native actions, modals, sections, badges, and links +- **New abstraction introduced? why?**: none planned. A page-local derived label/helper is allowed only if it maps existing repair/no-action state and replaces duplicated view conditionals. +- **Why the existing abstraction was sufficient or insufficient**: Existing route/action/bundle truth is sufficient; only product entrypoint semantics are missing. +- **Bounded deviation / spread control**: do not reuse provider-readiness guidance as a generic diagnostic engine; keep provider-specific checks out of Environment Diagnostics. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no. +- **Central contract reused**: existing `OperationRunLinks` only if support diagnostics bundle already includes operation context. +- **Delegated UX behaviors**: N/A. +- **Surface-owned behavior kept local**: entrypoint and reference presentation only. +- **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 and destination mapping only. +- **Provider-owned seams**: provider connection readiness, Microsoft/Graph permission facts, provider-specific error/reason details. +- **Platform-core seams**: managed environment dashboard, support diagnostics entrypoint, repair diagnostics route, operation/evidence references. +- **Neutral platform terms / contracts preserved**: provider connection, required permissions, managed environment, operation, evidence, support diagnostics, repair diagnostics. +- **Retained provider-specific semantics and why**: provider-specific details stay in provider-owned destinations and lower support diagnostics sections where exact troubleshooting proof is necessary. +- **Bounded extraction or follow-up path**: provider/permission diagnostics productization remains a follow-up if needed. + +## 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: no new write action. Existing repair actions must retain confirmation, authorization, and service/audit ownership. +- Graph contract path: no new Graph calls. Page/modal render remains DB-local. +- Deterministic capabilities: existing capability checks remain authoritative. +- RBAC-UX: workspace/environment entitlement and support diagnostics capability remain server-side truth. +- Workspace/Tenant isolation: no query-string context shortcuts or remembered-environment overrides. +- OperationRun observability: no new OperationRun type/start/completion. +- 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. +- Proportionality: no new persisted truth, framework, enum/status family, or diagnostic hub. +- Shared pattern first: reuse existing diagnostic/support/action/link patterns. +- Provider boundary: provider readiness stays provider-owned and is not imported into repair diagnostics. +- UI-COV-001: reachable UI impact declared; coverage artifact handling is proportional. +- DECIDE-001 / DECIDE-AUD-001: dashboard stays decision/context, support/repair diagnostics stay tertiary, raw/support depth remains lower or gated. +- 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. +- 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 Dashboard diagnostic entry: Feature/Livewire + Browser. + - Support Diagnostics modal: Feature/Livewire + Browser. + - Environment Diagnostics repair/no-action framing: Feature/Livewire + Browser. + - Matrix/artifacts: static review. +- **Affected validation lanes**: confidence and browser. +- **Why this lane mix is the narrowest sufficient proof**: entrypoint clarity must be proven both in action state/rendered DOM and actual browser discoverability. +- **Narrowest proving commands**: + - exact Spec 374 diagnostic-entry test file/filter if created; otherwise record why no `DiagnosticEntry` filter exists and run the concrete dashboard/support/repair tests that cover it + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` or the exact replacement Spec 374 repair-diagnostics test file + - `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics` + - bounded browser smoke for dashboard action/modal/direct page + - `cd apps/platform && ./vendor/bin/sail pint --dirty` if PHP changes + - `git diff --check` +- **Fixture / helper / factory / seed / context cost risks**: use existing fixture patterns; document unavailable repair/blocker screenshots instead of widening fixture infrastructure. +- **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 action state; shared-detail-family for modal; exception-coded-surface for direct repair page. +- **Closing validation and reviewer handoff**: reviewers should inspect source audit, matrix, browser report, screenshot index, validation report, and final diff for out-of-scope files. +- **Budget / baseline / trend follow-up**: none expected. +- **Review-stop questions**: Did implementation add a hub, new nav, provider/permission runtime checks, raw leakage, or weaken repair-action safety? +- **Escalation path**: document-in-feature for fixture gaps; follow-up-spec for provider/permission diagnostics, evidence/system browser reachability, or bloat guard. +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage. +- **Why no dedicated follow-up spec is needed**: This is the dedicated entrypoint productization slice; broader diagnostic lanes are explicitly deferred. + +## Project Structure + +### Documentation (this feature) + +```text +specs/374-diagnostic-entry-point-support-diagnostics-consolidation/ +├── spec.md +├── plan.md +├── tasks.md +├── checklists/ +│ └── requirements.md +└── artifacts/ + ├── source-audit-summary.md + ├── diagnostic-entrypoint-matrix.md + ├── affected-files.md + ├── implementation-notes.md + ├── browser-verification-report.md + ├── before-after-screenshot-index.md + ├── validation-report.md + ├── follow-up-recommendations.md + └── screenshots/ +``` + +### Source Code (repository root) + +Likely affected runtime surfaces for later implementation: + +```text +apps/platform/app/Filament/Pages/EnvironmentDashboard.php +apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php +apps/platform/app/Support/ManagedEnvironmentLinks.php +apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php +apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php +apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php +apps/platform/tests/Feature/Filament/ +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**: unclear official diagnostic entrypoint causes hidden-route confusion and future scope creep. +- **Existing structure is insufficient because**: existing surfaces show diagnostic content but do not clearly define which surface owns quick support, repair, provider/permission, system, evidence, or customer review questions. +- **Narrowest correct implementation**: label/copy/conditional entrypoint and no-action semantics over existing routes/actions, backed by a matrix and browser proof. +- **Ownership cost created**: focused tests, screenshots, and spec-local artifacts. +- **Alternative intentionally rejected**: generic Diagnostic Hub or provider-health framework. +- **Release truth**: current-release productization over existing diagnostic/support surfaces. + +## Implementation Phases + +### Phase 0 - Repo Truth And Source Audit + +Re-read Spec 373/370 context, current dashboard/diagnostics/support code, route inventory, and tests. Re-run `ManagedEnvironmentLinks::diagnosticsUrl()` usage search. Complete source audit and matrix before runtime edits. + +### Phase 1 - Tests First + +Add/update focused tests for dashboard support diagnostics entry, optional secondary repair diagnostics link behavior, direct repair diagnostics no-action state, repair action safety, DB-local/no-Graph rendering for dashboard/modal/page paths, and support modal purpose/first-check fallback. + +### Phase 2 - Dashboard Diagnostic Entry Productization + +Refine `Open support diagnostics` label/description/placement if needed. Add a secondary repair diagnostics link only if repo-backed and low-risk. Keep the dashboard's primary decision hierarchy intact. + +### Phase 3 - Repair Diagnostics Page Framing + +Reframe the direct diagnostics page as repair diagnostics. Improve no-action copy and support-diagnostics handoff. Preserve repair action authorization, confirmation, and service ownership. Audit existing action-surface metadata before changing canonical nouns so the page contract does not drift from the UI governance catalog. + +### Phase 4 - Support Diagnostics Purpose Check + +Confirm the modal begins with recommended first check and purpose. Add calm fallback only if current bundle output can lack a recommendation. Do not add new support sections or inferred causes. + +### Phase 5 - Browser Smoke And Artifacts + +Use the existing browser fixture/login path. Capture dashboard entry, support diagnostics modal, direct repair diagnostics page, and no-action state screenshots where reachable. Document any repair-blocker fixture gap. + +### Phase 6 - Validation And Close-Out Artifacts + +Run targeted tests/static checks, complete implementation notes, browser report, screenshot index, validation report, affected files, and follow-up recommendations. Confirm no out-of-scope runtime files changed. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md new file mode 100644 index 00000000..847c5494 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md @@ -0,0 +1,339 @@ +# Feature Specification: Spec 374 - Diagnostic Entry Point and Support Diagnostics Consolidation v1 + +**Feature Branch**: `374-diagnostic-entry-point-support-diagnostics-consolidation` +**Created**: 2026-06-12 +**Status**: Draft +**Input**: User-provided Spec 374 draft and repo-verified follow-up from completed Spec 373. + +## Candidate Selection Summary + +- **Selected candidate**: Diagnostic Entry Point and Support Diagnostics Consolidation v1. +- **Source**: User-provided pasted note for Spec 374, using Spec 373 implementation results as immediate source truth. +- **Why selected**: Spec 373 productized Environment Diagnostics and the support diagnostics modal, but the remaining product ambiguity is entrypoint ownership: the visible operator path is Environment Dashboard -> More actions -> Open support diagnostics, while the direct `/diagnostics` page is a narrow repair-diagnostics utility. That ambiguity can make an empty repair page look like a broken diagnostic hub. +- **Roadmap relationship**: Supports the Product Scalability and Self-Service Foundation lane by making supportability and diagnostics calmer, discoverable, and honest without creating a broad diagnostic platform. +- **Smallest viable slice**: Define and productize the diagnostic entrypoint contract: Support Diagnostics is the official quick diagnostics entry; Environment Diagnostics is a secondary repair/deeplink surface; provider/permission/system/evidence/customer diagnostic questions remain owned by their existing or future surfaces. +- **Deferred close alternatives**: + - UI Bloat Regression Guard: useful later, but too broad for the immediate entrypoint ambiguity. + - Provider/Permission Diagnostics: completed or separately owned by Provider Connections, Required Permissions, and Spec 353 style guidance. + - Evidence/System fixture reachability: fixture and auth coverage follow-up, not diagnostic IA. + - Full Diagnostic Hub: explicitly out of scope because current repo truth has only two real diagnostic entry families. +- **Completed-spec guardrail result**: + - Spec 373 has completed task markers, validation artifacts, browser evidence, and implementation close-out language in artifacts. Treat as completed context only. + - Specs 370, 371, and 372 are consumed as completed context and are not rewritten. + - No existing `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/` package existed before this preparation run. + +## Spec Candidate Check *(mandatory - SPEC-GATE-001)* + +- **Problem**: Operators do not have one clearly documented official starting point for environment diagnostics. The support diagnostics modal is visible and useful, while the direct repair diagnostics page is narrow, hidden from navigation, and can look empty or generic if reached directly. +- **Today's failure**: An operator or implementer can mistake `/diagnostics` for a generic health hub, add provider/permission/system checks into the wrong route, or expose a prominent repair diagnostics action even when no supported repair case exists. +- **User-visible improvement**: The Environment Dashboard presents Support Diagnostics as the primary quick diagnostics path, any repair diagnostics entry is secondary and conditional, and the repair page clearly explains no-action or repair-only states. +- **Smallest enterprise-capable version**: Productize labels, descriptions, conditional entrypoint rules, no-action state, and documentation artifacts over existing routes and helpers. Do not add provider checks, new diagnostics backend logic, migrations, or new navigation. +- **Explicit non-goals**: No diagnostic hub, no Provider Connections redesign, no Required Permissions redesign, no System panel auth fix, no Evidence Snapshot reachability fix, no new Graph checks, no new repair logic, no OperationRun lifecycle work, no customer/auditor surface changes, and no UI bloat guard. +- **Permanent complexity imported**: One narrow entrypoint contract, focused feature/browser tests, and spec-local artifacts. No new persisted truth, enum/status family, provider framework, navigation branch, or generic diagnostic taxonomy. +- **Why now**: Spec 373 completed the diagnostic hierarchy inside the existing surfaces. The next smallest productization step is to clarify when operators should enter each surface so future work does not turn repair diagnostics into a catch-all hub. +- **Why not local**: Copy tweaks alone would not protect discoverability, route purpose, dashboard action hierarchy, `ManagedEnvironmentLinks::diagnosticsUrl()` usage, and deferred provider/permission/system boundaries together. +- **Approval class**: Workflow Compression. +- **Red flags triggered**: Multiple diagnostic surfaces and a consolidation theme. Defense: the scope is intentionally limited to existing entrypoints and documentation; it rejects a hub, framework, and new backend checks. +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12** +- **Decision**: approve. + +## Spec Scope Fields *(mandatory)* + +- **Scope**: workspace + managed-environment diagnostic entrypoints. +- **Primary Routes / Surfaces**: + - `/admin/workspaces/{workspace}/environments/{environment}` + - `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` + - Existing `openSupportDiagnostics` modal/action on Environment Dashboard and OperationRun detail contexts where already repo-backed +- **Data Ownership**: No ownership change. `ManagedEnvironment` remains the environment context; support diagnostics remains derived from existing stored workspace/environment/provider/operation/evidence/review/audit truth. +- **RBAC**: + - Existing workspace membership and environment entitlement remain authoritative. + - Existing support diagnostics capability gating remains authoritative. + - Existing repair actions remain capability-gated, server-authorized, confirmation-gated, and audit-owned by current services. + - Non-members remain deny-as-not-found; entitled members missing capability receive existing 403/disabled behavior. + +For canonical or mixed context surfaces: + +- **Default filter behavior when tenant-context is active**: N/A. This spec uses workspace/environment routes and does not introduce query-string context. +- **Explicit entitlement checks preventing cross-tenant leakage**: Existing route model binding, workspace middleware, environment context middleware, policies, and action authorization remain the only allowed entitlement paths. + +## 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 + +No new navigation is planned. Dangerous-action behavior is still reviewed because the repair diagnostics page already contains destructive-like repair actions; the spec must preserve confirmation, authorization, and audit ownership without adding or changing repair behavior. + +## 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 Dashboard diagnostic action area | Operator Surface / Environment Dashboard | Strategic Surface | repo-verified, browser-proven by Specs 371/373 | Environment Dashboard More action pattern and support diagnostics action | after screenshot required if action label/placement changes | operator/support only; no new dangerous action | +| Support Diagnostics modal | Support / Diagnostics modal | Domain Pattern Surface | repo-verified, browser-proven by Spec 373 | `SupportDiagnosticBundleBuilder`, redaction, contextual help, modal sections | after screenshot required if modal explanation changes | support-only; rendered as redacted support view while retaining internal `default_redacted` mode | +| Environment Diagnostics page | Tertiary Evidence / Diagnostics / Repair utility | Domain Pattern Surface | repo-verified, browser-proven by Spec 373 | `EnvironmentDiagnostics`, Filament Page, existing repair actions | after screenshot required for no-action state and any repair state fixture available | existing repair actions must retain confirmation, capability gating, server authorization, and audit/service ownership | +| Provider/Permission diagnostics destinations | Provider / Integration, Configuration Surface | Deferred / existing completed context | repo-verified, completed Spec 353 context | Provider Connections and Required Permissions surfaces | no screenshot unless linked regression occurs | keep out of repair diagnostics route | +| System and Evidence diagnostics destinations | System / Evidence surfaces | Deferred | not available or separate surface truth | existing system/evidence surfaces | document deferred; no screenshot in this spec unless already reachable | not customer default diagnostics | + +Coverage files to review during implementation: + +- `docs/ui-ux-enterprise-audit/route-inventory.md` +- `docs/ui-ux-enterprise-audit/page-reports/ui-002-environment-dashboard.md` +- `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` +- `docs/ui-ux-enterprise-audit/unresolved-pages.md` only if the scoped modal/page remains unreachable + +## Cross-Cutting / Shared Pattern Reuse + +- **Cross-cutting feature?**: yes. +- **Interaction class(es)**: diagnostic entrypoints, header/more actions, support diagnostics modal, status/no-action messaging, action links, technical-details disclosure. +- **Systems touched**: Environment Dashboard, Environment Diagnostics, support diagnostics modal/bundle, `ManagedEnvironmentLinks::diagnosticsUrl()`, `EnvironmentDiagnostics::actionSurfaceDeclaration()`, `ActionSurfaceExemptions` terminology if the canonical noun changes, UI audit artifacts, focused tests. +- **Existing pattern(s) to extend**: Spec 370 IA contract, Spec 373 diagnostic hierarchy, `SupportDiagnosticBundleBuilder`, existing support diagnostics actions, existing Filament action confirmation/RBAC helpers. +- **Shared contract / presenter / builder / renderer to reuse**: `SupportDiagnosticBundleBuilder`, `ManagedEnvironmentLinks`, `OperationRunLinks` for existing proof links, `UiEnforcement`, `Capabilities`, and existing Filament action/modal primitives. +- **Why the existing shared path is sufficient or insufficient**: The existing surfaces and builders already hold the truth. The missing piece is official entrypoint ownership and conditional discoverability, not another diagnostic abstraction. +- **Allowed deviation and why**: A page-local label or view-state helper is allowed only if it maps existing repair/no-action truth and does not become a generic diagnostic resolver. +- **Consistency impact**: Support Diagnostics, Repair Diagnostics, Provider/Permission Diagnostics, System Diagnostics, and customer review evidence must not share one vague "Diagnostics" mental model. +- **Review focus**: Confirm the implementation does not create a new diagnostic hub, does not promote `/diagnostics` to top-level navigation, and does not blend provider/permission readiness into repair diagnostics. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: No new OperationRun start/completion behavior. Existing support diagnostics may show existing OperationRun references only. +- **Shared OperationRun UX contract/layer reused**: Existing `OperationRunLinks` and canonical OperationRun detail URLs only when already present in bundle data. +- **Delegated start/completion UX behaviors**: N/A. +- **Local surface-owned behavior that remains**: Displaying existing operation context as support diagnostic evidence when repo-backed. +- **Queued DB-notification policy**: unchanged. +- **Terminal notification path**: unchanged. +- **Exception required?**: none. + +## Provider Boundary / Platform Core Check + +- **Shared provider/platform boundary touched?**: yes, presentation and routing semantics only. +- **Boundary classification**: mixed. +- **Seams affected**: diagnostic labels, provider/permission follow-up links, support-diagnostic bundle sections, managed-environment route helper usage. +- **Neutral platform terms preserved or introduced**: Support Diagnostics, Repair Diagnostics, Managed Environment, provider connection, required permissions, operation, evidence, system diagnostics. +- **Provider-specific semantics retained and why**: Microsoft/provider permission names and provider connection details may remain inside provider-owned destinations or lower support sections because they are real troubleshooting proof. +- **Why this does not deepen provider coupling accidentally**: The repair diagnostics route remains about TenantPilot membership/repair cases; provider readiness stays in provider/permission destinations and is explicitly deferred from `/diagnostics`. +- **Follow-up path**: Provider/Permission diagnostic productization remains a separate follow-up if fresh repo evidence shows a gap after Spec 353. + +## 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 Dashboard diagnostic entry | yes | Native Filament page/action/modal host | diagnostics entrypoint, support modal | page/action | no | existing surface only | +| Support Diagnostics modal purpose copy | yes | Filament modal + Blade content | support diagnostics, redaction, contextual help | modal/detail | no | existing modal only | +| Environment Diagnostics repair/no-action framing | yes | Filament page + Blade content | repair diagnostics, destructive repair actions | page/detail | no | existing route only | +| Provider/Permission/System/Evidence diagnostics | no direct change | N/A | deferred surfaces | none | no | documented as out of scope | + +## 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 Dashboard diagnostic entry | Secondary Context Surface | Operator decides where to start diagnostics for this environment | primary quick path: Open support diagnostics; secondary repair path only when relevant | support bundle sections, repair diagnostics page | Secondary because dashboard remains the environment overview, not a diagnostic hub | starts from the operator's current environment page | avoids searching for hidden repair routes | +| Support Diagnostics modal | Tertiary Evidence / Diagnostics Surface | Operator/support decides what to inspect first | purpose, recommended first check, redaction/freshness, related context | provider/operation/evidence/audit references | Tertiary because it supports troubleshooting, not primary governance decisions | quick diagnostics from current environment/run context | reduces raw-section scanning | +| Environment Diagnostics page | Tertiary Evidence / Diagnostics Surface | Operator/support decides whether a repairable membership/access issue exists | repair-only purpose, action-needed/no-action/unavailable state, next action | individual blocker proof, technical repair context | Tertiary because it is a repair utility and may remain deeplink-only | focused repair diagnostic after support/dashboard signal | prevents false "generic diagnostics hub" expectations | + +## 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 Dashboard diagnostic entry | operator-MSP, support-platform | official quick entry and any conditional repair entry | short purpose descriptions | none on dashboard | Open support diagnostics | repair page link hidden or secondary unless repairable/repo-backed | dashboard owns only entrypoint decision | +| Support Diagnostics modal | operator-MSP, support-platform | recommended first check, purpose, redaction/freshness | lower diagnostic sections | redacted references and support context | open most relevant existing reference when available | raw provider payloads, secrets, unrestricted logs | summary owns first check; sections add proof | +| Environment Diagnostics page | operator-MSP, support-platform | repair/no-action state and recommended repair check | repair blocker details | technical repair context only if needed | existing repair action when blocker exists | raw IDs and unsupported checks | top state owns repair/no-action truth | + +## 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 Dashboard diagnostic entry | Utility / Diagnostic | Operator dashboard action area | Open support diagnostics | More action/modal | forbidden | More actions or secondary contextual placement | none added | N/A | `/admin/workspaces/{workspace}/environments/{environment}` | workspace + managed environment | Support Diagnostics | official diagnostic starting path | dashboard utility action | +| Support Diagnostics modal | Utility / Support | Support diagnostic modal | review first check or open reference | modal | forbidden | lower sections | none | source page | source page/action | workspace + managed environment/run context | Support Diagnostics | purpose, redaction, first check | support modal exemption | +| Environment Diagnostics page | Utility / Diagnostic | Repair diagnostics singleton | run existing repair or return to support diagnostics | same page | forbidden | contextual lower sections | header actions with confirmation/capability gating | N/A | `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` | workspace + managed environment | Repair Diagnostics | repair/no-action state | singleton repair utility | + +## 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 Dashboard diagnostic entry | TenantPilot operator/support user | Choose the right diagnostic entrypoint | Secondary context action area | Where should I start for this environment? | Support Diagnostics as quick path; repair diagnostics if applicable | none on dashboard | supportability, repair availability | none added | Open support diagnostics | none added | +| Support Diagnostics modal | Support user/operator with capability | Decide what to inspect first | Tertiary support diagnostics | What should support check first? | recommended first check, purpose, redaction/freshness, related context | raw/support references below | support availability, freshness, redaction | read-only | Open existing reference if available | none | +| Environment Diagnostics page | TenantPilot operator/support user | Decide whether to run an existing repair action | Tertiary repair diagnostics | Are repairable environment access/membership issues active? | action-needed/no-action/unavailable state, impact, recommended repair check | detailed repair context below | repair availability, membership/access consistency | TenantPilot-only membership repair | Bootstrap owner / Merge duplicate access scopes when visible | existing repair actions only | + +## Proportionality Review + +- **New source of truth?**: no. +- **New persisted entity/table/artifact?**: no domain persistence. Spec-local artifacts are preparation and implementation evidence only. +- **New abstraction?**: no generic abstraction planned. +- **New enum/state/reason family?**: no persisted state family. Presentation labels such as `official-entrypoint`, `secondary-entrypoint`, and `repair-only` are documentation classifications, not runtime state. +- **New cross-domain UI framework/taxonomy?**: no. The diagnostic entrypoint matrix is a spec artifact and must not become a mandatory platform framework. +- **Current operator problem**: The product has multiple diagnostics-adjacent entrypoints, but their official roles are unclear. +- **Existing structure is insufficient because**: Existing surfaces answer diagnostic details once reached, but not which entrypoint should be used first or when `/diagnostics` should remain quiet/deeplink-only. +- **Narrowest correct implementation**: Clarify action labels, descriptions, conditional link/discoverability, no-action state, and artifacts over existing routes/actions. +- **Ownership cost**: Focused tests, browser smoke, screenshots, and a small entrypoint matrix. +- **Alternative intentionally rejected**: A diagnostic hub or provider-health framework. Current repo truth does not justify that breadth. +- **Release truth**: current-release productization of existing diagnostic surfaces. + +### Compatibility posture + +This feature assumes the repo's pre-production posture. It introduces no migration, compatibility shim, legacy alias, backfill, or old-data preservation path. + +## Testing / Lane / Runtime Impact + +- **Test purpose / classification**: Feature/Livewire for action visibility, labels, no-action/repair states, and modal content; Browser for actual dashboard entrypoint and route/modal reachability; static checks for preparation artifacts. +- **Validation lane(s)**: confidence + browser + static diff/format checks. +- **Why this classification and these lanes are sufficient**: The behavior is UI entrypoint and copy hierarchy over existing DB-local truth. Feature/Livewire proves state and action contracts; Browser proves the entrypoint is findable and not visually competing. +- **New or expanded test families**: one bounded Spec 374 feature/browser family, or extension of existing Spec 373 diagnostic tests. +- **Fixture / helper cost impact**: Reuse existing smoke-login, workspace/environment, support diagnostics, and diagnostic repair fixtures. Do not add seeders or broaden smoke auth unless an established fixture already supports it. +- **Heavy-family visibility / justification**: Browser coverage is explicit and bounded because the source issue is discoverability and first-viewport productization. +- **Special surface test profile**: `standard-native-filament` for Environment Dashboard/action state; `shared-detail-family` for support diagnostics modal; `exception-coded-surface` for direct repair diagnostics page if fixture coverage is partial. +- **Standard-native relief or required special coverage**: ordinary feature coverage plus one browser smoke path. +- **Reviewer handoff**: Verify no new navigation/top-level hub, no provider/permission scope creep, existing destructive repair safety preserved, and no customer/auditor/raw leakage. +- **Budget / baseline / trend impact**: none expected beyond bounded browser smoke. +- **Escalation needed**: `document-in-feature` for fixture gaps; `follow-up-spec` for provider/permission diagnostics, evidence/system reachability, or UI bloat guard if recurring. +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage. +- **Planned validation commands**: + - `git diff --check` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` or the exact replacement Spec 374 repair-diagnostics test file + - `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics` + - exact Spec 374 diagnostic-entry test file/filter if created; otherwise record why no `DiagnosticEntry` filter exists and run the concrete dashboard/support/repair tests that cover it + - `cd apps/platform && ./vendor/bin/sail pint --dirty` if PHP changes + - bounded browser smoke for Environment Dashboard diagnostic entry, support diagnostics modal, and Environment Diagnostics direct page + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Start Diagnostics From The Environment Dashboard (Priority: P1) + +An operator viewing an environment needs a clear first diagnostic entry. The dashboard should make Support Diagnostics the quick, visible path without adding competing buttons or implying that repair diagnostics is the default health hub. + +**Why this priority**: The dashboard is the visible operator starting point and the current modal path is already repo-real. + +**Independent Test**: Open the Environment Dashboard with an authorized fixture. Verify the More action exposes `Open support diagnostics`, its description/purpose is clear, and any repair diagnostics link is absent, secondary, or conditional according to repo-backed repair state. + +**Acceptance Scenarios**: + +1. **Given** an authorized operator is on Environment Dashboard, **When** they open diagnostic-related actions, **Then** Support Diagnostics is the clear quick diagnostic entrypoint. +2. **Given** no repairable diagnostics are active, **When** the dashboard renders, **Then** a repair diagnostics path is not promoted as a primary action. +3. **Given** a repairable diagnostic exists and the implementation adds a repair link, **When** the dashboard renders, **Then** the link is clearly secondary and labeled as repair diagnostics. + +### User Story 2 - Use Repair Diagnostics Without Generic-Hub Confusion (Priority: P2) + +An operator who reaches `/diagnostics` should understand that the page is a repair diagnostics utility for supported membership/access issues, not a general provider, permission, system, or evidence diagnostics hub. + +**Why this priority**: The direct page exists and is reachable by URL/helper, so no-action and repair states must be honest. + +**Independent Test**: Open `/admin/workspaces/{workspace}/environments/{environment}/diagnostics` in no-action and repairable states. Verify the page labels itself around repair diagnostics, shows no-action copy when appropriate, and keeps existing repair action safety intact. + +**Acceptance Scenarios**: + +1. **Given** no supported repair issue is detected, **When** the direct diagnostics page renders, **Then** it states that no repair diagnostics are needed and points broader support context back to Support Diagnostics. +2. **Given** a duplicate-membership repair state exists, or an existing/synthetic missing-owner presentation path is exercised without adding new detection logic, **When** the page renders, **Then** the repair issue, impact, recommended action, and existing repair action are visible without implying broad health coverage. +3. **Given** the current actor lacks the required capability for a repair action, **When** the action is rendered or invoked, **Then** the existing disabled/403 behavior remains unchanged. + +### User Story 3 - Keep Deferred Diagnostics In Their Own Surfaces (Priority: P3) + +An implementer needs a durable matrix that says where provider, permission, system, evidence, and customer review diagnostics belong so future work does not pile unrelated checks into Environment Diagnostics. + +**Why this priority**: The biggest long-term risk is scope creep into a broad diagnostics hub. + +**Independent Test**: Review `diagnostic-entrypoint-matrix.md`, source audit artifacts, and final diff. Provider/permission/system/evidence/customer items should be mapped to owned or deferred surfaces and should not create runtime scope in Spec 374. + +**Acceptance Scenarios**: + +1. **Given** a provider connection is blocked, **When** the matrix is reviewed, **Then** Provider Connections or Required Permissions are the destination, not Environment Repair Diagnostics. +2. **Given** Evidence Snapshot or System panel reachability is blocked, **When** implementation starts, **Then** the gap is documented as deferred rather than fixed in this spec. +3. **Given** a customer asks about review evidence, **When** entrypoint guidance is reviewed, **Then** Customer Review Workspace or Review Pack remains the destination, not support diagnostics. + +### Edge Cases + +- `ManagedEnvironmentLinks::diagnosticsUrl()` may have no runtime caller. If so, document it as a deeplink utility or add exactly one secondary/conditional use if repo-backed and low-risk. +- A repo-backed repair value means an existing repair truth already available in this slice, currently duplicate membership for the current user, or a documented secondary deeplink utility decision. It does not authorize new missing-owner detection or new repair logic. +- Missing-owner repair detection is not currently repo-active because `ManagedEnvironmentDiagnosticsService::tenantHasNoOwners()` returns `false`; Spec 374 may preserve/test the existing presentation path but MUST NOT implement owner-detection or owner-recovery behavior. +- The current smoke fixture may only provide a no-action repair diagnostics state. Do not create a new seeder just for screenshots unless an existing browser fixture pattern already supports it. +- Support diagnostics may lack a specific recommended first check. Show a calm fallback rather than inventing likely cause. +- Repair diagnostics may have multiple blockers. Show one dominant repair path and demote secondary repair paths without hiding real blockers. +- Provider/permission/system/evidence issues may be discoverable from support diagnostics sections. They remain related context, not repair diagnostics responsibilities. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-374-001**: Environment Dashboard MUST keep Support Diagnostics as the official quick diagnostic entrypoint for environment-near support cases. +- **FR-374-002**: Environment Dashboard MUST NOT promote the direct repair diagnostics page as a primary generic diagnostics hub. +- **FR-374-003**: If a repair diagnostics link is added from Environment Dashboard, it MUST be secondary, clearly labeled as repair diagnostics, and shown only when existing repair truth indicates value or when the matrix/source audit explicitly justifies one secondary deeplink utility. +- **FR-374-004**: Support Diagnostics modal MUST explain its purpose as quick support context and recommended first-check review before lower technical sections. +- **FR-374-005**: Environment Diagnostics page MUST frame itself as repair diagnostics for supported environment access or membership issues, not all environment health. +- **FR-374-006**: Environment Diagnostics no-action state MUST say no supported repair diagnostics are active and MUST direct broader support context to the Environment Dashboard support diagnostics entry. +- **FR-374-007**: Existing Environment Diagnostics repair actions MUST retain `->action(...)`, `->requiresConfirmation()`, destructive/action styling where applicable, capability gating, server-side authorization, and audit/service ownership. +- **FR-374-008**: `ManagedEnvironmentLinks::diagnosticsUrl()` usage MUST be audited and documented as used, unused-retained, or adopted for one secondary repair diagnostics link. +- **FR-374-009**: Provider/Permission Diagnostics MUST remain mapped to Provider Connections, Required Permissions, or a future provider-readiness follow-up, not added to the repair diagnostics page. +- **FR-374-010**: System, Evidence, and Customer Review diagnostic needs MUST be mapped to their owned/deferred surfaces in the matrix and not implemented inside Spec 374. +- **FR-374-011**: The implementation MUST create/update `diagnostic-entrypoint-matrix.md`, `source-audit-summary.md`, `affected-files.md`, `implementation-notes.md`, browser report, screenshot index, validation report, and follow-up recommendations. +- **FR-374-012**: No new migrations, tables, persisted diagnostic truth, provider/Graph checks, OperationRun types, packages, panel providers, or top-level navigation entries MAY be introduced. +- **FR-374-013**: If runtime copy changes the canonical page noun from Environment Diagnostics toward Repair Diagnostics, action-surface declarations and exemptions MUST be audited and either updated consistently or documented as intentionally retained. + +### Non-Functional Requirements + +- **NFR-374-001**: Page and modal rendering MUST remain DB-local and MUST NOT perform Graph/provider HTTP calls during render. +- **NFR-374-002**: Default-visible diagnostic copy MUST avoid secrets, raw provider payloads, unrestricted logs, raw credential values, and customer-unsafe debug detail. +- **NFR-374-003**: UI changes MUST reuse native Filament components or existing shared primitives before local Blade/Tailwind styling, and any local Blade/Tailwind change MUST document why it stays within existing Filament visual language. +- **NFR-374-004**: Browser proof MUST document blocked/unavailable states honestly rather than faking screenshots or fixture states. + +## UI Action Matrix *(mandatory when Filament is changed)* + +| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions | Bulk Actions | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions | +|---|---|---|---|---|---|---|---|---|---|---| +| Environment Dashboard | `apps/platform/app/Filament/Pages/EnvironmentDashboard.php` | existing More actions including `Open support diagnostics`; optional secondary repair diagnostics link only if repo-backed | N/A | N/A | N/A | N/A | N/A | N/A | existing support diagnostics telemetry/audit preserved | no new primary diagnostic hub action | +| Support Diagnostics modal | `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` | source action opens modal | N/A | N/A | N/A | N/A | N/A | N/A | existing telemetry/audit preserved | read-only support modal | +| Environment Diagnostics | `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` | existing `Bootstrap owner`, `Merge duplicate access scopes` when applicable | N/A | N/A | N/A | N/A | existing repair actions in header | N/A | existing service/audit ownership preserved | singleton repair diagnostics page | + +### Key Entities + +No new entities. + +Existing records in scope as source/context only: + +- **Workspace**: platform workspace scope. +- **ManagedEnvironment**: environment target for dashboard, support diagnostics, and repair diagnostics. +- **OperationRun / ProviderConnection / Evidence / Review artifacts**: existing related context inside support diagnostics or deferred surfaces only. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-374-001**: In browser smoke, an authorized operator can open Support Diagnostics from Environment Dashboard and see the official quick diagnostic purpose. +- **SC-374-002**: In browser smoke or feature tests, Environment Diagnostics no-action state states repair-only/no-action semantics and does not imply broad health coverage. +- **SC-374-003**: Feature tests prove existing repair actions keep confirmation, capability gating, and server-side authorization. +- **SC-374-004**: `diagnostic-entrypoint-matrix.md` maps at least support quick context, repairable membership issue, no-owner state, duplicate membership, provider connection blocker, missing required permissions, evidence snapshot issue, system-level issue, and customer review evidence question. +- **SC-374-005**: Final diff contains no runtime changes to Provider Connections, Required Permissions, System panel auth, Evidence Snapshot reachability, OperationRun lifecycle, migrations, packages, or panel provider registration unless explicitly documented as an unavoidable bounded regression fix. + +## Risks + +- Dashboard action copy could become too verbose or compete with primary environment decisions. +- Repair diagnostics could become more discoverable than its narrow scope justifies. +- Browser fixture may not cover a repair-blocker state. +- Provider/permission blockers may be tempting to include because they feel diagnostic, but they belong to separate surfaces. + +## Assumptions + +- Spec 373 implementation results are the current repo truth on this branch. +- Environment Dashboard remains the primary operator starting surface for environment-near work. +- Support Diagnostics modal remains the official quick support context entry. +- Environment Diagnostics remains a direct route and can remain non-navigation/discoverability-limited. +- The product does not need a generic Diagnostic Hub in this slice. + +## Open Questions + +- None blocking preparation. +- Implementation should verify whether `ManagedEnvironmentLinks::diagnosticsUrl()` remains unused and whether one conditional secondary link is justified. + +## Follow-up Spec Candidates + +- Provider/Permission Diagnostic Productization, if fresh evidence shows Provider Connections or Required Permissions still fail as diagnostic destinations after Spec 353. +- Evidence/System browser fixture coverage, if browser reachability remains blocked and product priority warrants a fixture/auth spec. +- UI Bloat Regression Guard, if repeated surface work keeps adding diagnostic or support entrypoints without a shared review gate. +- Support Desk / PSA Handoff productization, separate from support diagnostics entrypoint semantics. diff --git a/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/tasks.md b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/tasks.md new file mode 100644 index 00000000..5a8f6e12 --- /dev/null +++ b/specs/374-diagnostic-entry-point-support-diagnostics-consolidation/tasks.md @@ -0,0 +1,194 @@ +# Tasks: Spec 374 - Diagnostic Entry Point and Support Diagnostics Consolidation v1 + +**Input**: `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md`, `plan.md`, `checklists/requirements.md`, Spec 373 completed artifacts, Spec 370 IA contract, and current dashboard/diagnostics/support code. + +**Tests**: Required for later implementation. This spec changes existing operator/support-facing diagnostic entrypoint semantics and repair diagnostics framing. + +## Implementation Notes For Task Completion + +- T017/T032/T033 were satisfied by proving no dashboard repair diagnostics link is needed in the current no-action state; `ManagedEnvironmentLinks::diagnosticsUrl()` remains a retained canonical deeplink utility, while Repair Diagnostics now exposes one secondary read-only `Open support diagnostics` modal action. +- T031 was satisfied by preserving the existing dashboard `Open support diagnostics` action label/grouping and updating only the modal description copy in localization. +- T041 was not needed because existing `SupportDiagnosticBundleBuilder` recommended-first-check data was sufficient. +- T045/T063 were not applicable because Provider Connections and Required Permissions runtime files were not touched. +- T058 was documented as fixture-limited: the browser smoke did not create a repair/blocker state, while missing-owner and duplicate-membership states remain covered by Feature/Livewire tests. +- The implementation commands were run with local `php artisan test` / `php vendor/bin/pint` instead of Sail because the local non-Docker test harness was available and passed. + +## Test Governance Checklist + +- [x] Lane assignment is named and narrow: Feature/Livewire for action/page/modal rendering, Browser for entrypoint discoverability, static checks for artifact quality. +- [x] New or changed tests stay in the smallest honest family; browser coverage is explicit and bounded to Spec 374 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`, `shared-detail-family`, and `exception-coded-surface` profiles are applied only where they match the proof need. +- [x] Any material fixture, browser, or follow-up note is recorded in the active spec artifacts. + +## Phase 1: Preparation And Repo Truth + +**Purpose**: Confirm Spec 374 is an entrypoint/productization follow-up, not a rewrite of completed Spec 373. + +- [x] T001 Re-read `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`. +- [x] T002 Re-read completed Spec 373 governing artifacts: + - `specs/373-diagnostic-surface-separation/spec.md` + - `specs/373-diagnostic-surface-separation/plan.md` + - `specs/373-diagnostic-surface-separation/tasks.md` + - `specs/373-diagnostic-surface-separation/artifacts/source-audit-summary.md` + - `specs/373-diagnostic-surface-separation/artifacts/diagnostic-surface-contracts.md` + - `specs/373-diagnostic-surface-separation/artifacts/browser-verification-report.md` + - `specs/373-diagnostic-surface-separation/artifacts/validation-report.md` +- [x] T003 Re-read Spec 370 IA inputs: + - `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/copy-and-terminology-rules.md` +- [x] T004 Re-read current runtime truth in: + - `apps/platform/routes/web.php` + - `apps/platform/app/Filament/Pages/EnvironmentDashboard.php` + - `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` + - `apps/platform/app/Support/ManagedEnvironmentLinks.php` + - `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` + - `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` + - `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` +- [x] T005 Search and document all current `ManagedEnvironmentLinks::diagnosticsUrl()` callers. +- [x] T006 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**: Prevent the implementation from growing into a diagnostic hub. + +- [x] T007 Update `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/source-audit-summary.md` with final pre-edit route/action/helper/code findings. +- [x] T008 Update `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/diagnostic-entrypoint-matrix.md` with all required diagnostic need rows and final in-scope/deferred decisions. +- [x] T009 Create `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/implementation-notes.md` with planned product decision sections before runtime edits. +- [x] T010 Create `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/browser-verification-report.md` with planned URLs, fixture assumptions, and screenshot slots before browser work. +- [x] T011 Create `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/before-after-screenshot-index.md` with Spec 373/route-inventory before evidence and planned after slots. +- [x] T012 Create `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/follow-up-recommendations.md` with deferred Provider/Permission, Evidence/System, UI Bloat Guard, and long-term repair-diagnostics discoverability sections. +- [x] T013 Update `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/affected-files.md` with planned runtime/docs/test/artifact rows before code changes. +- [x] T014 Update `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/validation-report.md` with branch, HEAD, dirty state before implementation, and planned commands. + +## Phase 3: Tests First - Dashboard Entry And Repair Link Semantics + +**Purpose**: Lock the official diagnostic entrypoint and avoid button overload before changing UI copy. + +- [x] T015 Add or update Feature/Livewire coverage proving Environment Dashboard exposes `Open support diagnostics` as the clear quick diagnostic entry. +- [x] T016 Add or update coverage proving no repair diagnostics action is promoted as a primary dashboard action when no repairable diagnostics are active. +- [x] T017 If a repair diagnostics link is implemented, add coverage proving it is secondary, clearly labeled, and shown only when existing repair truth indicates value or the matrix/source audit documents one secondary deeplink utility rationale. +- [x] T018 Add assertions that dashboard diagnostic entry changes do not add new top-level navigation, alter panel provider registration, or call Graph/provider HTTP during render. + +## Phase 4: Tests First - Repair Diagnostics Page + +**Purpose**: Make the direct page honest about being repair diagnostics, not all environment health. + +- [x] T019 Add or update Feature/Livewire coverage for the no-action Environment Diagnostics state: repair-only language, broader support context handoff, and no broad health claim. +- [x] T020 Add or update coverage for the existing missing-owner presentation path only if it can be exercised without adding runtime missing-owner detection or owner-recovery logic. +- [x] T021 Add or update coverage for duplicate-membership repair state. +- [x] T022 Add or update coverage for both repair blockers, ensuring one dominant repair path and secondary repair path demotion. +- [x] T023 Add or update assertions that existing `bootstrapOwner` and `mergeDuplicateMemberships` actions remain visible only when applicable. +- [x] T024 Add or update assertions that existing repair actions keep confirmation, capability gating, destructive treatment, and server-side authorization behavior. +- [x] T025 Add or update assertions that repair diagnostics render paths do not call Graph/provider HTTP, use existing DB-local truth only, and keep action-surface metadata consistent if the canonical noun changes to Repair Diagnostics. + +## Phase 5: Tests First - Support Diagnostics Modal + +**Purpose**: Preserve the official quick support entry while avoiding invented likely causes. + +- [x] T026 Add or update support diagnostics modal coverage proving the modal explains its quick support purpose before lower technical sections. +- [x] T027 Add or update coverage proving recommended first check appears when repo-backed data exists. +- [x] T028 Add or update coverage for calm fallback copy when no specific recommended first check exists. +- [x] T029 Add or update assertions that redaction markers and raw/support detail remain lower-priority, redacted, unavailable, or capability-gated. +- [x] T030 Add or update assertions that support diagnostics telemetry, audit, authorization behavior, and DB-local/no-Graph rendering remain unchanged when the modal opens. + +## Phase 6: Dashboard Diagnostic Entry Implementation + +**Purpose**: Productize the visible entrypoint without making diagnostics compete with dashboard decisions. + +- [x] T031 Update `apps/platform/app/Filament/Pages/EnvironmentDashboard.php` only if needed to clarify `Open support diagnostics` label, description, grouping, or tooltip. +- [x] T032 If adding a repair diagnostics link, use `ManagedEnvironmentLinks::diagnosticsUrl()` or an existing route helper in exactly one secondary placement, and record whether the trigger is existing repair truth or a documented secondary deeplink utility. +- [x] T033 Ensure any repair diagnostics link is clearly labeled as repair diagnostics and does not displace the dashboard's primary decisions. +- [x] T034 Do not add a top-level navigation item, a dashboard diagnostic hub card, or multiple competing diagnostic buttons. + +## Phase 7: Repair Diagnostics Page Implementation + +**Purpose**: Make the direct route repair-only and honest in no-action states. + +- [x] T035 Update `apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php` only if needed for page-local derived labels or secondary handoff text. +- [x] T036 Update `apps/platform/resources/views/filament/pages/environment-diagnostics.blade.php` so the page title/body/state copy says repair diagnostics and no supported repair diagnostics when no issue exists. +- [x] T037 Add or preserve clear copy that broader support context starts from `Open support diagnostics`, with Environment Dashboard remaining the official quick entrypoint and Repair Diagnostics providing a secondary handoff. +- [x] T038 Preserve existing `ActionSurfaceDeclaration`, `ActionSurfaceExemptions`, `UiEnforcement`, `Capabilities::TENANT_MANAGE`, confirmation, action handlers, and repair service ownership; update or explicitly retain existing ManagedEnvironment diagnostics terminology if rendered copy moves to Repair Diagnostics. +- [x] T039 Keep provider/permission/evidence/system checks out of the repair page unless already repo-backed as lower related context and explicitly documented. + +## Phase 8: Support Diagnostics Modal Implementation + +**Purpose**: Confirm the modal is the official quick diagnostics/support context path. + +- [x] T040 Update `apps/platform/resources/views/filament/modals/support-diagnostic-bundle.blade.php` only if current copy does not clearly state the quick support diagnostics purpose. +- [x] T041 If existing bundle data is insufficient for fallback first-check copy, update `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` narrowly using existing truth only. +- [x] T042 Keep support modal read-only, redacted, capability-gated, and lower sections secondary. +- [x] T043 Document native Filament/shared primitive reuse for any changed Blade/Tailwind composition, including why no ad-hoc status/button/card language was introduced, and do not add support request lifecycle, PSA, AI, export, provider, permission, or OperationRun behavior. + +## Phase 9: Deferred Surface And UI Audit Handling + +**Purpose**: Keep follow-up diagnostics out of Spec 374 while recording durable decisions. + +- [x] T044 Confirm Provider Connections and Required Permissions runtime files are unchanged unless a shared helper change requires a targeted regression note. +- [x] T045 If Provider Connections or Required Permissions are touched by shared code, run focused Spec 353-style regression tests and document why the touch was unavoidable. +- [x] T046 Update `docs/ui-ux-enterprise-audit/page-reports/ui-002-environment-dashboard.md` only if dashboard diagnostic action evidence/status changes materially. +- [x] T047 Update `docs/ui-ux-enterprise-audit/page-reports/ui-012-environment-diagnostics.md` if repair diagnostics evidence/status changes materially. +- [x] T048 Update `docs/ui-ux-enterprise-audit/route-inventory.md` only if screenshot/report references or classifications materially change. +- [x] T049 Update `docs/ui-ux-enterprise-audit/unresolved-pages.md` only if a scoped modal/page remains unreachable and needs durable tracking. + +## Phase 10: Browser Smoke And Screenshots + +**Purpose**: Prove entrypoint discoverability and honest page semantics in the browser. + +- [x] T050 Start the local platform stack using Sail or the repo's platform dev command. +- [x] T051 Resolve/open the Environment Dashboard with the existing smoke-login/browser fixture. +- [x] T052 Capture `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/001-environment-dashboard-diagnostic-entry-after.png` if reachable. +- [x] T053 Open Support Diagnostics from the dashboard action/modal. +- [x] T054 Capture `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/002-support-diagnostics-modal-after.png` if reachable. +- [x] T055 Open the direct Environment Diagnostics / Repair Diagnostics page. +- [x] T056 Capture `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/003-environment-repair-diagnostics-after.png` if reachable. +- [x] T057 Verify no-action state and capture the secondary support handoff as `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/screenshots/004-environment-repair-diagnostics-support-modal-after.png` if reachable. +- [x] T058 Verify repair/blocker state if the fixture can create it safely; otherwise document `not available` and do not fake the state. +- [x] T059 Verify browser console has no new JavaScript/runtime errors for the scoped flow. + +## Phase 11: Validation And Close-Out Artifacts + +**Purpose**: Finish with bounded proof and no application-scope creep. + +- [x] T060 Run the exact Spec 374 diagnostic-entry test file/filter if created; otherwise record why no `DiagnosticEntry` filter exists and run the concrete dashboard/support/repair test files that cover it. +- [x] T061 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantDiagnosticsRepairsTest.php` or the exact replacement Spec 374 repair-diagnostics test file. +- [x] T062 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=SupportDiagnostics`. +- [x] T063 Run focused Spec 353 regression tests only if Provider Connections or Required Permissions were touched by shared code. +- [x] T064 Run `cd apps/platform && ./vendor/bin/sail pint --dirty` if PHP files changed. +- [x] T065 Run `git diff --check`. +- [x] T066 Complete `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/affected-files.md` with final touched files, risk, verification class, and out-of-scope side effects. +- [x] T067 Complete `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/browser-verification-report.md` with URLs, fixture, screenshots, reachability, blocked pages, and remaining issues. +- [x] T068 Complete `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/before-after-screenshot-index.md`. +- [x] T069 Complete `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/implementation-notes.md`. +- [x] T070 Complete `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/follow-up-recommendations.md`. +- [x] T071 Complete `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/validation-report.md` with tests, browser results, dirty state, runtime files changed, and recommended next spec. +- [x] T072 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 build a diagnostic hub. +- [x] NT002 Do not register Environment Diagnostics as top-level navigation. +- [x] NT003 Do not reimplement Provider Connections or Required Permissions readiness guidance. +- [x] NT004 Do not solve `/system` auth or browser fixture reachability. +- [x] NT005 Do not solve Evidence Snapshot reachability or customer review evidence productization. +- [x] NT006 Do not change ProviderGateway, provider health resolver, provider credential, Microsoft Graph permission calculation, or Graph contracts. +- [x] NT007 Do not add migrations, new models, persisted diagnostic truth, enum/status families, or provider/onboarding frameworks. +- [x] NT008 Do not add new Graph calls or provider HTTP calls during render. +- [x] NT009 Do not add support request lifecycle, external PSA handoff, AI, automation, billing, or entitlement behavior. +- [x] NT010 Do not intentionally refactor customer/auditor surfaces or core operator surfaces from Specs 371/372. +- [x] NT011 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 updated before tests and implementation so scope drift is visible. +- Phases 3, 4, and 5 test work should precede Phases 6, 7, and 8 implementation. +- Phase 9 runs after any shared-code touch and before browser close-out. +- Phase 10 browser smoke runs after targeted tests are green enough to make rendered proof meaningful. +- Phase 11 closes the implementation package. + +## Recommended Implementation Strategy + +Deliver User Story 1 first: the dashboard entrypoint and support diagnostics modal. Then refine direct repair diagnostics no-action semantics. Treat provider/permission/system/evidence/customer review diagnostics as matrix/follow-up decisions unless a shared change creates a bounded regression.