*/ private const REPORT_TYPE_CAPABILITIES = [ StoredReport::REPORT_TYPE_PERMISSION_POSTURE => Capabilities::PERMISSION_POSTURE_VIEW, StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES => Capabilities::ENTRA_ROLES_VIEW, ]; protected static ?string $model = StoredReport::class; protected static ?string $slug = 'stored-reports'; protected static ?string $tenantOwnershipRelationshipName = 'tenant'; protected static bool $isGloballySearchable = false; protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-chart-bar'; protected static string|UnitEnum|null $navigationGroup = 'Reporting'; protected static ?string $navigationLabel = 'Stored reports'; protected static ?int $navigationSort = 49; public static function canViewAny(): bool { return static::visibleReportTypesForCurrentUser() !== []; } public static function canView(Model $record): bool { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); if (! $tenant instanceof Tenant || ! $user instanceof User) { return false; } if (! $user->canAccessTenant($tenant)) { return false; } if (! $record instanceof StoredReport) { return true; } if ((int) $record->tenant_id !== (int) $tenant->getKey() || (int) $record->workspace_id !== (int) $tenant->workspace_id) { return false; } $capability = static::capabilityForReportType((string) $record->report_type); return $capability !== null && $user->can($capability, $tenant); } 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 actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView, ActionSurfaceType::ReadOnlyRegistryReport) ->exempt(ActionSurfaceSlot::ListHeader, 'Stored reports are read-only in v1 and do not expose generation, rerun, export, or mutation actions.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Clickable-row inspection is the only row action for the read-only stored-report register.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Stored reports do not support bulk actions in v1.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state links back to the tenant overview without implying report generation from this surface.') ->satisfy(ActionSurfaceSlot::DetailHeader, 'Historical detail exposes the single navigation action Open current report; current detail has no header action.'); } public static function getEloquentQuery(): Builder { $query = static::getTenantOwnedEloquentQuery() ->with('tenant') ->whereIn('report_type', static::supportedReportTypes()); $tenant = static::resolveTenantContextForCurrentPanel(); if ($tenant instanceof Tenant) { $query->where('workspace_id', (int) $tenant->workspace_id); } $visibleReportTypes = static::visibleReportTypesForCurrentUser(); if ($visibleReportTypes === []) { return $query->whereRaw('1 = 0'); } return $query->whereIn('report_type', $visibleReportTypes); } public static function resolveScopedRecordOrFail(int|string|null $record): Model { $query = parent::getEloquentQuery() ->with('tenant') ->whereIn('report_type', static::supportedReportTypes()); $tenant = static::resolveTenantContextForCurrentPanel(); if ($tenant instanceof Tenant) { $query->where('workspace_id', (int) $tenant->workspace_id); } return static::resolveTenantOwnedRecordOrFail( $record, $query, ); } public static function form(Schema $schema): Schema { return $schema; } public static function infolist(Schema $schema): Schema { return $schema->schema([ Section::make('Outcome summary') ->schema([ ViewEntry::make('artifact_truth') ->hiddenLabel() ->view('filament.infolists.entries.governance-artifact-truth') ->state(fn (StoredReport $record): array => static::truthState($record)) ->columnSpanFull(), ]) ->columnSpanFull(), Section::make('Stored report') ->schema([ TextEntry::make('display_reference') ->label('Artifact reference') ->state(fn (StoredReport $record): string => static::displayReference($record)), TextEntry::make('report_type') ->label('Report family') ->formatStateUsing(fn (string $state): string => static::reportFamilyReportLabel($state)), TextEntry::make('measured_at') ->label('Measured at') ->state(fn (StoredReport $record): ?CarbonInterface => static::measuredAt($record)) ->dateTime() ->placeholder('—'), TextEntry::make('lifecycle_state') ->label('Lifecycle') ->state(fn (StoredReport $record): string => static::lifecycleState($record)) ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::GovernanceArtifactLifecycle)) ->color(BadgeRenderer::color(BadgeDomain::GovernanceArtifactLifecycle)) ->icon(BadgeRenderer::icon(BadgeDomain::GovernanceArtifactLifecycle)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::GovernanceArtifactLifecycle)), TextEntry::make('retention_state') ->label('Retention') ->state(fn (): string => 'retained') ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::GovernanceArtifactRetention)) ->color(BadgeRenderer::color(BadgeDomain::GovernanceArtifactRetention)) ->icon(BadgeRenderer::icon(BadgeDomain::GovernanceArtifactRetention)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::GovernanceArtifactRetention)), TextEntry::make('fingerprint') ->label('Integrity anchor') ->copyable() ->fontFamily('mono') ->placeholder('—') ->columnSpanFull(), TextEntry::make('previous_fingerprint') ->label('Previous fingerprint') ->copyable() ->fontFamily('mono') ->placeholder('—') ->columnSpanFull(), ]) ->columns(2) ->columnSpanFull(), Section::make('Permission posture summary') ->schema([ TextEntry::make('payload.posture_score')->label('Posture score')->placeholder('—'), TextEntry::make('payload.required_count')->label('Required permissions')->placeholder('0'), TextEntry::make('payload.granted_count')->label('Granted permissions')->placeholder('0'), TextEntry::make('missing_count') ->label('Missing permissions') ->state(fn (StoredReport $record): int => static::permissionPostureMissingCount($record)), TextEntry::make('at_risk_permissions') ->label('Missing or at-risk permission context') ->state(fn (StoredReport $record): array => static::permissionPostureAtRiskPermissions($record)) ->bulleted() ->listWithLineBreaks() ->placeholder('No missing or at-risk permissions in the stored payload.') ->columnSpanFull(), ]) ->columns(2) ->visible(fn (StoredReport $record): bool => $record->report_type === StoredReport::REPORT_TYPE_PERMISSION_POSTURE) ->columnSpanFull(), Section::make('Entra admin roles summary') ->schema([ TextEntry::make('payload.totals.roles_total')->label('Roles total')->placeholder('0'), TextEntry::make('payload.totals.assignments_total')->label('Assignments total')->placeholder('0'), TextEntry::make('payload.totals.high_privilege_assignments')->label('High-privilege assignments')->placeholder('0'), TextEntry::make('highest_risk_assignment') ->label('Highest-risk assignment') ->state(fn (StoredReport $record): ?string => static::highestRiskAssignmentLabel($record)) ->placeholder('No high-privilege assignments in the stored payload.') ->columnSpanFull(), ]) ->columns(2) ->visible(fn (StoredReport $record): bool => $record->report_type === StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES) ->columnSpanFull(), Section::make('Raw payload') ->schema([ ViewEntry::make('payload') ->hiddenLabel() ->view('filament.infolists.entries.snapshot-json') ->state(fn (StoredReport $record): array => is_array($record->payload) ? $record->payload : []) ->columnSpanFull(), ]) ->collapsible() ->collapsed() ->columnSpanFull(), ]); } public static function table(Table $table): Table { return $table ->defaultSort('created_at', 'desc') ->paginated(TablePaginationProfiles::resource()) ->recordUrl(fn (StoredReport $record): string => static::getUrl('view', ['record' => $record])) ->columns([ Tables\Columns\TextColumn::make('id') ->label('Reference') ->formatStateUsing(fn (int|string|null $state): string => sprintf('Stored report #%s', $state ?? '—')) ->searchable(query: fn (Builder $query, string $search): Builder => static::applyReportSearch($query, $search)), Tables\Columns\TextColumn::make('report_type') ->label('Report family') ->formatStateUsing(fn (string $state): string => static::reportFamilyReportLabel($state)) ->searchable(query: fn (Builder $query, string $search): Builder => static::applyReportSearch($query, $search)), Tables\Columns\TextColumn::make('lifecycle_state') ->label('Lifecycle') ->getStateUsing(fn (StoredReport $record): string => static::lifecycleState($record)) ->badge() ->formatStateUsing(BadgeRenderer::label(BadgeDomain::GovernanceArtifactLifecycle)) ->color(BadgeRenderer::color(BadgeDomain::GovernanceArtifactLifecycle)) ->icon(BadgeRenderer::icon(BadgeDomain::GovernanceArtifactLifecycle)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::GovernanceArtifactLifecycle)), Tables\Columns\TextColumn::make('measured_at') ->label('Measured at') ->getStateUsing(fn (StoredReport $record): ?CarbonInterface => static::measuredAt($record)) ->dateTime() ->placeholder('—'), Tables\Columns\TextColumn::make('summary') ->label('Summary') ->getStateUsing(fn (StoredReport $record): string => static::summaryText($record)) ->wrap(), Tables\Columns\TextColumn::make('fingerprint') ->label('Integrity') ->formatStateUsing(fn (?string $state): string => filled($state) ? 'Present' : '—') ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\SelectFilter::make('report_type') ->label('Report family') ->options(fn (): array => static::visibleReportFamilyOptions()), Tables\Filters\SelectFilter::make('history') ->label('Records') ->options([ 'current' => 'Current records', 'all' => 'Include history', ]) ->default('current') ->query(fn (Builder $query, array $data): Builder => ($data['value'] ?? 'current') === 'all' ? $query : static::scopeCurrentRecords($query)), ]) ->actions([]) ->bulkActions([]) ->emptyStateHeading('No stored reports yet') ->emptyStateDescription('Stored reports appear here after their origin surfaces create retained report records.') ->emptyStateIcon('heroicon-o-document-chart-bar') ->emptyStateActions([ Actions\Action::make('open_tenant_overview') ->label('Open tenant overview') ->icon('heroicon-o-home') ->url(fn (): string => static::tenantOverviewUrl()), ]); } public static function getPages(): array { return [ 'index' => Pages\ListStoredReports::route('/'), 'view' => Pages\ViewStoredReport::route('/{record}'), ]; } /** * @return array */ public static function supportedReportTypes(): array { return array_keys(self::REPORT_TYPE_CAPABILITIES); } /** * @return array */ public static function reportFamilyOptions(): array { return [ StoredReport::REPORT_TYPE_PERMISSION_POSTURE => 'Permission posture', StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES => 'Entra admin roles', ]; } /** * @return array */ public static function visibleReportFamilyOptions(): array { return array_intersect_key( static::reportFamilyOptions(), array_flip(static::visibleReportTypesForCurrentUser()), ); } /** * @return array */ public static function visibleReportTypesForCurrentUser(): array { $tenant = static::resolveTenantContextForCurrentPanel(); $user = auth()->user(); if (! $tenant instanceof Tenant || ! $user instanceof User || ! $user->canAccessTenant($tenant)) { return []; } return array_values(array_filter( static::supportedReportTypes(), static fn (string $reportType): bool => ($capability = static::capabilityForReportType($reportType)) !== null && $user->can($capability, $tenant), )); } public static function capabilityForReportType(string $reportType): ?string { return self::REPORT_TYPE_CAPABILITIES[$reportType] ?? null; } public static function reportFamilyLabel(string $reportType): string { return static::reportFamilyOptions()[$reportType] ?? Str::headline($reportType); } public static function reportFamilyReportLabel(string $reportType): string { return match ($reportType) { StoredReport::REPORT_TYPE_PERMISSION_POSTURE => 'Permission posture report', StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES => 'Entra admin roles report', default => Str::headline($reportType), }; } public static function displayReference(StoredReport $report): string { $truth = app(ArtifactTruthPresenter::class)->forStoredReportFresh($report); return $truth->displayReference ?? sprintf('Stored report #%d (%s)', (int) $report->getKey(), static::reportFamilyLabel((string) $report->report_type)); } public static function lifecycleState(StoredReport $report): string { $truth = app(ArtifactTruthPresenter::class)->forStoredReportFresh($report); return $truth->lifecycleState ?? 'current'; } /** * @return array */ public static function truthState(StoredReport $report): array { return app(ArtifactTruthPresenter::class)->forStoredReportFresh($report)->toArray(); } public static function measuredAt(StoredReport $report): ?CarbonInterface { $payload = is_array($report->payload) ? $report->payload : []; $value = Arr::get($payload, 'measured_at') ?? Arr::get($payload, 'checked_at'); if ($value instanceof CarbonInterface) { return $value; } if (is_string($value) && trim($value) !== '') { try { return Carbon::parse($value); } catch (Throwable) { // Fall back to persisted timestamps when payload timestamps are malformed. } } return $report->created_at ?? $report->updated_at; } public static function summaryText(StoredReport $report): string { $highlights = static::summaryHighlights($report); if ($highlights === []) { return 'No bounded summary is available for this report family.'; } return collect($highlights) ->map(static fn (array $highlight): string => sprintf('%s: %s', $highlight['label'], $highlight['value'])) ->implode(' · '); } /** * @return list */ public static function summaryHighlights(StoredReport $report): array { return match ((string) $report->report_type) { StoredReport::REPORT_TYPE_PERMISSION_POSTURE => static::permissionPostureHighlights($report), StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES => static::entraAdminRolesHighlights($report), default => [], }; } public static function permissionPostureMissingCount(StoredReport $report): int { $payload = is_array($report->payload) ? $report->payload : []; $requiredCount = max(0, (int) ($payload['required_count'] ?? 0)); $grantedCount = max(0, (int) ($payload['granted_count'] ?? 0)); return max(0, $requiredCount - $grantedCount); } /** * @return list */ public static function permissionPostureAtRiskPermissions(StoredReport $report): array { $payload = is_array($report->payload) ? $report->payload : []; $permissions = is_array($payload['permissions'] ?? null) ? $payload['permissions'] : []; return collect($permissions) ->filter(static fn (mixed $permission): bool => is_array($permission) && ($permission['status'] ?? null) !== 'granted') ->take(5) ->map(static function (array $permission): string { $key = trim((string) ($permission['key'] ?? 'Unknown permission')); $status = trim((string) ($permission['status'] ?? 'unknown')); return sprintf('%s (%s)', $key !== '' ? $key : 'Unknown permission', $status !== '' ? $status : 'unknown'); }) ->values() ->all(); } public static function highestRiskAssignmentLabel(StoredReport $report): ?string { $payload = is_array($report->payload) ? $report->payload : []; $assignments = is_array($payload['high_privilege'] ?? null) ? $payload['high_privilege'] : []; $assignment = collect($assignments) ->filter(static fn (mixed $assignment): bool => is_array($assignment)) ->sortBy(static fn (array $assignment): int => match ((string) ($assignment['severity'] ?? '')) { 'critical' => 0, 'high' => 1, 'medium' => 2, 'low' => 3, default => 4, }) ->first(); if (! is_array($assignment)) { return null; } $role = trim((string) ($assignment['role_display_name'] ?? 'Unknown role')); $principal = trim((string) ($assignment['principal_display_name'] ?? 'Unknown principal')); $severity = trim((string) ($assignment['severity'] ?? 'unknown')); return sprintf('%s assigned to %s (%s)', $role !== '' ? $role : 'Unknown role', $principal !== '' ? $principal : 'Unknown principal', $severity !== '' ? $severity : 'unknown'); } public static function currentReportFor(StoredReport $report): ?StoredReport { $current = StoredReport::query() ->where('tenant_id', (int) $report->tenant_id) ->where('workspace_id', (int) $report->workspace_id) ->where('report_type', (string) $report->report_type) ->orderByDesc('created_at') ->orderByDesc('id') ->first(); return $current instanceof StoredReport && ! $current->is($report) ? $current : null; } public static function currentReportUrlFor(StoredReport $report): ?string { $current = static::currentReportFor($report); if (! $current instanceof StoredReport) { return null; } $tenant = $current->tenant ?? static::resolveTenantContextForCurrentPanel(); if (! $tenant instanceof Tenant) { return null; } return static::getUrl('view', ['record' => $current], panel: 'tenant', tenant: $tenant); } public static function scopeCurrentRecords(Builder $query): Builder { return $query->whereNotExists(function ($subQuery): void { $subQuery ->selectRaw('1') ->from('stored_reports as newer_stored_reports') ->whereColumn('newer_stored_reports.tenant_id', 'stored_reports.tenant_id') ->whereColumn('newer_stored_reports.workspace_id', 'stored_reports.workspace_id') ->whereColumn('newer_stored_reports.report_type', 'stored_reports.report_type') ->where(function ($newerQuery): void { $newerQuery ->whereColumn('newer_stored_reports.created_at', '>', 'stored_reports.created_at') ->orWhere(function ($tieQuery): void { $tieQuery ->whereColumn('newer_stored_reports.created_at', 'stored_reports.created_at') ->whereColumn('newer_stored_reports.id', '>', 'stored_reports.id'); }); }); }); } public static function applyReportSearch(Builder $query, string $search): Builder { $search = trim($search); if ($search === '') { return $query; } $normalizedSearch = Str::lower($search); $matchingReportTypes = collect(static::reportFamilyOptions()) ->filter(static fn (string $label, string $reportType): bool => str_contains(Str::lower($label), $normalizedSearch) || str_contains(Str::lower(static::reportFamilyReportLabel($reportType)), $normalizedSearch) || str_contains(Str::lower($reportType), $normalizedSearch)) ->keys() ->all(); return $query->where(function (Builder $searchQuery) use ($search, $matchingReportTypes): void { $searchQuery->where('report_type', 'like', '%'.$search.'%'); if (is_numeric($search)) { $searchQuery->orWhereKey((int) $search); } if ($matchingReportTypes !== []) { $searchQuery->orWhereIn('report_type', $matchingReportTypes); } }); } /** * @return list */ private static function permissionPostureHighlights(StoredReport $report): array { $payload = is_array($report->payload) ? $report->payload : []; $postureScore = $payload['posture_score'] ?? null; $requiredCount = (int) ($payload['required_count'] ?? 0); $grantedCount = (int) ($payload['granted_count'] ?? 0); return [ ['label' => 'Posture score', 'value' => is_numeric($postureScore) ? (string) ((int) $postureScore) : '—'], ['label' => 'Required', 'value' => (string) $requiredCount], ['label' => 'Granted', 'value' => (string) $grantedCount], ['label' => 'Missing', 'value' => (string) static::permissionPostureMissingCount($report)], ]; } /** * @return list */ private static function entraAdminRolesHighlights(StoredReport $report): array { $payload = is_array($report->payload) ? $report->payload : []; $totals = is_array($payload['totals'] ?? null) ? $payload['totals'] : []; return [ ['label' => 'Roles', 'value' => (string) ((int) ($totals['roles_total'] ?? 0))], ['label' => 'Assignments', 'value' => (string) ((int) ($totals['assignments_total'] ?? 0))], ['label' => 'High privilege', 'value' => (string) ((int) ($totals['high_privilege_assignments'] ?? 0))], ['label' => 'Highest risk', 'value' => static::highestRiskAssignmentLabel($report) ?? '—'], ]; } private static function tenantOverviewUrl(): string { $tenant = static::resolveTenantContextForCurrentPanel(); if (! $tenant instanceof Tenant) { return '#'; } return TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant); } }