create(['status' => 'active']); [$user, $tenantOverdue] = createUserWithTenant($tenantOverdue, role: 'owner', workspaceRole: 'readonly'); [$overdueProfile, $overdueSnapshot] = seedActiveBaselineForTenant($tenantOverdue); seedBaselineCompareRun($tenantOverdue, $overdueProfile, $overdueSnapshot, workspaceOverviewCompareCoverage()); Finding::factory()->count(3)->for($tenantOverdue)->create([ 'workspace_id' => (int) $tenantOverdue->workspace_id, 'status' => Finding::STATUS_TRIAGED, 'due_at' => now()->subDay(), ]); $tenantExpiring = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantOverdue->workspace_id, 'name' => 'Expiring Tenant', ]); createUserWithTenant($tenantExpiring, $user, role: 'owner', workspaceRole: 'readonly'); [$expiringProfile, $expiringSnapshot] = seedActiveBaselineForTenant($tenantExpiring); seedBaselineCompareRun($tenantExpiring, $expiringProfile, $expiringSnapshot, workspaceOverviewCompareCoverage()); $finding = Finding::factory()->riskAccepted()->create([ 'workspace_id' => (int) $tenantExpiring->workspace_id, 'tenant_id' => (int) $tenantExpiring->getKey(), ]); FindingException::query()->create([ 'workspace_id' => (int) $tenantExpiring->workspace_id, 'tenant_id' => (int) $tenantExpiring->getKey(), 'finding_id' => (int) $finding->getKey(), 'requested_by_user_id' => (int) $user->getKey(), 'owner_user_id' => (int) $user->getKey(), 'approved_by_user_id' => (int) $user->getKey(), 'status' => FindingException::STATUS_EXPIRING, 'current_validity_state' => FindingException::VALIDITY_EXPIRING, 'request_reason' => 'Pending governance review', 'approval_reason' => 'Short lived exception', 'requested_at' => now()->subDays(2), 'approved_at' => now()->subDay(), 'effective_from' => now()->subDay(), 'expires_at' => now()->addDay(), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 0], ]); $tenantStale = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantOverdue->workspace_id, 'name' => 'Stale Tenant', ]); createUserWithTenant($tenantStale, $user, role: 'owner', workspaceRole: 'readonly'); [$staleProfile, $staleSnapshot] = seedActiveBaselineForTenant($tenantStale); seedBaselineCompareRun( $tenantStale, $staleProfile, $staleSnapshot, workspaceOverviewCompareCoverage(), completedAt: now()->subDays(10), ); $tenantFailedCompare = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $tenantOverdue->workspace_id, 'name' => 'Failed Compare Tenant', ]); createUserWithTenant($tenantFailedCompare, $user, role: 'owner', workspaceRole: 'readonly'); [$failedProfile, $failedSnapshot] = seedActiveBaselineForTenant($tenantFailedCompare); seedBaselineCompareRun( $tenantFailedCompare, $failedProfile, $failedSnapshot, workspaceOverviewCompareCoverage(), outcome: \App\Support\OperationRunOutcome::Failed->value, ); $workspace = $tenantOverdue->workspace()->firstOrFail(); $overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user); $metrics = collect($overview['summary_metrics'])->keyBy('key'); expect($metrics->get('governance_attention_tenants')['value'])->toBe(4) ->and($metrics->get('governance_attention_tenants')['category'])->toBe('governance_risk') ->and($metrics->get('governance_attention_tenants')['destination']['kind'])->toBe('choose_tenant'); }); it('keeps activity and alerts metrics separate from governance risk', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'readonly'); [$profile, $snapshot] = seedActiveBaselineForTenant($tenant); seedBaselineCompareRun($tenant, $profile, $snapshot, workspaceOverviewCompareCoverage()); OperationRun::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => \App\Support\OperationRunStatus::Running->value, 'outcome' => \App\Support\OperationRunOutcome::Pending->value, ]); AlertDelivery::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => AlertDelivery::STATUS_FAILED, 'created_at' => now(), ]); $workspace = $tenant->workspace()->firstOrFail(); $overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user); $metrics = collect($overview['summary_metrics'])->keyBy('key'); expect($metrics->get('governance_attention_tenants')['value'])->toBe(0) ->and($metrics->get('active_operations')['value'])->toBe(1) ->and($metrics->get('active_operations')['category'])->toBe('activity') ->and($metrics->get('active_operations')['destination']['kind'])->toBe('operations_index') ->and($metrics->get('alert_failures')['value'])->toBe(1) ->and($metrics->get('alert_failures')['category'])->toBe('alerts'); }); it('counts backup and recovery attention tenants separately and chooses precise or fallback destinations', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC')); $singleRecoveryTenant = Tenant::factory()->create([ 'status' => 'active', 'name' => 'Single Recovery Tenant', ]); [$user, $singleRecoveryTenant] = createUserWithTenant($singleRecoveryTenant, role: 'owner', workspaceRole: 'readonly'); workspaceOverviewSeedQuietTenantTruth($singleRecoveryTenant); $singleRecoveryBackup = workspaceOverviewSeedHealthyBackup($singleRecoveryTenant, [ 'completed_at' => now()->subMinutes(20), ]); workspaceOverviewSeedRestoreHistory($singleRecoveryTenant, $singleRecoveryBackup, 'follow_up'); $backupTenantA = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $singleRecoveryTenant->workspace_id, 'name' => 'Backup Tenant A', ]); createUserWithTenant($backupTenantA, $user, role: 'owner', workspaceRole: 'readonly'); workspaceOverviewSeedQuietTenantTruth($backupTenantA); $backupTenantB = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $singleRecoveryTenant->workspace_id, 'name' => 'Backup Tenant B', ]); createUserWithTenant($backupTenantB, $user, role: 'owner', workspaceRole: 'readonly'); workspaceOverviewSeedQuietTenantTruth($backupTenantB); workspaceOverviewSeedHealthyBackup($backupTenantB, [ 'completed_at' => now()->subDays(2), ]); $calmTenant = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $singleRecoveryTenant->workspace_id, 'name' => 'Calm Tenant', ]); createUserWithTenant($calmTenant, $user, role: 'owner', workspaceRole: 'readonly'); workspaceOverviewSeedQuietTenantTruth($calmTenant); $calmBackup = workspaceOverviewSeedHealthyBackup($calmTenant, [ 'completed_at' => now()->subMinutes(15), ]); workspaceOverviewSeedRestoreHistory($calmTenant, $calmBackup, 'completed'); $workspace = $singleRecoveryTenant->workspace()->firstOrFail(); $overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user); $metrics = collect($overview['summary_metrics'])->keyBy('key'); expect($metrics->get('backup_attention_tenants')['value'])->toBe(2) ->and($metrics->get('backup_attention_tenants')['category'])->toBe('backup_health') ->and($metrics->get('backup_attention_tenants')['destination']['kind'])->toBe('choose_tenant') ->and($metrics->get('recovery_attention_tenants')['value'])->toBe(1) ->and($metrics->get('recovery_attention_tenants')['category'])->toBe('recovery_evidence') ->and($metrics->get('recovery_attention_tenants')['destination']['kind'])->toBe('tenant_dashboard') ->and($metrics->get('recovery_attention_tenants')['destination']['tenant_route_key'])->toBe((string) $singleRecoveryTenant->external_id) ->and($metrics->get('recovery_attention_tenants')['destination_url'])->toContain('/admin/t/'); }); it('keeps backup and recovery attention counts at zero for calm visible tenants', function (): void { CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 9, 9, 0, 0, 'UTC')); $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'readonly'); workspaceOverviewSeedQuietTenantTruth($tenant); $backupSet = workspaceOverviewSeedHealthyBackup($tenant, [ 'completed_at' => now()->subMinutes(10), ]); workspaceOverviewSeedRestoreHistory($tenant, $backupSet, 'completed'); $workspace = $tenant->workspace()->firstOrFail(); $overview = app(WorkspaceOverviewBuilder::class)->build($workspace, $user); $metrics = collect($overview['summary_metrics'])->keyBy('key'); expect($metrics->get('backup_attention_tenants')['value'])->toBe(0) ->and($metrics->get('recovery_attention_tenants')['value'])->toBe(0) ->and($overview['calmness']['checked_domains'])->toContain('backup_health', 'recovery_evidence'); });