TenantAtlas/apps/platform/app/Support/Tenants/TenantRecoveryTriagePresentation.php
ahmido 9fbd3e5ec7 Spec 186: implement tenant registry recovery triage (#217)
## Summary
- turn the tenant registry into a workspace-scoped recovery triage surface with backup posture and recovery evidence columns
- preserve workspace overview backup and recovery drilldown intent by routing multi-tenant cases into filtered tenant registry slices
- add the Spec 186 planning artifacts, focused regression coverage, and shared triage presentation helpers

## Testing
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantRegistryRecoveryTriageTest.php tests/Feature/Filament/WorkspaceOverviewSummaryMetricsTest.php tests/Feature/Filament/WorkspaceOverviewDrilldownContinuityTest.php tests/Feature/Filament/TenantResourceIndexIsWorkspaceScopedTest.php tests/Feature/Filament/WorkspaceOverviewAuthorizationTest.php tests/Feature/Guards/ActionSurfaceContractTest.php tests/Feature/Guards/FilamentTableStandardsGuardTest.php`

## Notes
- no schema change
- no new persisted recovery truth
- branch includes the full Spec 186 spec, plan, research, data model, contract, quickstart, and tasks artifacts

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #217
2026-04-09 19:20:48 +00:00

184 lines
5.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Tenants;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\RestoreSafety\RestoreResultAttention;
use Illuminate\Support\Str;
final class TenantRecoveryTriagePresentation
{
public const RECOVERY_EVIDENCE_WEAKENED = 'weakened';
public const RECOVERY_EVIDENCE_UNVALIDATED = 'unvalidated';
public const RECOVERY_EVIDENCE_NO_RECENT_ISSUES_VISIBLE = 'no_recent_issues_visible';
public const TRIAGE_SORT_DEFAULT = 'default';
public const TRIAGE_SORT_WORST_FIRST = 'worst_first';
/**
* @return array<string, string>
*/
public static function backupPostureOptions(): array
{
return [
TenantBackupHealthAssessment::POSTURE_ABSENT => 'Absent',
TenantBackupHealthAssessment::POSTURE_STALE => 'Stale',
TenantBackupHealthAssessment::POSTURE_DEGRADED => 'Degraded',
TenantBackupHealthAssessment::POSTURE_HEALTHY => 'Healthy',
];
}
/**
* @return array<string, string>
*/
public static function recoveryEvidenceOptions(): array
{
return [
self::RECOVERY_EVIDENCE_WEAKENED => 'Weakened',
self::RECOVERY_EVIDENCE_UNVALIDATED => 'Unvalidated',
self::RECOVERY_EVIDENCE_NO_RECENT_ISSUES_VISIBLE => 'No recent issues visible',
];
}
/**
* @return array<string, string>
*/
public static function triageSortOptions(): array
{
return [
self::TRIAGE_SORT_DEFAULT => 'Default order',
self::TRIAGE_SORT_WORST_FIRST => 'Worst first',
];
}
public static function backupPostureState(?TenantBackupHealthAssessment $assessment): ?string
{
return $assessment?->posture;
}
public static function backupPostureLabel(?TenantBackupHealthAssessment $assessment): string
{
$state = self::backupPostureState($assessment);
if ($state === null) {
return 'Unknown';
}
return self::backupPostureOptions()[$state] ?? Str::headline($state);
}
public static function backupPostureTone(?TenantBackupHealthAssessment $assessment): string
{
return $assessment?->tone() ?? 'gray';
}
public static function backupPostureDescription(?TenantBackupHealthAssessment $assessment, ?string $helperText = null): ?string
{
if (! $assessment instanceof TenantBackupHealthAssessment) {
return $helperText;
}
$parts = [
$assessment->supportingMessage ?? $assessment->headline,
$assessment->posture === TenantBackupHealthAssessment::POSTURE_HEALTHY
? $assessment->positiveClaimBoundary
: null,
$helperText,
];
$description = trim(implode(' ', array_filter($parts, static fn (?string $part): bool => filled($part))));
return $description !== '' ? $description : null;
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public static function recoveryEvidenceState(?array $recoveryEvidence): ?string
{
$state = $recoveryEvidence['overview_state'] ?? null;
return is_string($state) && $state !== '' ? $state : null;
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public static function recoveryEvidenceLabel(?array $recoveryEvidence): string
{
$state = self::recoveryEvidenceState($recoveryEvidence);
if ($state === null) {
return 'Unknown';
}
return self::recoveryEvidenceOptions()[$state] ?? Str::headline($state);
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public static function recoveryEvidenceDescription(?array $recoveryEvidence, ?string $helperText = null): ?string
{
if (! is_array($recoveryEvidence)) {
return $helperText;
}
$parts = [
is_string($recoveryEvidence['summary'] ?? null) ? $recoveryEvidence['summary'] : null,
is_string($recoveryEvidence['claim_boundary'] ?? null) ? $recoveryEvidence['claim_boundary'] : null,
$helperText,
];
$description = trim(implode(' ', array_filter($parts, static fn (?string $part): bool => filled($part))));
return $description !== '' ? $description : null;
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public static function recoveryEvidenceTone(?array $recoveryEvidence): string
{
if (! is_array($recoveryEvidence)) {
return 'gray';
}
$attentionState = is_string($recoveryEvidence['latest_relevant_attention_state'] ?? null)
? $recoveryEvidence['latest_relevant_attention_state']
: null;
return match (self::recoveryEvidenceState($recoveryEvidence)) {
self::RECOVERY_EVIDENCE_UNVALIDATED => 'warning',
self::RECOVERY_EVIDENCE_WEAKENED => $attentionState === RestoreResultAttention::STATE_FAILED ? 'danger' : 'warning',
self::RECOVERY_EVIDENCE_NO_RECENT_ISSUES_VISIBLE => 'success',
default => 'gray',
};
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public static function triageTier(
?TenantBackupHealthAssessment $backupHealth,
?array $recoveryEvidence,
): int {
$backupPosture = self::backupPostureState($backupHealth);
$recoveryState = self::recoveryEvidenceState($recoveryEvidence);
return match (true) {
$backupPosture === TenantBackupHealthAssessment::POSTURE_ABSENT => 1,
$recoveryState === self::RECOVERY_EVIDENCE_WEAKENED => 2,
$backupPosture === TenantBackupHealthAssessment::POSTURE_STALE => 3,
$recoveryState === self::RECOVERY_EVIDENCE_UNVALIDATED => 4,
$backupPosture === TenantBackupHealthAssessment::POSTURE_DEGRADED => 5,
default => 6,
};
}
}