getId() !== 'admin') { return false; } return parent::shouldRegisterNavigation(); } public static function canViewAny(): bool { $user = auth()->user(); if (! $user instanceof User) { return false; } $workspace = self::resolveWorkspace(); if (! $workspace instanceof Workspace) { return false; } $resolver = app(\App\Services\Auth\WorkspaceCapabilityResolver::class); return $resolver->isMember($user, $workspace) && $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_VIEW); } public static function canCreate(): bool { return false; } public static function canEdit(Model $record): bool { return false; } public static function canDelete(Model $record): bool { return false; } public static function canView(Model $record): bool { if (! $record instanceof BaselineSnapshot) { return false; } $workspace = self::resolveWorkspace(); if (! $workspace instanceof Workspace) { return false; } return self::canViewAny() && (int) $record->workspace_id === (int) $workspace->getKey(); } public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView) ->exempt(ActionSurfaceSlot::ListHeader, 'Snapshots are created by capture runs; no list-header actions.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Snapshots are immutable; rows navigate directly to the detail page.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Snapshots are immutable; no bulk actions.') ->exempt(ActionSurfaceSlot::ListEmptyState, 'Empty-state CTA is intentionally omitted; snapshots appear after baseline captures.') ->satisfy(ActionSurfaceSlot::DetailHeader, 'Primary related navigation is surfaced in the view header.'); } public static function getEloquentQuery(): Builder { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); return parent::getEloquentQuery() ->with('baselineProfile') ->when( $workspaceId !== null, fn (Builder $query): Builder => $query->where('workspace_id', (int) $workspaceId), ) ->when( $workspaceId === null, fn (Builder $query): Builder => $query->whereRaw('1 = 0'), ); } public static function form(Schema $schema): Schema { return $schema; } public static function table(Table $table): Table { return $table ->defaultSort('captured_at', 'desc') ->paginated(\App\Support\Filament\TablePaginationProfiles::resource()) ->persistFiltersInSession() ->persistSearchInSession() ->persistSortInSession() ->columns([ TextColumn::make('id') ->label('Snapshot') ->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—') ->sortable(), TextColumn::make('baselineProfile.name') ->label('Baseline') ->wrap() ->searchable() ->placeholder('—'), TextColumn::make('captured_at') ->label('Captured') ->since() ->sortable(), TextColumn::make('fidelity_summary') ->label('Fidelity') ->getStateUsing(static fn (BaselineSnapshot $record): string => self::fidelitySummary($record)) ->wrap(), TextColumn::make('snapshot_state') ->label('State') ->badge() ->getStateUsing(static fn (BaselineSnapshot $record): string => self::stateLabel($record)) ->color(static fn (BaselineSnapshot $record): string => self::gapSpec($record)->color) ->icon(static fn (BaselineSnapshot $record): ?string => self::gapSpec($record)->icon) ->iconColor(static fn (BaselineSnapshot $record): ?string => self::gapSpec($record)->iconColor), ]) ->recordUrl(static fn (BaselineSnapshot $record): ?string => static::canView($record) ? static::getUrl('view', ['record' => $record]) : null) ->filters([ SelectFilter::make('baseline_profile_id') ->label('Baseline') ->options(static::baselineProfileOptions()) ->searchable(), SelectFilter::make('snapshot_state') ->label('State') ->options(static::snapshotStateOptions()) ->query(fn (Builder $query, array $data): Builder => static::applySnapshotStateFilter($query, $data['value'] ?? null)), FilterPresets::dateRange('captured_at', 'Captured', 'captured_at'), ]) ->actions([ static::primaryRelatedAction(), ]) ->bulkActions([]) ->emptyStateHeading('No baseline snapshots') ->emptyStateDescription('Capture a baseline snapshot to review evidence fidelity and compare tenants over time.') ->emptyStateIcon('heroicon-o-camera'); } public static function infolist(Schema $schema): Schema { return $schema; } private static function primaryRelatedAction(): Action { return Action::make('primary_drill_down') ->label(fn (BaselineSnapshot $record): string => static::primaryRelatedEntry($record)?->actionLabel ?? 'Open related record') ->url(fn (BaselineSnapshot $record): ?string => static::primaryRelatedEntry($record)?->targetUrl) ->hidden(fn (BaselineSnapshot $record): bool => ! (static::primaryRelatedEntry($record)?->isAvailable() ?? false)) ->color('gray'); } private static function primaryRelatedEntry(BaselineSnapshot $record): ?RelatedContextEntry { return app(RelatedNavigationResolver::class) ->primaryListAction(CrossResourceNavigationMatrix::SOURCE_BASELINE_SNAPSHOT, $record); } public static function getPages(): array { return [ 'index' => Pages\ListBaselineSnapshots::route('/'), 'view' => Pages\ViewBaselineSnapshot::route('/{record}'), ]; } /** * @return array */ private static function baselineProfileOptions(): array { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if ($workspaceId === null) { return []; } return BaselineProfile::query() ->where('workspace_id', (int) $workspaceId) ->orderBy('name') ->pluck('name', 'id') ->all(); } /** * @return array */ private static function snapshotStateOptions(): array { return BadgeCatalog::options(BadgeDomain::BaselineSnapshotGapStatus, ['clear', 'gaps_present']); } public static function resolveWorkspace(): ?Workspace { $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if ($workspaceId === null) { return null; } return Workspace::query()->whereKey($workspaceId)->first(); } private static function summary(BaselineSnapshot $snapshot): array { return is_array($snapshot->summary_jsonb) ? $snapshot->summary_jsonb : []; } private static function fidelityCounts(BaselineSnapshot $snapshot): array { $summary = self::summary($snapshot); $counts = $summary['fidelity_counts'] ?? null; $counts = is_array($counts) ? $counts : []; $content = $counts['content'] ?? 0; $meta = $counts['meta'] ?? 0; return [ 'content' => is_numeric($content) ? (int) $content : 0, 'meta' => is_numeric($meta) ? (int) $meta : 0, ]; } private static function fidelitySummary(BaselineSnapshot $snapshot): string { $counts = self::fidelityCounts($snapshot); return sprintf( '%s %d, %s %d', BadgeCatalog::spec(BadgeDomain::BaselineSnapshotFidelity, FidelityState::Full->value)->label, (int) ($counts['content'] ?? 0), BadgeCatalog::spec(BadgeDomain::BaselineSnapshotFidelity, FidelityState::ReferenceOnly->value)->label, (int) ($counts['meta'] ?? 0), ); } private static function gapsCount(BaselineSnapshot $snapshot): int { $summary = self::summary($snapshot); $gaps = $summary['gaps'] ?? null; $gaps = is_array($gaps) ? $gaps : []; $byReason = is_array($gaps['by_reason'] ?? null) ? $gaps['by_reason'] : []; if ($byReason !== []) { return array_sum(array_map( static fn (mixed $count, string $reason): int => in_array($reason, ['meta_fallback'], true) || ! is_numeric($count) ? 0 : (int) $count, $byReason, array_keys($byReason), )); } $count = $gaps['count'] ?? 0; return is_numeric($count) ? (int) $count : 0; } private static function hasGaps(BaselineSnapshot $snapshot): bool { return self::gapsCount($snapshot) > 0; } private static function stateLabel(BaselineSnapshot $snapshot): string { return self::gapSpec($snapshot)->label; } private static function applySnapshotStateFilter(Builder $query, mixed $value): Builder { if (! is_string($value) || trim($value) === '') { return $query; } $gapCountExpression = self::gapCountExpression($query); return match ($value) { 'clear' => $query->whereRaw("{$gapCountExpression} = 0"), 'gaps_present' => $query->whereRaw("{$gapCountExpression} > 0"), default => $query, }; } private static function gapCountExpression(Builder $query): string { return match ($query->getConnection()->getDriverName()) { 'sqlite' => "COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.missing_evidence') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.missing_role_definition_version_reference') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.invalid_subject') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.duplicate_subject') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.policy_not_found') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.throttled') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.capture_failed') AS INTEGER), 0) + COALESCE(CAST(json_extract(summary_jsonb, '$.gaps.by_reason.budget_exhausted') AS INTEGER), 0)", 'pgsql' => "COALESCE((summary_jsonb #>> '{gaps,by_reason,missing_evidence}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,missing_role_definition_version_reference}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,invalid_subject}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,duplicate_subject}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,policy_not_found}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,throttled}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,capture_failed}')::int, 0) + COALESCE((summary_jsonb #>> '{gaps,by_reason,budget_exhausted}')::int, 0)", default => "COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.missing_evidence') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.missing_role_definition_version_reference') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.invalid_subject') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.duplicate_subject') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.policy_not_found') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.throttled') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.capture_failed') AS UNSIGNED), 0) + COALESCE(CAST(JSON_EXTRACT(summary_jsonb, '$.gaps.by_reason.budget_exhausted') AS UNSIGNED), 0)", }; } private static function gapSpec(BaselineSnapshot $snapshot): \App\Support\Badges\BadgeSpec { return BadgeCatalog::spec( BadgeDomain::BaselineSnapshotGapStatus, self::hasGaps($snapshot) ? 'gaps_present' : 'clear', ); } }