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.') ->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; 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 diagnostic action is required', 'body' => 'No actionable tenant membership defect is visible for the current managed-environment context.', 'status' => 'No action required', 'color' => 'gray', 'impact' => 'This page did not find a tenant membership repair to run for the current user and environment.', 'next_check' => 'Review provider, operation, or audit surfaces only when another page reports a blocker.', 'primary_action_label' => null, 'secondary_action_label' => null, 'blockers' => [], ]; } $primaryBlocker = $blockers[0]; $secondaryBlocker = $blockers[1] ?? null; return [ 'headline' => $blockerCount === 1 ? '1 diagnostic blocker needs attention' : $blockerCount.' diagnostic blockers need attention', 'body' => 'Resolve the highest-impact tenant membership blocker first; lower repair paths remain visible for context.', 'status' => $blockerCount === 1 ? 'Action needed' : $blockerCount.' blockers', 'color' => 'warning', 'impact' => $primaryBlocker['impact'], 'next_check' => $primaryBlocker['next_check'], 'primary_action_label' => $primaryBlocker['action_label'], 'secondary_action_label' => $secondaryBlocker['action_label'] ?? null, 'blockers' => $blockers, ]; } /** * @return array */ protected function getHeaderActions(): array { return [ 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), ]; } 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(); } }