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.'); } public bool $missingOwner = false; public bool $hasDuplicateMembershipsForCurrentUser = false; /** * @var list */ public array $supportDiagnosticsAuditKeys = []; public function mount(): void { $tenant = static::resolveTenantContextForCurrentPanelOrFail(); $this->missingOwner = app(ManagedEnvironmentDiagnosticsService::class)->tenantHasNoOwners($tenant); $user = auth()->user(); if (! $user instanceof User) { abort(403, 'Not allowed'); } $this->hasDuplicateMembershipsForCurrentUser = app(ManagedEnvironmentDiagnosticsService::class) ->userHasDuplicateMemberships($tenant, $user); } /** * @return array{ * headline: string, * body: string, * status: string, * color: string, * impact: string, * next_check: string, * primary_action_label: ?string, * secondary_action_label: ?string, * blockers: list * } */ public function diagnosticSummary(): array { $blockers = []; if ($this->missingOwner) { $blockers[] = [ 'key' => 'missing_owner', 'label' => 'Missing owner', 'failed_condition' => 'No Owner membership is currently visible for this managed environment.', 'impact' => 'Tenant repair and accountability workflows need an owner before support can treat access as complete.', 'next_check' => 'Confirm workspace role recovery, then use Bootstrap owner only when the current administrator is authorized to repair tenant scope.', 'action_label' => 'Bootstrap owner', 'action_role' => 'Primary repair path', ]; } if ($this->hasDuplicateMembershipsForCurrentUser) { $blockers[] = [ 'key' => 'duplicate_memberships', 'label' => 'Duplicate memberships', 'failed_condition' => 'The current user has more than one tenant membership row for this managed environment.', 'impact' => 'Duplicate access scope rows make authorization support harder to reason about for this user.', 'next_check' => 'Merge the duplicate rows, then reload the diagnostics page to confirm only one membership remains.', 'action_label' => 'Merge duplicate access scopes', 'action_role' => count($blockers) === 0 ? 'Primary repair path' : 'Secondary repair path', ]; } $blockerCount = count($blockers); if ($blockerCount === 0) { return [ 'headline' => 'No repair diagnostics are active', 'body' => 'No supported access or membership repair is active for this managed environment.', 'status' => 'No repair action', 'color' => 'gray', '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' => [], ]; } $primaryBlocker = $blockers[0]; $secondaryBlocker = $blockers[1] ?? null; return [ 'headline' => $blockerCount === 1 ? '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'], 'next_check' => $primaryBlocker['next_check'], 'primary_action_label' => $primaryBlocker['action_label'], 'secondary_action_label' => $secondaryBlocker['action_label'] ?? null, 'blockers' => $blockers, ]; } /** * @return array */ protected function getHeaderActions(): array { return [ $this->openSupportDiagnosticsAction(), UiEnforcement::forScopedAction( Action::make('bootstrapOwner') ->label('Bootstrap owner') ->requiresConfirmation() ->action(fn () => $this->bootstrapOwner()), fn (): UiActionContext => static::tenantUiActionContext(), ) ->requireCapability(Capabilities::TENANT_MANAGE) ->destructive() ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION) ->apply() ->visible(fn (): bool => $this->missingOwner), UiEnforcement::forScopedAction( Action::make('mergeDuplicateMemberships') ->label('Merge duplicate access scopes') ->requiresConfirmation() ->action(fn () => $this->mergeDuplicateMemberships()), fn (): UiActionContext => static::tenantUiActionContext(), ) ->requireCapability(Capabilities::TENANT_MANAGE) ->destructive() ->tooltip(UiTooltips::INSUFFICIENT_PERMISSION) ->apply() ->visible(fn (): bool => $this->hasDuplicateMembershipsForCurrentUser), ]; } 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(); $user = auth()->user(); if (! $user instanceof User) { abort(403, 'Not allowed'); } app(ManagedEnvironmentMembershipManager::class)->grantScope($tenant, $user, $user, sourceRef: 'diagnostic'); $this->mount(); } public function mergeDuplicateMemberships(): void { $tenant = static::resolveTenantContextForCurrentPanelOrFail(); $user = auth()->user(); if (! $user instanceof User) { abort(403, 'Not allowed'); } app(ManagedEnvironmentDiagnosticsService::class)->mergeDuplicateMembershipsForUser($tenant, $user, $user); $this->mount(); } }