feat(ui): implement core operator view surfaces productization for backup sets
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m23s

Applied the decision-first global surface IA contract to BackupSet views. Includes decision summary header, usability status, and separation of technical metadata.
This commit is contained in:
Ahmed Darrazi 2026-06-11 09:37:26 +02:00
parent c36cb43741
commit 4f3a28f3e5
28 changed files with 1715 additions and 121 deletions

View File

@ -22,6 +22,7 @@
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\BackupHealth\TenantBackupHealthResolver;
use App\Support\BackupQuality\BackupQualityResolver;
use App\Support\BackupQuality\BackupQualitySummary;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\TablePaginationProfiles;
@ -67,6 +68,8 @@ class BackupSetResource extends Resource
protected static ?string $model = BackupSet::class;
protected static bool $isGloballySearchable = false;
protected static ?string $tenantOwnershipRelationshipName = 'tenant';
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-archive-box';
@ -176,20 +179,22 @@ public static function table(Table $table): Table
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('backup_quality')
->label('Restore-point decision')
->state(fn (BackupSet $record): string => static::backupSetListDecision($record))
->description(fn (BackupSet $record): string => static::backupSetListGuidance($record))
->wrap(),
Tables\Columns\TextColumn::make('item_count')->label('Items captured')->numeric()->sortable(),
Tables\Columns\TextColumn::make('status')
->label('Lifecycle')
->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::BackupSetStatus))
->color(BadgeRenderer::color(BadgeDomain::BackupSetStatus))
->icon(BadgeRenderer::icon(BadgeDomain::BackupSetStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::BackupSetStatus)),
Tables\Columns\TextColumn::make('item_count')->label('Items')->numeric()->sortable(),
Tables\Columns\TextColumn::make('backup_quality')
->label('Backup quality')
->state(fn (BackupSet $record): string => static::backupQualitySummary($record)->compactSummary)
->description(fn (BackupSet $record): string => static::backupQualitySummary($record)->nextAction)
->wrap(),
Tables\Columns\TextColumn::make('created_by')->label('Created by')->toggleable(isToggledHiddenByDefault: true),
->iconColor(BadgeRenderer::iconColor(BadgeDomain::BackupSetStatus))
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('completed_at')->label('Completed')->dateTime()->since()->sortable(),
Tables\Columns\TextColumn::make('created_by')->label('Created by')->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('created_at')->label('Captured')->dateTime()->since()->sortable()->toggleable(isToggledHiddenByDefault: true),
])
->filters([
@ -556,7 +561,7 @@ public static function table(Table $table): Table
])->label('More'),
])
->emptyStateHeading('No backup sets')
->emptyStateDescription('Create a backup set to start protecting your configurations.')
->emptyStateDescription('Create a backup set to capture policy versions and assignments for later restore review.')
->emptyStateIcon('heroicon-o-archive-box')
->emptyStateActions([
static::makeCreateAction(),
@ -677,11 +682,6 @@ private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetai
$isArchived = $record->trashed();
$qualitySummary = static::backupQualitySummary($record);
$backupHealthAssessment = static::backupHealthContinuityAssessment($record);
$qualityBadge = match (true) {
$qualitySummary->totalItems === 0 => $factory->statusBadge('No items', 'gray'),
$qualitySummary->hasDegradations() => $factory->statusBadge('Degraded input', 'warning', 'heroicon-m-exclamation-triangle'),
default => $factory->statusBadge('No degradations', 'success', 'heroicon-m-check-circle'),
};
$backupHealthBadge = $backupHealthAssessment instanceof TenantBackupHealthAssessment
? $factory->statusBadge(
static::backupHealthContinuityLabel($backupHealthAssessment),
@ -691,105 +691,57 @@ private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetai
: null;
$descriptionHint = $backupHealthAssessment instanceof TenantBackupHealthAssessment
? trim($backupHealthAssessment->headline.' '.($backupHealthAssessment->supportingMessage ?? ''))
: 'Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.';
: null;
return EnterpriseDetailBuilder::make('backup_set', 'tenant')
->header(new SummaryHeaderData(
title: (string) $record->name,
subtitle: 'Backup set #'.$record->getKey(),
statusBadges: [
statusBadges: array_values(array_filter([
$backupHealthBadge,
$factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor),
$factory->statusBadge($isArchived ? 'Archived' : 'Active', $isArchived ? 'warning' : 'success'),
...array_filter([$backupHealthBadge]),
$qualityBadge,
],
keyFacts: [
$factory->keyFact('Items', $record->item_count),
...array_filter([
$backupHealthAssessment instanceof TenantBackupHealthAssessment
? $factory->keyFact('Backup posture', static::backupHealthContinuityLabel($backupHealthAssessment), badge: $backupHealthBadge)
: null,
]),
$factory->keyFact('Backup quality', $qualitySummary->compactSummary),
$factory->keyFact('Created by', $record->created_by),
$factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)),
$factory->keyFact('Captured', static::formatDetailTimestamp($record->created_at)),
],
$isArchived
? $factory->statusBadge('Archived', 'warning')
: null,
])),
descriptionHint: $descriptionHint,
))
->decisionZone($factory->decisionZone(
facts: array_values(array_filter([
$backupHealthAssessment instanceof TenantBackupHealthAssessment
? $factory->keyFact('Backup posture', static::backupHealthContinuityLabel($backupHealthAssessment), badge: $backupHealthBadge)
: null,
$factory->keyFact('Backup quality', $qualitySummary->compactSummary, badge: $qualityBadge),
$factory->keyFact('Degraded items', $qualitySummary->degradedItemCount),
$factory->keyFact('Metadata only', $qualitySummary->metadataOnlyCount),
$factory->keyFact('Assignment issues', $qualitySummary->assignmentIssueCount),
$factory->keyFact('Orphaned assignments', $qualitySummary->orphanedAssignmentCount),
$factory->keyFact('Integrity warnings', $qualitySummary->integrityWarningCount),
$qualitySummary->unknownQualityCount > 0
? $factory->keyFact('Unknown quality', $qualitySummary->unknownQualityCount)
: null,
])),
facts: static::backupDecisionFacts($factory, $qualitySummary, $backupHealthAssessment, $backupHealthBadge),
primaryNextStep: $factory->primaryNextStep(
$qualitySummary->nextAction,
'Backup quality',
static::backupDecisionNextAction($qualitySummary),
'Restore-point decision',
),
description: 'Start here to judge whether this backup set looks strong or weak as restore input before reading diagnostics or raw metadata.',
compactCounts: $factory->countPresentation(summaryLine: $qualitySummary->summaryMessage),
description: 'Start here to decide whether this backup set is usable for restore review before reading lifecycle, operation, or raw metadata.',
compactCounts: static::backupDecisionCounts($factory, $qualitySummary),
attentionNote: $backupHealthAssessment?->positiveClaimBoundary ?? $qualitySummary->positiveClaimBoundary,
title: 'Backup quality',
title: 'Restore-point decision',
))
->addSection(
$factory->factsSection(
id: 'lifecycle_overview',
kind: 'core_details',
title: 'Lifecycle overview',
items: [
$factory->keyFact('Status', $statusSpec->label, badge: $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor)),
$factory->keyFact('Items', $record->item_count),
$factory->keyFact('Created by', $record->created_by),
$factory->keyFact('Archived', $isArchived),
],
),
$factory->viewSection(
id: 'related_context',
kind: 'related_context',
title: 'Related context',
title: 'Related operation and evidence',
view: 'filament.infolists.entries.related-context',
viewData: ['entries' => $relatedContext],
description: 'Operation links stay available for traceability, but they are secondary to backup usability and included-item truth.',
emptyState: $factory->emptyState('No related context is available for this record.'),
),
)
->addSupportingCard(
$factory->supportingFactsCard(
kind: 'status',
title: 'Backup quality counts',
items: [
$factory->keyFact('Degraded items', $qualitySummary->degradedItemCount),
$factory->keyFact('Metadata only', $qualitySummary->metadataOnlyCount),
$factory->keyFact('Assignment issues', $qualitySummary->assignmentIssueCount),
$factory->keyFact('Orphaned assignments', $qualitySummary->orphanedAssignmentCount),
],
),
$factory->supportingFactsCard(
kind: 'timestamps',
title: 'Timing',
items: [
$factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)),
$factory->keyFact('Captured', static::formatDetailTimestamp($record->created_at)),
],
collapsible: true,
collapsed: true,
),
)
->addTechnicalSection(
$factory->technicalDetail(
title: 'Technical detail',
title: 'Technical and lifecycle detail',
entries: [
$factory->keyFact('Lifecycle status', $statusSpec->label, badge: $factory->statusBadge($statusSpec->label, $statusSpec->color, $statusSpec->icon, $statusSpec->iconColor)),
$factory->keyFact('Record state', $isArchived ? 'Archived' : 'Active'),
$factory->keyFact('Created by', $record->created_by),
$factory->keyFact('Completed', static::formatDetailTimestamp($record->completed_at)),
$factory->keyFact('Captured', static::formatDetailTimestamp($record->created_at)),
$factory->keyFact('Metadata keys', $metadataKeyCount),
$factory->keyFact('Archived', $isArchived),
],
description: 'Low-signal backup metadata stays available here without taking over the recovery workflow.',
description: 'Lifecycle, timing, IDs, and raw metadata stay available here without taking over the restore-point decision.',
view: $metadata !== [] ? 'filament.infolists.entries.snapshot-json' : null,
viewData: ['payload' => $metadata],
emptyState: $factory->emptyState('No backup metadata was recorded for this backup set.'),
@ -798,6 +750,170 @@ private static function enterpriseDetailPage(BackupSet $record): EnterpriseDetai
->build();
}
private static function backupSetListDecision(BackupSet $record): string
{
$summary = static::backupQualitySummary($record);
if ($summary->totalItems === 0) {
return 'Blocked: no items captured';
}
if ($summary->hasDegradations()) {
return 'Action needed before restore review';
}
return 'Usable for restore review';
}
private static function backupSetListGuidance(BackupSet $record): string
{
$summary = static::backupQualitySummary($record);
if ($summary->totalItems === 0) {
return 'No backup items were captured. Create or refresh a backup set before restore review.';
}
if ($summary->hasDegradations()) {
return $summary->compactSummary.'. Inspect degraded items before relying on this backup.';
}
return sprintf(
'%s captured. Review included items before relying on this backup.',
static::formatItemCount($summary->totalItems),
);
}
private static function backupUsabilityLabel(BackupQualitySummary $summary): string
{
if ($summary->totalItems === 0) {
return 'Blocked until items are captured';
}
if ($summary->hasDegradations()) {
return 'Action needed before restore review';
}
return 'Usable for restore review';
}
/**
* @return list<array<string, mixed>>
*/
private static function backupDecisionFacts(
EnterpriseDetailSectionFactory $factory,
BackupQualitySummary $summary,
?TenantBackupHealthAssessment $assessment,
?array $backupHealthBadge,
): array {
return array_values(array_filter([
$factory->keyFact('Usability', static::backupUsabilityLabel($summary)),
$factory->keyFact('Reason', static::backupDecisionReason($summary)),
$factory->keyFact('Impact', static::backupDecisionImpact($summary)),
$factory->keyFact('Items captured', static::formatItemCount($summary->totalItems)),
$summary->totalItems === 0
? $factory->keyFact('Current blocker', 'No backup items were captured.')
: null,
$summary->hasDegradations()
? $factory->keyFact('Current degradation', $summary->compactSummary)
: null,
$assessment instanceof TenantBackupHealthAssessment
? $factory->keyFact('Backup posture', static::backupHealthContinuityLabel($assessment), badge: $backupHealthBadge)
: null,
]));
}
private static function backupDecisionReason(BackupQualitySummary $summary): string
{
if ($summary->totalItems === 0) {
return 'No backup item inventory is available on this restore point.';
}
if ($summary->hasDegradations()) {
return $summary->summaryMessage;
}
return 'Captured item inventory is available for operator review.';
}
private static function backupDecisionImpact(BackupQualitySummary $summary): string
{
if ($summary->totalItems === 0) {
return 'Restore review should wait for a backup set with captured items.';
}
if ($summary->hasDegradations()) {
return 'Treat this backup as investigation input until degraded items are reviewed.';
}
return 'Use this as restore review input only after confirming included items below.';
}
private static function backupDecisionNextAction(BackupQualitySummary $summary): string
{
if ($summary->totalItems === 0) {
return 'Create or refresh a backup set before starting restore review.';
}
if ($summary->hasDegradations()) {
return 'Review degraded included items and source operation before continuing into restore.';
}
return 'Review included items below before starting any separate restore workflow.';
}
/**
* @return array{
* summaryLine?: ?string,
* primaryFacts: list<array<string, mixed>>,
* diagnosticFacts: list<array<string, mixed>>
* }|null
*/
private static function backupDecisionCounts(EnterpriseDetailSectionFactory $factory, BackupQualitySummary $summary): ?array
{
$facts = static::nonZeroQualityFacts($factory, $summary);
if ($facts === [] && $summary->totalItems > 0 && ! $summary->hasDegradations()) {
return null;
}
return $factory->countPresentation(
summaryLine: $summary->summaryMessage,
primaryFacts: $facts,
);
}
/**
* @return list<array<string, mixed>>
*/
private static function nonZeroQualityFacts(EnterpriseDetailSectionFactory $factory, BackupQualitySummary $summary): array
{
return array_values(array_filter([
$summary->degradedItemCount > 0
? $factory->keyFact('Degraded items', $summary->degradedItemCount)
: null,
$summary->metadataOnlyCount > 0
? $factory->keyFact('Metadata-only items', $summary->metadataOnlyCount)
: null,
$summary->assignmentIssueCount > 0
? $factory->keyFact('Assignment issues', $summary->assignmentIssueCount)
: null,
$summary->orphanedAssignmentCount > 0
? $factory->keyFact('Orphaned assignments', $summary->orphanedAssignmentCount)
: null,
$summary->integrityWarningCount > 0
? $factory->keyFact('Integrity warnings', $summary->integrityWarningCount)
: null,
$summary->unknownQualityCount > 0
? $factory->keyFact('Unknown quality', $summary->unknownQualityCount)
: null,
]));
}
private static function formatItemCount(int $count): string
{
return sprintf('%d item%s', $count, $count === 1 ? '' : 's');
}
private static function formatDetailTimestamp(mixed $value): string
{
if (! $value instanceof Carbon) {

View File

@ -40,6 +40,8 @@ class BackupItemsRelationManager extends RelationManager
{
protected static string $relationship = 'items';
protected static ?string $title = 'Included backup items';
protected $listeners = [
'backup-set-policy-picker:close' => 'closeAddPoliciesModal',
];
@ -104,7 +106,7 @@ public function table(Table $table): Table
]);
});
UiEnforcement::forAction($addPolicies)
UiEnforcement::forAction($addPolicies, fn (): BackupSet => $this->getOwnerRecord())
->requireCapability(Capabilities::TENANT_SYNC)
->tooltip('You do not have permission to add policies.')
->apply();

View File

@ -76,7 +76,7 @@ function spec334SmokeLoginUrl(User $user, ManagedEnvironment $tenant, string $re
->waitForText('Spec334 Browser Backup Set')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->assertSee('Related context');
->assertSee('Related operation and evidence');
$page->script('window.scrollTo(0, document.body.scrollHeight);');

View File

@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
use App\Filament\Resources\BackupSetResource;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\User;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use Illuminate\Foundation\Testing\RefreshDatabase;
pest()->browser()->timeout(60_000);
uses(RefreshDatabase::class);
it('Spec371 smokes backup set list and detail decision hierarchy with screenshots', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$healthyBackupSet = spec371BrowserHealthyBackupSet($tenant, [
'name' => 'Spec 371 Browser Healthy Backup',
]);
BackupSet::factory()
->for($tenant)
->degradedCompleted()
->create([
'name' => 'Spec 371 Browser Degraded Backup',
]);
spec371BrowserSourceOperation($tenant, $user, $healthyBackupSet);
$listPath = spec371BrowserListPath($tenant);
$detailPath = spec371BrowserViewPath($tenant, $healthyBackupSet);
$page = visit(spec371BrowserLoginUrl($user, $tenant, $listPath))
->resize(1440, 1100)
->waitForText('Backup Sets')
->waitForText('Restore-point decision')
->assertSee('Spec 371 Browser Healthy Backup')
->assertSee('Usable for restore review')
->assertSee('Spec 371 Browser Degraded Backup')
->assertSee('Action needed before restore review')
->assertSee('Items captured')
->assertDontSee('No degradations detected across')
->assertScript("document.querySelector('a[href$=\"{$detailPath}\"]') !== null", true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec371BrowserScreenshotName('01-backup-sets-list'));
spec371BrowserCopyScreenshot('01-backup-sets-list');
$page = visit($detailPath)
->resize(1440, 1100)
->waitForText('Restore-point decision')
->assertSee('Spec 371 Browser Healthy Backup')
->assertSee('Usable for restore review')
->assertSee('Captured item inventory is available for operator review.')
->assertSee('Review included items below before starting any separate restore workflow.')
->assertSee('Included backup items')
->assertSee('Related operation and evidence')
->assertSee('Open operation')
->assertSee('Technical and lifecycle detail')
->assertDontSee('Item review state')
->assertDontSee('Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.')
->assertDontSee('This backup set has captured items ready for operator review before any separate restore workflow starts.')
->assertDontSee('Usable for review')
->assertScript("(() => { const firstSection = document.querySelector('section.fi-section'); return firstSection !== null && ! firstSection.textContent.includes('Usable for review') && ! firstSection.textContent.includes('Usability') && ! firstSection.textContent.includes('Active'); })()", true)
->assertScript("document.body.textContent.indexOf('Restore-point decision') < document.body.textContent.indexOf('Technical and lifecycle detail')", true)
->assertScript("document.body.textContent.indexOf('Technical and lifecycle detail') < document.body.textContent.indexOf('Included backup items')", true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec371BrowserScreenshotName('02-backup-set-detail'));
spec371BrowserCopyScreenshot('02-backup-set-detail');
$page
->resize(430, 900)
->assertScript('document.documentElement.scrollWidth <= window.innerWidth', true)
->assertNoJavaScriptErrors()
->screenshot(true, spec371BrowserScreenshotName('03-backup-set-detail-mobile'));
spec371BrowserCopyScreenshot('03-backup-set-detail-mobile');
});
function spec371BrowserLoginUrl(User $user, ManagedEnvironment $tenant, string $redirect): string
{
return route('admin.local.smoke-login', [
'email' => $user->email,
'tenant' => $tenant->external_id,
'workspace' => $tenant->workspace->slug,
'redirect' => $redirect,
]);
}
function spec371BrowserListPath(ManagedEnvironment $tenant): string
{
$url = BackupSetResource::getUrl('index', panel: 'admin', tenant: $tenant);
return parse_url($url, PHP_URL_PATH) ?: '/admin';
}
function spec371BrowserViewPath(ManagedEnvironment $tenant, BackupSet $backupSet): string
{
$url = BackupSetResource::getUrl('view', ['record' => $backupSet], panel: 'admin', tenant: $tenant);
return parse_url($url, PHP_URL_PATH) ?: '/admin';
}
/**
* @param array<string, mixed> $attributes
*/
function spec371BrowserHealthyBackupSet(ManagedEnvironment $tenant, array $attributes = []): BackupSet
{
$backupSet = BackupSet::factory()
->for($tenant)
->create(array_merge([
'name' => 'Spec 371 Browser Healthy Backup',
'status' => 'completed',
'item_count' => 1,
'completed_at' => now()->subMinutes(30),
'metadata' => [],
], $attributes));
BackupItem::factory()
->for($tenant)
->for($backupSet)
->create([
'payload' => ['id' => 'spec-371-browser-policy'],
'metadata' => [],
'assignments' => [],
]);
return $backupSet;
}
function spec371BrowserSourceOperation(ManagedEnvironment $tenant, User $user, BackupSet $backupSet): OperationRun
{
return OperationRun::factory()
->forTenant($tenant)
->withUser($user)
->create([
'type' => OperationRunType::BackupScheduleExecute->value,
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'completed_at' => now()->subMinutes(20),
'context' => [
'backup_set_id' => (int) $backupSet->getKey(),
],
]);
}
function spec371BrowserScreenshotName(string $name): string
{
return 'spec371-backup-set-productization-'.$name;
}
function spec371BrowserCopyScreenshot(string $name): void
{
$filename = spec371BrowserScreenshotName($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/371-core-operator-view-surfaces-productization/artifacts/screenshots');
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
return;
}
if (! is_file($source)) {
$source = \Pest\Browser\Support\Screenshot::path($filename);
}
if (is_file($source)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$filename);
}
}

View File

@ -15,7 +15,7 @@
CarbonImmutable::setTestNow();
});
it('renders backup sets with lifecycle summary, related context, and secondary technical detail', function (): void {
it('renders backup sets with restore-point decision, related evidence, and secondary technical detail', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
@ -52,18 +52,19 @@
$this->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Backup quality')
->assertSee('Restore-point decision')
->assertSee('Action needed before restore review')
->assertSee('1 degraded item')
->assertSee('1 metadata-only')
->assertSee('1 assignment issue')
->assertSee('1 integrity warning')
->assertSee('Timing')
->assertSee('Technical and lifecycle detail')
->assertSee('Archive')
->assertSee('More')
->assertSee(OperationRunLinks::tenantlessView($run), false)
->assertDontSee('Related record')
->assertDontSee('>Completed</span>', false)
->assertSeeInOrder(['Nightly backup', 'Backup quality', 'Lifecycle overview', 'Related context', 'Technical detail']);
->assertSeeInOrder(['Nightly backup', 'Restore-point decision', 'Related operation and evidence', 'Technical and lifecycle detail']);
});
it('keeps operations context and technical empty states readable for sparse backup sets', function (): void {
@ -81,7 +82,8 @@
$this->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('No items captured')
->assertSee('Blocked until items are captured')
->assertSee('No backup items were captured.')
->assertSee('No backup metadata was recorded for this backup set.')
->assertSee('Metadata keys')
->assertDontSee('Related record');

View File

@ -28,7 +28,7 @@
$this->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Related context')
->assertSee('Related operation and evidence')
->assertSee('Operations')
->assertSee(OperationRunLinks::tenantlessView($run), false);

View File

@ -113,7 +113,7 @@ function getTableEmptyStateAction($component, string $name): ?\Filament\Actions\
expect($action->getTooltip())->toBe(UiTooltips::insufficientPermission());
});
test('readonly members still see backup quality truth on the backup-set list', function () {
test('readonly members still see restore-point decision truth on the backup-set list', function () {
$tenant = ManagedEnvironment::factory()->create();
[$user] = createUserWithTenant($tenant, role: 'readonly');
@ -138,6 +138,7 @@ function getTableEmptyStateAction($component, string $name): ?\Filament\Actions\
$this->actingAs($user)
->get(BackupSetResource::getUrl('index', tenant: $tenant))
->assertOk()
->assertSee('Backup quality')
->assertSee('Restore-point decision')
->assertSee('Action needed before restore review')
->assertSee('1 metadata-only');
});

View File

@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
use App\Filament\Resources\BackupSetResource;
use App\Filament\Resources\BackupSetResource\Pages\ViewBackupSet;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Actions\Action;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function (): void {
bindFailHardGraphClient();
});
it('renders a healthy backup set as a decision-first restore point without Graph calls', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$backupSet = spec371HealthyBackupSet($tenant, [
'name' => 'Spec 371 Healthy Backup',
'created_by' => 'operator@example.test',
'completed_at' => now()->subHour(),
'metadata' => ['source' => 'spec371'],
]);
$response = $this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Restore-point decision')
->assertSee('Usable for restore review')
->assertSee('Captured item inventory is available for operator review.')
->assertSee('Review included items below before starting any separate restore workflow.')
->assertSee('Technical and lifecycle detail')
->assertDontSee('Item review state')
->assertDontSee('This backup set has captured items ready for operator review before any separate restore workflow starts.')
->assertDontSee('Usable for review')
->assertDontSee('No degradations detected across');
spec371AssertOrdered($response->getContent(), [
'Backup set #'.$backupSet->getKey(),
'Restore-point decision',
'Usable for restore review',
'Technical and lifecycle detail',
]);
});
it('shows degraded backup input as action-needed before restore review', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$backupSet = BackupSet::factory()
->for($tenant)
->degradedCompleted()
->create([
'name' => 'Spec 371 Degraded Backup',
]);
$response = $this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Action needed before restore review')
->assertSee('Current degradation')
->assertSee('1 degraded item')
->assertSee('Treat this backup as investigation input until degraded items are reviewed.')
->assertSee('Review degraded included items and source operation before continuing into restore.');
spec371AssertOrdered($response->getContent(), [
'Restore-point decision',
'Current degradation',
'Technical and lifecycle detail',
]);
});
it('explains an empty backup set blocker before technical context', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$backupSet = BackupSet::factory()
->for($tenant)
->create([
'name' => 'Spec 371 Empty Backup',
'item_count' => 0,
'metadata' => [],
]);
$response = $this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $tenant))
->assertOk()
->assertSee('Blocked until items are captured')
->assertSee('Current blocker')
->assertSee('No backup items were captured.')
->assertSee('Create or refresh a backup set before starting restore review.')
->assertDontSee('Backup quality, lifecycle status, and related operations stay ahead of raw backup metadata.');
spec371AssertOrdered($response->getContent(), [
'Restore-point decision',
'Current blocker',
'Technical and lifecycle detail',
]);
});
it('keeps the backup sets list scan-first without repeated healthy zero-noise', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
spec371HealthyBackupSet($tenant, ['name' => 'Spec 371 Healthy Row']);
BackupSet::factory()
->for($tenant)
->degradedCompleted()
->create(['name' => 'Spec 371 Degraded Row']);
$this->actingAs($user)
->get(BackupSetResource::getUrl('index', tenant: $tenant))
->assertOk()
->assertSee('Restore-point decision')
->assertSee('Items captured')
->assertSee('Usable for restore review')
->assertSee('Action needed before restore review')
->assertSee('1 degraded item')
->assertDontSee('No degradations detected across');
});
it('shows a capability-aware empty state with one create backup CTA for authorized users', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
setAdminPanelContext($tenant);
$this->actingAs($user)
->get(BackupSetResource::getUrl('index', tenant: $tenant))
->assertOk()
->assertSee('No backup sets')
->assertSee('Create a backup set to capture policy versions and assignments for later restore review.')
->assertSee('Create backup set');
});
it('preserves destructive detail action confirmation and readonly blocking', function (): void {
[$owner, $tenant] = createUserWithTenant(role: 'owner');
$readonly = User::factory()->create();
createUserWithTenant(tenant: $tenant, user: $readonly, role: 'readonly');
$backupSet = spec371HealthyBackupSet($tenant, ['name' => 'Spec 371 Action Safety']);
$this->actingAs($owner);
setAdminPanelContext($tenant);
Livewire::actingAs($owner)
->test(ViewBackupSet::class, ['record' => (string) $backupSet->getKey()])
->assertActionExists('archive', fn (Action $action): bool => $action->isConfirmationRequired());
$this->actingAs($readonly);
setAdminPanelContext($tenant);
Livewire::actingAs($readonly)
->test(ViewBackupSet::class, ['record' => (string) $backupSet->getKey()])
->assertActionDisabled('archive')
->callAction('archive');
expect($backupSet->fresh()?->trashed())->toBeFalse();
});
it('denies wrong-environment backup set access as not found before content leaks', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$foreignTenant = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Spec 371 Foreign Environment',
]);
$foreignBackupSet = spec371HealthyBackupSet($foreignTenant, [
'name' => 'Spec 371 Foreign Backup',
]);
setAdminPanelContext($tenant);
$this->actingAs($user)
->get(BackupSetResource::getUrl('view', ['record' => (string) $foreignBackupSet->getKey()], tenant: $tenant))
->assertNotFound();
});
/**
* @param array<string, mixed> $attributes
*/
function spec371HealthyBackupSet(ManagedEnvironment $tenant, array $attributes = []): BackupSet
{
$backupSet = BackupSet::factory()
->for($tenant)
->create(array_merge([
'name' => 'Spec 371 Healthy Backup',
'item_count' => 1,
'completed_at' => now()->subMinutes(30),
'metadata' => [],
], $attributes));
BackupItem::factory()
->for($tenant)
->for($backupSet)
->create([
'payload' => ['id' => 'spec-371-policy'],
'metadata' => [],
'assignments' => [],
]);
return $backupSet;
}
/**
* @param list<string> $needles
*/
function spec371AssertOrdered(string $content, array $needles): void
{
$lastPosition = -1;
foreach ($needles as $needle) {
$position = strpos($content, $needle);
expect($position)->not->toBeFalse();
expect($position)->toBeGreaterThan($lastPosition);
$lastPosition = (int) $position;
}
}

View File

@ -66,6 +66,30 @@
->assertTableBulkActionEnabled('bulk_remove', [$item]);
});
it('keeps add policies enabled from the owner backup set when ambient tenant context is missing', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
setAdminPanelContext(null);
$backupSet = BackupSet::factory()->create([
'managed_environment_id' => $tenant->id,
'name' => 'Tenantless nested context backup',
]);
BackupItem::factory()->for($backupSet)->for($tenant)->create();
Livewire::test(BackupItemsRelationManager::class, [
'ownerRecord' => $backupSet,
'pageClass' => EditBackupSet::class,
])
->assertTableActionVisible('addPolicies')
->assertTableActionEnabled('addPolicies')
->assertTableActionExists('addPolicies', function (Action $action): bool {
return $action->getTooltip() === null;
});
});
it('hides actions after tenant scope is revoked mid-session', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$otherTenant = \App\Models\ManagedEnvironment::factory()->active()->create([

View File

@ -54,7 +54,7 @@ ## Coverage By Area
| Inventory | 8 | Route-discovered only; coverage, policy version detail, and raw-data exposure need later review. |
| Evidence / audit | 8 | Audit log captured; evidence/report detail routes need customer-safe progressive-disclosure review. |
| Reviews | 7 | Review register, customer workspace, review pack detail, and the rendered-report route now have bounded browser evidence; Spec 366 adds rendered-report profile, print, and mobile-ish captures while deeper evidence/report surfaces still remain open elsewhere. |
| Backup / restore | 6 | High-risk area; backup sets and restore runs were blocked by fixture capability. |
| Backup / restore | 6 | High-risk area; Spec 371 adds seeded browser proof for Backup Sets list/detail, while restore runs and create/failure workflow states remain unresolved. |
| Settings / admin | 5 | Workspace and environment access are RBAC-sensitive and need later review. |
| Provider / integration | 5 | Provider connections and required permissions are captured; create/edit/onboarding remain high-risk unresolved surfaces. |
| Findings | 5 | Queue/inbox patterns captured; finding detail needs individual triage target. |

View File

@ -7,42 +7,42 @@ # UI-013 Environment Backup Sets
| Area / scope | Backup / restore / environment |
| Archetype | Backup / Restore |
| Design depth | Strategic Surface |
| Repo truth | repo-verified route; browser blocked |
| Screenshot | `../screenshots/desktop/ui-013-environment-backup-sets.png` |
| Browser status | Local Spec 180 fixture returned Forbidden. |
| Repo truth | repo-verified route; Spec 371 browser verified |
| Screenshot | `../../../specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-01-backup-sets-list.png` |
| Detail screenshot | `../../../specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-02-backup-set-detail.png` |
| Mobile screenshot | `../../../specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-03-backup-set-detail-mobile.png` |
| Browser status | Spec 371 smoke passed with seeded owner/manager capability, healthy/degraded backup sets, included item, and linked operation. |
## First Five Seconds
The browser pass could not evaluate the product page because the fixture lacked sufficient capability or state. The route remains strategic because backup sets are recovery evidence and restore-point truth.
Spec 371 verifies the Backup Sets list/detail with a capability-backed fixture. The list now leads with restore-point decision, item count, and degradation guidance. The detail now answers whether the backup is usable before lifecycle, operation, or raw metadata.
## Productization Review
- Decision-first: not evaluated in browser.
- Evidence-first: expected to be central.
- Decision-first: implemented for list rows and detail decision zone.
- Evidence-first: included backup items are primary detail content below the decision.
- Context: environment-bound route.
- Customer/auditor safety: high when used as restore proof.
- Diagnostics: raw backup payload must not dominate default view.
- Customer/auditor safety: high when used as restore proof; Spec 371 avoids restore-safety claims beyond repo truth.
- Diagnostics: operation evidence and technical/lifecycle metadata remain available but secondary/collapsed.
## Information Inventory
Expected default content should show backup set identity, source environment, capture time, coverage, integrity/fidelity, failure/partial state, and restore readiness.
Default content shows backup set identity, restore-point decision, captured item count, degraded/blocking state when present, included item evidence, operation traceability, and secondary lifecycle/technical details.
## Dangerous Actions
Create backup, retry, restore from backup, delete/archive, and export evidence are high impact and require confirmation, audit, and OperationRun continuity.
Create backup, archive/delete, and restore-adjacent flows are high impact and require confirmation, authorization, audit, and OperationRun continuity. Spec 371 did not add a restore-from-backup action/link and preserved existing action handlers.
## Scores
| IA | Density | User Clarity | Sellability | Disclosure | Hierarchy | DS Fit | A11y | Responsive | Components | UX Writing | Perf |
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Spec 371 did not rerun the full Spec 368 numeric scorecard. Browser smoke evidence now verifies the target list/detail hierarchy, JavaScript health, screenshots, and mobile width check.
## Top Issues
1. Browser review blocked by local capability/data.
2. Strategic recovery truth needs individual target mockup.
3. Restore-point safety posture must be first-class.
1. Backup Set create and additional partial/failure workflow states still need separate target coverage.
2. Restore Runs remain a separate high-risk backup/restore surface.
3. Manual review should verify production-like data density beyond the bounded smoke fixture.
## Target Direction
P0 backup/recovery target mockup. Add a seeded/capability-backed browser review in the implementation-wave spec.
Spec 371 provides seeded browser proof for list/detail. Keep future backup/restore work focused on create flows, restore execution history, and richer backup failure states.

View File

@ -57,8 +57,8 @@ # Route Inventory
| UI-048 | `/admin/workspaces/{workspace}/environments/{environment}/stored-reports/{record}` | resource | Stored Report Detail | Evidence / audit | environment record | route exists | environment + record entitlement | Evidence / Audit | Reviews | Strategic Surface | repo-verified | - | - | Customer/auditor readable report review needed. |
| UI-049 | `/admin/workspaces/{workspace}/environments/{environment}/backup-schedules` | resource | Backup Schedules | Backup / restore | environment-bound | route exists | environment entitlement + backup capability | Backup / Restore | Operations / Monitoring | Strategic Surface | repo-verified | - | - | Schedule run/retry actions are high impact. |
| UI-050 | `/admin/workspaces/{workspace}/environments/{environment}/backup-schedules/create` and `/edit` | resource | Backup Schedule Create/Edit | Backup / restore | environment-bound | route exists | backup schedule capability | Backup / Restore | Settings / Admin | Domain Pattern Surface | repo-verified | - | - | Form state and confirmation copy need later review. |
| UI-051 | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets` | resource | Backup Sets | Backup / restore | environment-bound | browser blocked by capability in fixture | environment entitlement + backup capability | Backup / Restore | Evidence / Audit | Strategic Surface | repo-verified | [blocked](screenshots/desktop/ui-013-environment-backup-sets.png) | [report](page-reports/ui-013-environment-backup-sets.md) | Route exists; local fixture returned Forbidden. |
| UI-052 | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets/create` and `/view` | resource | Backup Set Create/View | Backup / restore | environment record/workflow | route exists | backup capability | Backup / Restore | Evidence / Audit | Strategic Surface | repo-verified | - | - | Backup creation and restore-point detail require seeded capability/data. |
| UI-051 | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets` | resource | Backup Sets | Backup / restore | environment-bound | reachable | environment entitlement + backup capability | Backup / Restore | Evidence / Audit | Strategic Surface | browser-verified | [desktop](../../specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-01-backup-sets-list.png) | [report](page-reports/ui-013-environment-backup-sets.md) | Spec 371 verifies seeded Backup Sets list/detail with restore-point decision, included items, and secondary technical detail. |
| UI-052 | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets/create` and `/view` | resource | Backup Set Create/View | Backup / restore | environment record/workflow | route exists | backup capability | Backup / Restore | Evidence / Audit | Strategic Surface | repo-verified | - | - | Backup creation plus partial/failure restore-point states still need separate seeded workflow coverage. |
| UI-053 | `/admin/workspaces/{workspace}/environments/{environment}/restore-runs` | resource | Restore Runs | Backup / restore | environment-bound | browser blocked by capability in fixture | environment entitlement + restore capability | Backup / Restore | Operations / Monitoring | Strategic Surface | repo-verified | [blocked](screenshots/desktop/ui-014-restore-runs.png) | [report](page-reports/ui-014-restore-runs.md) | Route exists; local fixture returned Forbidden. |
| UI-054 | `/admin/workspaces/{workspace}/environments/{environment}/restore-runs/create` and `/view` | resource | Restore Run Create/View | Backup / restore | environment record/workflow | route exists | restore capability | Backup / Restore | Operations / Monitoring | Strategic Surface | repo-verified | - | - | Destructive/high-impact workflow; individual target spec required. |
| UI-055 | `/admin/baseline-profiles` | resource | Baseline Profiles | Governance | workspace analysis | reachable | workspace member | Drift / Diff | Settings / Admin | Strategic Surface | repo-verified | [desktop](screenshots/desktop/ui-010-baseline-profiles.png) | [report](page-reports/ui-010-baseline-profiles.md) | Workspace-owned baseline library. |

View File

@ -52,7 +52,7 @@ ### Deferred By Spec 325
| P0 | UI-036 | Exception Detail | `/admin/workspaces/{workspace}/environments/{environment}/finding-exceptions/{record}` | Accepted-risk detail. | Expiry, approver, and audit trail need strong hierarchy. | Individual detail mockup. |
| P0 | UI-038 | Customer Review Workspace | `/admin/reviews/workspace` | Customer/auditor-facing workspace. | Customer-safe language and proof context are critical. | Individual target mockup. |
| P0 | UI-049 | Backup Schedules | `/admin/workspaces/{workspace}/environments/{environment}/backup-schedules` | Backup readiness and schedule safety. | Run/retry controls are high impact. | Backup pattern target. |
| P0 | UI-051 | Backup Sets | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets` | Restore-point truth and recovery evidence. | Browser blocked by capability fixture. | Individual backup set target with fixture. |
| P0 | UI-051 | Backup Sets | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets` | Restore-point truth and recovery evidence. | Spec 371 adds seeded browser proof for list/detail; richer failure/create states remain separate. | Keep restore execution and backup creation targets separate. |
| P0 | UI-053 | Restore Runs | `/admin/workspaces/{workspace}/environments/{environment}/restore-runs` | Restore execution history. | Browser blocked; destructive workflow context unknown. | Individual restore target with fixture. |
| P0 | UI-054 | Restore Run Create/View | `/admin/workspaces/{workspace}/environments/{environment}/restore-runs/create` and `/view` | High-impact restore workflow. | Dry-run, confirmation, partial restore, and audit UX need proof. | Restore workflow target. |
| P0 | UI-055 | Baseline Profiles | `/admin/baseline-profiles` | Baseline source of governance truth. | Assignment/capture/compare semantics need hierarchy. | Drift/diff target pattern. |

View File

@ -4,9 +4,9 @@ # Unresolved Pages
Summary:
- High-priority unresolved/manual-review entries: 28.
- Capability/fixture blockers with desktop evidence: UI-051, UI-053, UI-061.
- Strategic routes not browser-captured in this bounded pass: 24.
- High-priority unresolved/manual-review entries: 27.
- Capability/fixture blockers with desktop evidence: UI-053, UI-061.
- Strategic routes not browser-captured in this bounded pass: 23.
- Hidden/file-discovered manual-review surface: UI-080.
| ID | Page | Blocker / Reason | Needed Evidence | Next Action |
@ -20,7 +20,6 @@ # Unresolved Pages
| UI-046 | Evidence Snapshot Detail | Dynamic raw/support evidence detail requires snapshot record. | Snapshot with normalized summary and raw payload. | Add progressive-disclosure review. |
| UI-048 | Stored Report Detail | Dynamic report artifact requires stored report record. | Stored report with customer-facing summary and export metadata. | Add stored-report target pass. |
| UI-049 | Backup Schedules | Strategic backup schedule page was not captured. | Environment with active, paused, failing, and never-run schedules. | Include in backup/restore safety spec. |
| UI-051 | Backup Sets | Browser returned Forbidden for the local fixture. | Capability-backed environment with backup-set records. | Re-test with seeded capability; screenshot exists as blocker evidence. |
| UI-052 | Backup Set Create/View | Backup workflow/detail route needs capability and backup data. | Create form, backup-set view, partial/failure states. | Add backup workflow target. |
| UI-053 | Restore Runs | Browser returned Forbidden for the local fixture. | Capability-backed environment with restore-run records. | Re-test with seeded capability; screenshot exists as blocker evidence. |
| UI-054 | Restore Run Create/View | Restore create/view route needs capability and restore data. | Dry-run, validation, partial restore, conflict, and confirmation states. | Add restore workflow target. |

View File

@ -0,0 +1,37 @@
# Affected Files
Verification level: derived from repo file discovery, implementation diff, feature tests, and browser smoke evidence.
| File | Purpose | Change type | Surface affected | Verification level | Risk |
|---|---|---|---|---|---|
| `apps/platform/app/Filament/Resources/BackupSetResource.php` | Backup Set resource table/detail schema and actions | runtime edit | Backup Sets list/detail | feature + browser verified | high, restore-adjacent action surface |
| `apps/platform/app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php` | Included item table title | runtime edit | Backup Set detail item relation | browser verified | low |
| `apps/platform/tests/Feature/Filament/Spec371BackupSetProductizationTest.php` | Focused Feature/Livewire coverage | new test | Backup Sets list/detail/action safety | passed | medium fixture cost |
| `apps/platform/tests/Feature/Filament/BackupSetEnterpriseDetailPageTest.php` | Existing enterprise detail coverage | assertion update | Backup Set detail | passed | low |
| `apps/platform/tests/Feature/Filament/BackupSetRelatedNavigationTest.php` | Existing operation link coverage | assertion update | Backup Set detail/list links | passed | low |
| `apps/platform/tests/Feature/Filament/BackupSetUiEnforcementTest.php` | Existing UI enforcement coverage | assertion update | Backup Set list readonly truth | passed | low |
| `apps/platform/tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php` | Existing relation-manager RBAC coverage | assertion/new regression update | Backup Set detail Add Policies action | passed | low |
| `apps/platform/tests/Browser/Spec371BackupSetProductizationSmokeTest.php` | Browser proof and screenshots | new test | Backup Sets list/detail/responsive smoke | passed | medium browser fixture cost |
| `apps/platform/tests/Browser/Spec334NestedFilamentContextContractSmokeTest.php` | Existing nested-context browser smoke | assertion update | Backup Set Add Policies picker | passed | low |
| `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md` | UI audit registry page report | documentation update | Backup Sets | browser verified | low |
| `docs/ui-ux-enterprise-audit/route-inventory.md` | UI route inventory | documentation update | Backup Sets | browser verified | low |
| `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` | UI coverage matrix | documentation update | Backup Sets | browser verified | low |
| `docs/ui-ux-enterprise-audit/strategic-surfaces.md` | Strategic surface ledger | documentation update | Backup Sets | browser verified | low |
| `docs/ui-ux-enterprise-audit/unresolved-pages.md` | Blocked/fixture status | documentation update | Backup Sets | browser verified | low |
| `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-01-backup-sets-list.png` | Browser screenshot | generated artifact | Backup Sets list | browser verified | low |
| `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-02-backup-set-detail.png` | Browser screenshot | generated artifact | Backup Set detail | browser verified | low |
| `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-03-backup-set-detail-mobile.png` | Browser screenshot | generated artifact | Backup Set detail mobile | browser verified | low |
| `specs/371-core-operator-view-surfaces-productization/artifacts/*` | Spec-local proof/report artifacts | implementation report updates | Spec 371 | repo-verified | low |
## Inspected But Not Runtime-Edited
- `apps/platform/app/Filament/Resources/BackupSetResource/Pages/ListBackupSets.php`
- `apps/platform/app/Filament/Resources/BackupSetResource/Pages/ViewBackupSet.php`
## Files Explicitly Out Of Scope Unless Spec/Plan Changes
- `specs/328-*`, `specs/330-*`, `specs/335-*`, `specs/352-*`, `specs/367-*`, `specs/369-*`, `specs/370-*`
- Restore execution jobs/services
- Backup capture jobs/services
- Models, migrations, policies, routes, provider adapters, Graph contracts
- Filament panel providers and app-level navigation

View File

@ -0,0 +1,16 @@
# Before / After Screenshot Index
Verification level: browser-verified for existing before screenshots where Spec 368 provides them; browser-verified for Spec 371 after screenshots.
| Page | Before screenshot | After screenshot | Notes |
|---|---|---|---|
| Backup Sets list | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/004-workflow-surface-list-backup-sets.png` | `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-01-backup-sets-list.png` | Browser smoke passed with scan-first usability/item/degradation hierarchy |
| Backup Set detail | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/005-workflow-surface-view-backup-set.png` | `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-02-backup-set-detail.png` | Browser smoke passed with restore-point decision before technical detail |
| Backup Set mobile/narrow viewport | N/A | `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-03-backup-set-detail-mobile.png` | Browser smoke passed width overflow check |
| Environment Dashboard | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/001-operator-surface-dashboard-environment-dashboard.png` | Existing Spec 352 screenshots, not recaptured by default | Regression context only |
| Operations Hub | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/002-operator-surface-list-operations-hub.png` | Existing Spec 328/367 screenshots, not recaptured by default | Regression context only |
| OperationRun View | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/003-operator-surface-view-operation-run.png` | Existing OperationRun specs, not recaptured by default | Regression context only |
| Restore Run View | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/006-workflow-surface-view-restore-run.png` | Existing Spec 335 screenshots, not recaptured by default | Regression context only |
| Baseline Profile View | `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/008-decision-surface-view-baseline-profile.png` | Existing Spec 369 screenshots, not recaptured by default | Regression context only |
No separate expanded-technical screenshot was captured because the detail smoke verifies the collapsed secondary section label and responsive width without opening optional diagnostics.

View File

@ -0,0 +1,39 @@
# Browser Verification Report
Status: executed and passing.
## Required Browser Targets
| Target | URL pattern | Required proof |
|---|---|---|
| Backup Sets list | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets` | reachable page, no JS errors, scan-first usability/item/degradation hierarchy |
| Backup Set detail | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets/{record}` | reachable page, no JS errors, usability first, included items primary, technical metadata secondary |
## Screenshot Outputs
- `artifacts/screenshots/spec371-backup-set-productization-01-backup-sets-list.png`
- `artifacts/screenshots/spec371-backup-set-productization-02-backup-set-detail.png`
- `artifacts/screenshots/spec371-backup-set-productization-03-backup-set-detail-mobile.png`
## Score Targets
Use the Spec 368 score model.
| Page | Before score | Target after score |
|---|---:|---:|
| Backup Sets list | 3.9 | Not numerically re-scored; smoke checks verify target hierarchy |
| Backup Set detail | 3.4 | Not numerically re-scored; smoke checks verify target hierarchy |
## Results
- Command: `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec371BackupSetProductizationSmokeTest.php --compact`
- Result: passed, 1 test, 31 assertions.
- Auth/fixture: local smoke-login route with an owner/manager user, an environment tenant, one healthy backup set with an included backup item, one degraded backup set, and a source `OperationRun` linked by `context.backup_set_id`.
- URLs tested: generated Filament paths for Backup Sets list and healthy Backup Set detail under `/admin/workspaces/{workspace}/environments/{environment}/backup-sets`.
- Screenshots created: all files listed above.
- JavaScript errors: none asserted by Pest Browser.
- Console logs: none asserted on desktop list/detail paths.
- Responsive check: mobile detail resized to `430x900` and asserted `document.documentElement.scrollWidth <= window.innerWidth`.
- Browser-comment follow-up: the detail smoke now asserts the summary header and the usability fact do not repeat healthy usability labels before or inside `Restore-point decision`, and the separate included-items summary fact block no longer appears before the actual item relation table.
- Add Policies follow-up: the relation-manager action now resolves authorization from the owner backup set, so nested Livewire updates do not disable it with `Environment context not available`; manually verified in the in-app browser and covered by the Spec 334 picker smoke.
- Remaining issues: no in-scope browser blocker remains for UI-051 Backup Sets list/detail. Backup Set create, Backup Schedules, and Restore Runs remain separate unresolved backup/restore surfaces.

View File

@ -0,0 +1,42 @@
# Implementation Notes
Status: implemented.
## Scope Notes
- Active implementation target: Backup Sets list/detail.
- Regression/context only: Environment Dashboard, Operations Hub, OperationRun View, Restore Run View, Baseline Profile View.
- Completed spec packages must not be edited or normalized.
- Runtime implementation stayed inside `BackupSetResource`; page classes, relation manager, services, jobs, policies, routes, migrations, Graph contracts, panel providers, and assets were not changed.
- Backup Sets global search is explicitly disabled for this resource.
## Implementation Guardrails
- Preserve existing backup/restore-adjacent action handlers, service/job paths, resource-level capability/UI enforcement, audit logging, notifications, and OperationRun links.
- Do not add backup readiness persistence or restore eligibility state.
- Do not call Graph during render.
- Do not move destructive/high-impact actions into unsafe prominent placement.
- Do not default-show raw payloads, provider IDs, internal IDs, exact timestamps, or operation context JSON as primary content.
- Use Filament v5 and Livewire v4-compatible APIs only.
## Runtime Result
- Backup Sets list now leads with `Restore-point decision`, `Items captured`, and state-aware guidance.
- Healthy rows avoid repeated zero/no-degradation copy.
- Backup Set detail now starts with `Restore-point decision`, then `Included backup items`, then secondary related operation/evidence and collapsed technical/lifecycle detail.
- Backup Set detail header stays context-only for healthy backups and does not repeat the healthy usability decision before the decision zone.
- Backup Set detail usability facts use one plain value instead of repeating the same state as an adjacent badge.
- The separate included-items summary fact block was removed; the actual item relation table is titled `Included backup items`.
- `Add Policies` on the item relation manager now resolves RBAC context from the owner backup set, avoiding a false `Environment context not available` disabled state in nested Livewire requests.
- Empty backup sets show a blocker before technical context.
- Degraded backup sets show action-needed copy before restore review language.
- No new restore-from-backup action or restore execution path was introduced.
- Existing destructive/high-impact action handlers and authorization paths were preserved.
- Rendering tests bind a fail-hard Graph client; the Spec 371 render paths do not call Graph.
## Stop Conditions
- A change requires migration/model/policy/route/Graph/OperationRun lifecycle work.
- A shared helper change materially affects completed context surfaces.
- Browser fixture work becomes structural across backup/restore rather than bounded to Backup Sets.
- Existing action safety cannot be preserved without changing backend behavior.

View File

@ -0,0 +1,69 @@
# Page Contracts
Verification level: derived from Spec 368 browser evidence, Spec 370 contract, and repo-verified completed-spec status.
## Active Implementation Surface: Backup Sets List
| Field | Contract |
|---|---|
| Page | Backup Sets list |
| Primary question | Which backup should I inspect or rely on next? |
| Current issue | Spec 368 said the list was scan-friendly but backup quality and operation links needed a stronger usability decision. |
| Implementation result | Spec 371 puts restore-point decision, item count, and degradation guidance ahead of lifecycle/technical columns. |
| Target first viewport | List context, restore-point decision per row, items captured, current degradation if present, primary inspect/open model, specific empty state. |
| Main content | Backup rows with usability and included-item inventory signals. |
| Sidebar/details content | N/A for list; table columns may hide technical fields by default. |
| Suppressed/default-hidden content | Repeated zero degradation/no-issue noise, raw IDs, exact technical timing unless table default already requires it. |
| Primary action | Open/inspect backup set, or create/capture backup when empty and authorized. |
| Secondary actions | Existing row/header actions and More group actions; operation detail remains available from related evidence paths. |
| Evidence access | Backup item inventory and operation link. |
| Diagnostics access | Operation detail and technical columns/details where already available. |
| Acceptance notes | Must preserve resource-level capability/UI enforcement action state and not introduce restore safety claims. |
## Active Implementation Surface: Backup Set Detail
| Field | Contract |
|---|---|
| Page | Backup Set detail |
| Primary question | Is this backup usable, and what is included? |
| Current issue | Spec 368 finding F04: usability, lifecycle, timing, related context, and technical detail appeared as peer sections. |
| Implementation result | Spec 371 makes Restore-point decision the first visible decision zone, removes duplicate included-item summary facts, and titles the actual item relation table `Included backup items`; operation evidence and lifecycle/raw metadata remain collapsed or secondary. |
| Target first viewport | Usability/readiness, reason, impact, captured item count, current degradation/blocker if present, one primary next action. |
| Main content | Actual included backup item table and any active degradation/quality exception in the decision zone. |
| Sidebar/details content | Captured at, created by, source operation, schedule/source context, retention/context metadata. |
| Suppressed/default-hidden content | Internal IDs, exact timestamps, operation IDs/context JSON, provider IDs, raw payload, technical counters. |
| Primary action | Review included backup items before using a separate restore workflow; no new restore-from-backup action/link. |
| Secondary actions | Open operation, edit/view related context, archive/delete in existing safe placement. |
| Evidence access | Included item table, quality summary, source operation link. |
| Diagnostics access | Collapsed technical details or operation detail. |
| Acceptance notes | Must not change restore workflow eligibility logic or backup quality logic. |
## Regression Context: Environment Dashboard
- Status: completed context through Specs 330 and 352.
- Treatment: do not refactor in Spec 371. Use only for regression reasoning if backup helper changes affect shared dashboard paths.
- Primary question: Is this environment ready, blocked, stale, or requiring review?
## Regression Context: Operations Hub
- Status: completed context through Specs 328, 365, and 367.
- Treatment: do not refactor in Spec 371. Preserve OperationRun links/actionability semantics.
- Primary question: Which operations need attention right now?
## Regression Context: OperationRun View
- Status: completed context through Specs 358-367.
- Treatment: do not refactor in Spec 371. Preserve execution truth, actionability, and diagnostics separation.
- Primary question: What happened in this operation, and does anyone need to act?
## Regression Context: Restore Run View
- Status: completed context through Spec 335.
- Treatment: do not refactor in Spec 371. Preserve restore outcome/proof split and safety-gate logic.
- Primary question: Was this restore safe, what happened, and what proof exists?
## Regression Context: Baseline Profile View
- Status: completed context through Spec 369.
- Treatment: do not refactor in Spec 371. Preserve decision-first baseline readiness and action safety.
- Primary question: What is this baseline for, is it usable, and what does it govern?

View File

@ -0,0 +1,41 @@
# Source Audit Summary
Verification level: repo-verified for file presence and existing code paths; browser-verified where Spec 368 screenshots or browser notes state browser evidence; derived from existing implementation for productization recommendations.
## Source Inputs
| Source | Availability | Verification level | Use |
|---|---|---|---|
| User-provided Spec 371 draft | available | user-provided | Primary requested candidate and page-level requirements |
| `specs/368-platform-ui-signal-to-noise-browser-audit/audit.md` | available | repo-verified | Audit counts, top findings, prioritized refactor candidates |
| `specs/368-platform-ui-signal-to-noise-browser-audit/findings.md` | available | repo-verified | Backup Set View finding `UI-AUDIT-368-F04` |
| `specs/368-platform-ui-signal-to-noise-browser-audit/page-scorecard.csv` | available | repo-verified | Scores and target pages |
| `specs/368-platform-ui-signal-to-noise-browser-audit/spec-candidates.md` | available | repo-verified | Candidate B source |
| `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/005-workflow-surface-view-backup-set.png` | available | browser-verified | Backup Set before screenshot |
| `specs/370-global-surface-information-architecture-contract/artifacts/surface-contract.md` | available | repo-verified | Decision-first IA contract |
| `specs/370-global-surface-information-architecture-contract/artifacts/page-assessment-checklist.md` | available | repo-verified | Page assessment checklist |
| `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md` | available | repo-verified | Current UI registry status and fixture gap |
## Key Evidence
- Spec 368 rated Backup Set View at 3.4 overall and identified lifecycle, timing, related context, and technical detail as competing with usability.
- Spec 368 Candidate B includes Backup Set View as a core operator productization surface.
- The broader user draft includes six surfaces, but the completed-spec guardrail requires narrowing because several named surfaces already have completed or validated productization specs.
- UI audit registry currently marks Backup Sets as strategic but fixture/browser review constrained in the older UI-013 record, while Spec 368 has a browser-verified Backup Set detail screenshot from a later audit context.
## Completed-Spec Guardrail
| Related spec | Status signal | Treatment |
|---|---|---|
| Spec 328 Operations Hub | implementation close-out and completed task markers | context only |
| Spec 330 Environment Dashboard / Baseline Compare | completed task markers and page-report updates | context only |
| Spec 335 Restore Run detail | completed task markers and browser proof | context only |
| Spec 352 Environment Dashboard guidance | implemented repo-truth map and completed tasks | context only |
| Spec 367 OperationRun actionability | implementation close-out and completed tasks | context only |
| Spec 369 Baseline Profile detail | completed implementation tasks and browser smoke | context only |
| Spec 370 Global Surface IA Contract | completed preparation artifacts | consumed as contract |
## Scope Decision
Active implementation is limited to Backup Sets list/detail productization and backup-set browser proof. Other draft-listed surfaces are regression/context only unless implementation discovers an actual shared-helper regression and the spec/plan is updated first.

View File

@ -0,0 +1,77 @@
# Validation Report
Status: implementation validation complete.
## Repo Safety
- Branch before Spec Kit execution: `platform-dev`
- Active branch: `371-core-operator-view-surfaces-productization`
- HEAD at implementation validation: `c36cb437 spec: add global surface IA contract (#441)`
- Dirty state: expected implementation changes only.
- Branch naming deviation from preparation: the user-provided draft named `feat/371-core-operator-view-surfaces-productization`, but the repository script created `371-core-operator-view-surfaces-productization`; this implementation stayed on the script-generated branch.
- Completed context specs `328`, `330`, `335`, `352`, `367`, `369`, and `370` were read-only and not edited.
## Gate Results
- Spec Readiness Gate: PASS. `spec.md`, `plan.md`, `tasks.md`, checklist, and required artifacts existed with no blocking open questions.
- Implementation Scope Gate: PASS. Runtime scope stayed on Backup Sets list/detail productization plus tests and registry artifacts.
- Completed-Surface Regression Gate: PASS. No completed context surfaces were refactored.
- Runtime Expansion Gate: PASS. No migrations, routes, packages, policies, services/jobs, Graph contracts, panel providers, queues, schedulers, storage, env vars, or Filament assets were changed.
- Post-implementation analysis: PASS after one bounded fix cycle to update existing BackupSet test expectations for the new Spec 371 labels and hierarchy, plus browser-comment follow-ups to remove duplicated healthy decision copy from the detail header, usability fact, included-items summary block, and false Add Policies environment-context disabled state.
## Validation Commands
| Command | Result |
|---|---|
| `cd apps/platform && php -l app/Filament/Resources/BackupSetResource.php` | pass |
| `cd apps/platform && php -l tests/Feature/Filament/Spec371BackupSetProductizationTest.php` | pass |
| `cd apps/platform && php -l tests/Browser/Spec371BackupSetProductizationSmokeTest.php` | pass |
| `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec371` | pass: 8 tests, 92 assertions |
| `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=BackupSet` | first run found 4 stale assertion failures; after updating existing tests, pass: 58 tests, 475 assertions |
| `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec371BackupSetProductizationSmokeTest.php --compact` | pass: 1 test, 31 assertions |
| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php` | pass: 5 tests, 42 assertions |
| `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec334NestedFilamentContextContractSmokeTest.php --compact` | pass after updating stale `Related context` expectation: 2 tests, 25 assertions |
| `cd apps/platform && ./vendor/bin/sail pint --dirty` | pass: 0 files changed |
| `git diff --check` | pass |
## Browser Proof
- Fixture: smoke-login owner/manager user, environment tenant, healthy backup set with included item, degraded backup set, and linked source `OperationRun`.
- List target: `/admin/workspaces/{workspace}/environments/{environment}/backup-sets`.
- Detail target: generated Backup Set view path under the same environment tenant.
- JavaScript/browser console: no JS errors asserted; no console logs asserted for desktop list/detail paths.
- Duplication checks: browser smoke asserts the first detail section does not include `Usable for review`, `Usability`, or healthy `Active` state before the decision zone; the detail page no longer renders the redundant healthy `Usable for review` badge; and the included-items relation table no longer has a preceding duplicate `Item review state` summary card.
- Add Policies context: in-app browser verification confirms the `Add Policies` button is enabled, has no context tooltip/title, opens the picker modal, and the page/modal no longer contains `Environment context not available`.
- Responsive proof: detail page at `430x900` asserted no horizontal overflow.
- Screenshots:
- `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-01-backup-sets-list.png`
- `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-02-backup-set-detail.png`
- `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/spec371-backup-set-productization-03-backup-set-detail-mobile.png`
## Runtime Files Changed
- `apps/platform/app/Filament/Resources/BackupSetResource.php`
- `apps/platform/tests/Feature/Filament/Spec371BackupSetProductizationTest.php`
- `apps/platform/tests/Browser/Spec371BackupSetProductizationSmokeTest.php`
- Existing BackupSet tests updated for the new labels: `BackupSetEnterpriseDetailPageTest.php`, `BackupSetRelatedNavigationTest.php`, `BackupSetUiEnforcementTest.php`
## Registry And Artifact Updates
- UI-051 Backup Sets changed from fixture-blocked to browser-verified.
- `unresolved-pages.md` reduced the unresolved/manual-review count from 28 to 27 and removed UI-051 from capability blockers.
- Backup / restore coverage notes now distinguish verified Backup Sets list/detail from still-unresolved restore runs, create flows, and richer failure states.
- Spec-local affected files, page contracts, implementation notes, screenshot index, and browser report were updated to match final implementation.
## Filament And Deployment Notes
- Filament v5 / Livewire v4-compatible APIs only.
- Panel provider registration remains `apps/platform/bootstrap/providers.php`; no provider registration changes.
- `BackupSetResource` global search is explicitly disabled.
- Destructive/high-impact action paths were preserved. Existing archive/restore/delete/add/remove semantics continue through current Filament action handlers, authorization/UI enforcement, notifications, audit/OperationRun paths where already present; Spec 371 tests assert archive confirmation, readonly blocking, and wrong-environment not-found behavior.
- Asset strategy: no new Filament assets and no deploy change to `filament:assets`.
- Deployment impact: no migrations, env vars, queues, scheduler, storage, Graph calls, or runtime infrastructure changes.
## Known Limitations
- Numeric Spec 368 page-scorecard values were not recomputed. Browser smoke validates the target hierarchy and screenshots instead.
- Backup Set create flow, partial/failure restore-point states, Backup Schedules, and Restore Runs remain separate backup/restore surfaces.

View File

@ -0,0 +1,51 @@
# Requirements Checklist: Spec 371 - Core Operator View Surfaces Productization Pass v1
**Purpose**: Validate that Spec 371 is a bounded preparation package and does not reopen completed productization specs.
**Created**: 2026-06-10
**Feature**: `specs/371-core-operator-view-surfaces-productization/spec.md`
## Candidate Selection
- [x] CHK001 Selected candidate exists in source material: user-provided Spec 371 draft and Spec 368 Candidate B.
- [x] CHK002 No existing `specs/371-*` package or branch was found before creation.
- [x] CHK003 Related completed/validated specs are context only and are not modified.
- [x] CHK004 Scope is narrowed to the remaining Backup Set productization gap instead of reopening completed surfaces.
- [x] CHK005 Close alternatives are deferred instead of hidden inside Spec 371.
## Requirements Quality
- [x] CHK006 Spec has a clear problem statement and user-visible value.
- [x] CHK007 Spec includes functional requirements, non-functional requirements, acceptance criteria, assumptions, risks, and open questions.
- [x] CHK008 User stories are independently testable.
- [x] CHK009 Out-of-scope boundaries explicitly exclude restore/capture backend behavior, migrations, models, routes, Graph contracts, and completed specs.
- [x] CHK010 No `[NEEDS CLARIFICATION]` markers remain.
## Architecture And Constitution
- [x] CHK011 Proportionality review is included.
- [x] CHK012 No new persisted truth, status family, provider contract, or cross-domain UI framework is proposed.
- [x] CHK013 UI-COV-001 impact is checked and UI/Productization Coverage is complete.
- [x] CHK014 RBAC, workspace/environment isolation, action safety, auditability, OperationRun semantics, and evidence/diagnostics separation are addressed.
- [x] CHK015 Test governance explicitly names Feature/Livewire plus bounded Browser lanes.
## Implementation Readiness
- [x] CHK016 `spec.md`, `plan.md`, and `tasks.md` exist.
- [x] CHK017 Required spec-local artifacts exist.
- [x] CHK018 Tasks are ordered, small, verifiable, and limited to the selected scope.
- [x] CHK019 Browser proof expectations are clear and do not fake blocked verification.
- [x] CHK020 Preparation can proceed without unresolved product decisions.
## Filament / UI Safety
- [x] CHK021 Filament v5 and Livewire v4 compliance is stated.
- [x] CHK022 Provider registration location remains `apps/platform/bootstrap/providers.php`.
- [x] CHK023 Global search posture is explicitly guarded.
- [x] CHK024 Destructive/high-impact actions retain `->action(...)`, confirmation, authorization, audit, and tests.
- [x] CHK025 Asset strategy states no Filament assets expected and documents `filament:assets` if that changes.
## Review Outcome
- [x] CHK026 Review outcome class: `acceptable-special-case` because the broad draft was narrowed to avoid completed-spec overlap.
- [x] CHK027 Workflow outcome: `keep` because the active implementation slice is bounded and repo-backed.

View File

@ -0,0 +1,228 @@
# Implementation Plan: Spec 371 - Core Operator View Surfaces Productization Pass v1
**Branch**: `371-core-operator-view-surfaces-productization` | **Date**: 2026-06-10 | **Spec**: `specs/371-core-operator-view-surfaces-productization/spec.md`
**Input**: Feature specification from `specs/371-core-operator-view-surfaces-productization/spec.md`
## Summary
Implement a bounded Backup Set list/detail productization pass that consumes Spec 370's decision-first IA contract and resolves the remaining Spec 368 Candidate B backup-set gap. The runtime work must make backup usability, included items, degradation/blocker state, and the safest next action visible before lifecycle metadata and technical context.
The plan intentionally narrows the user-provided six-page draft: Environment Dashboard, Operations Hub, OperationRun View, Restore Run View, and Baseline Profile View already have completed/validated productization specs and remain regression/context surfaces only. No completed spec package is edited.
## Technical Context
**Language/Version**: PHP 8.4, Laravel 12, Filament v5, Livewire v4.
**Primary Dependencies**: Existing Filament resources/pages, backup quality helpers, resource-level capability/UI enforcement, OperationRun link helpers, Pest 4, browser smoke harness.
**Storage**: Existing database only. No migration or new persistence expected.
**Testing**: Pest Feature/Livewire tests and bounded Browser smoke.
**Validation Lanes**: fast-feedback/confidence for Feature tests; browser for Backup Set list/detail smoke; `git diff --check`; Pint for touched PHP.
**Target Platform**: TenantPilot Laravel monolith under `apps/platform`.
**Project Type**: Laravel/Filament admin UI.
**Performance Goals**: No new per-row service calls or Graph calls during render; avoid N+1 regressions on backup item counts/quality.
**Constraints**: No restore/capture backend behavior changes, no Graph calls, no route additions, no completed-spec artifact edits, no broad UI framework, no migrations.
**Scale/Scope**: Existing Backup Sets list and Backup Set detail, plus spec-local artifacts and UI audit coverage updates.
## Candidate Selection Gate
- **Selected candidate exists in source material**: yes, user-provided Spec 371 draft and Spec 368 Candidate B.
- **Not already covered by active/completed spec**: partially. The broad six-page draft overlaps completed specs; the narrowed backup-set implementation slice is not covered by a completed productization spec.
- **Completed-spec guardrail**: PASS. Specs 328, 330, 335, 352, 367, 369, and 370 are context only and must not be modified.
- **Roadmap fit**: PASS. Backup/restore trust and operator productization are active product goals.
- **Smallest viable slice**: PASS. Backup Set list/detail only, with browser proof.
- **Deferred adjacent work**: customer/auditor surfaces, diagnostics, provider readiness, system panel, UI bloat guard, and completed-surface refreshes.
- **Gate result**: PASS.
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed existing operator-facing backup/restore surfaces.
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**:
- `/admin/workspaces/{workspace}/environments/{environment}/backup-sets`
- `/admin/workspaces/{workspace}/environments/{environment}/backup-sets/{record}`
- `BackupSetResource`, `ListBackupSets`, `ViewBackupSet`, `BackupItemsRelationManager`
- **No-impact class**: N/A.
- **Native vs custom classification summary**: Native Filament resource/table/infolist/action patterns first; optional small page-local helper only if it reduces review risk.
- **Shared-family relevance**: status messaging, backup quality/readiness, action links, OperationRun links, restore-adjacent safety copy, evidence/diagnostics disclosure.
- **State layers in scope**: route scope, page/detail derived display state, table/relation state.
- **Audience modes in scope**: operator-MSP, workspace manager, readonly reviewer, support reviewer.
- **Decision/diagnostic/raw hierarchy plan**: backup usability and included items first; operation context, timestamps, raw IDs, provider internals, raw payloads, and technical counters secondary/collapsed.
- **Raw/support gating plan**: raw/support detail collapsed, secondary, or capability-gated by default.
- **One-primary-action / duplicate-truth control**: one dominant action such as Review backup items, Open backup set, Open operation, or another already-existing safe action. No new restore-from-backup action/link is planned.
- **Handling modes by drift class or surface**: backup-surface changes are in scope; changes to completed context surfaces are hard-stop unless spec/plan are updated.
- **Repository-signal treatment**: Spec 368 backup-set finding is implementation input; UI-013/UI-052 fixture gaps are review-mandatory.
- **Special surface test profiles**: backup/restore strategic surface and shared-detail-family.
- **Required tests or manual smoke**: focused Feature/Livewire tests plus Browser smoke with screenshots.
- **Exception path and spread control**: any fixture expansion beyond Backup Set proof must be documented in `validation-report.md` and may require follow-up.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage.
- **Coverage artifacts to update**: `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md` and coverage matrix/route inventory if status changes.
- **Navigation / Filament provider-panel handling**: no provider registration or navigation changes expected. Laravel 12 panel providers remain in `apps/platform/bootstrap/providers.php`.
- **Screenshot or page-report need**: before screenshot from Spec 368, after screenshots under this package.
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes.
- **Systems touched**:
- `apps/platform/app/Filament/Resources/BackupSetResource.php`
- `apps/platform/app/Filament/Resources/BackupSetResource/Pages/ListBackupSets.php`
- `apps/platform/app/Filament/Resources/BackupSetResource/Pages/ViewBackupSet.php`
- `apps/platform/app/Filament/Resources/BackupSetResource/RelationManagers/BackupItemsRelationManager.php`
- existing backup quality and badge helpers
- existing OperationRun link helpers
- existing backup/restore action tests
- UI audit registry/page report
- **Shared abstractions reused**: existing backup quality summary, badges, `UiEnforcement`/`WorkspaceUiEnforcement`, OperationRun link/presenter helpers.
- **New abstraction introduced? why?**: none expected. If page logic becomes hard to review, add one derived backup-local helper/presenter and justify it in the implementation close-out.
- **Why existing abstractions are sufficient**: backup quality, item inventory, operation links, and action safety already exist. The gap is display hierarchy and browser proof.
- **Bounded deviation / spread control**: no global component, presenter framework, status taxonomy, or persisted backup readiness truth.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: existing backup operation links and existing backup actions only.
- **Central contract reused**: existing OperationRun links/presenter/browser event paths.
- **Delegated UX behaviors**: queued/open operation/dedupe feedback remain delegated to existing action implementations.
- **Surface-owned behavior kept local**: backup usability explanation and item inventory hierarchy.
- **Queued DB-notification policy**: unchanged.
- **Terminal notification path**: unchanged.
- **Exception path**: none expected.
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: display only.
- **Provider-owned seams**: raw provider IDs, Graph payloads, provider-specific policy metadata, provider error detail.
- **Platform-core seams**: backup set, backup item, restore point, operation, evidence, diagnostics, managed environment.
- **Neutral platform terms preserved**: yes.
- **Retained provider-specific semantics and why**: captured policy/provider labels may remain item-level truth; raw/provider internals are not default primary copy.
- **Bounded extraction or follow-up path**: provider payload disclosure issues become document-in-feature or follow-up-spec.
## Constitution Check
- Inventory-first / snapshots-second: PASS. Existing backup snapshot truth is preserved.
- Read/write separation: PASS. UI display changes only; existing write actions keep preview/confirmation/audit where applicable.
- Graph contract path: PASS / N/A. No Graph calls.
- Deterministic capabilities: PASS. Existing capability/UI enforcement remains authoritative.
- RBAC-UX and isolation: PASS. Route-scoped workspace/environment checks must remain tested.
- OperationRun observability: PASS. Existing links/feedback preserved; no lifecycle change.
- Evidence/result truth separation: PASS. Backup usability, operation trace, restore proof, and diagnostics stay separated.
- Test governance: PASS. Feature and Browser lanes are explicit and narrow.
- Proportionality: PASS. No new persistence/status/framework; optional local helper only if necessary.
- UI/Productization coverage: PASS. Existing page changed and coverage update required.
- Shared pattern first: PASS. Reuse existing helpers and Spec 370 contract.
- Provider boundary: PASS. Provider-specific detail remains technical/support detail.
- Filament v5 / Livewire v4: PASS. Runtime work must use Filament v5 and Livewire v4 APIs only.
## Project Structure
```text
specs/371-core-operator-view-surfaces-productization/
|-- spec.md
|-- plan.md
|-- tasks.md
|-- checklists/
| `-- requirements.md
`-- artifacts/
|-- source-audit-summary.md
|-- affected-files.md
|-- before-after-screenshot-index.md
|-- page-contracts.md
|-- implementation-notes.md
|-- browser-verification-report.md
|-- validation-report.md
`-- screenshots/
```
Runtime implementation is expected under existing backup-surface files only unless the spec is updated.
## Implementation Phases
### Phase 0 - Repo Truth And Scope Lock
- Confirm branch, working tree, source artifacts, current backup-set implementation, current action safety, and completed-spec guardrails.
- Update `source-audit-summary.md`, `affected-files.md`, and `page-contracts.md` before runtime edits.
### Phase 1 - Test First
- Add focused Feature/Livewire tests for Backup Set detail usability states, metadata demotion, item inventory priority, RBAC/scope, and dangerous-action preservation.
- Add list tests for scan-first quality/item/degradation presentation and empty state.
- Add or update browser smoke fixture/test for Backup Sets list/detail reachability and screenshot capture.
### Phase 2 - Backup Set Detail Productization
- Recompose `ViewBackupSet`/`BackupSetResource` detail content so the first viewport leads with usability, reason, impact, item count, current degradation/blocker, and one primary next action.
- Make included backup items primary content.
- Demote lifecycle/timing/operation context/raw technical metadata into secondary/collapsed/detail sections.
- Preserve all existing action handlers, resource-level capability checks, confirmations, audit behavior, notifications, and OperationRun links.
### Phase 3 - Backup Sets List Productization
- Ensure the list is scan-first: backup quality/usability, item count, degradation state, open/inspect path, and contextual operation link.
- Suppress zero/no-issue noise when healthy state is already clear.
- Keep empty state specific and capability-aware.
### Phase 4 - Browser Proof And UI Coverage
- Run bounded browser smoke and capture after screenshots.
- Update `before-after-screenshot-index.md`, `browser-verification-report.md`, and `validation-report.md`.
- Update UI audit coverage artifacts or record why route/archetype counts did not change.
### Phase 5 - Final Validation
- Run focused tests, browser smoke, Pint dirty, and `git diff --check`.
- Confirm no completed spec packages or out-of-scope runtime files were modified.
- Record final Livewire/Filament/global-search/destructive-action/asset/deploy/test impact in implementation close-out.
## Data Model
N/A. No tables, migrations, casts, indexes, JSONB changes, persisted readiness fields, or data backfills are planned.
## API / Contracts
N/A. No HTTP API, Graph contract, queue contract, package, external integration, or operation type changes are planned.
## UI / Filament Implications
- Filament v5 with Livewire v4.0+ remains required.
- No panel provider registration change is expected; Laravel 12 providers remain in `apps/platform/bootstrap/providers.php`.
- No global search enablement is expected. If `BackupSetResource` global-search metadata is touched, its effective non-participation must either be preserved by explicitly disabling global search or changed only with safe View/Edit pages, scoped URLs, `$recordTitleAttribute`, and tests.
- Existing destructive/high-impact actions must continue to use `Action::make(...)->action(...)`, `->requiresConfirmation()`, server-side authorization/capability checks, audit logging, and tests.
- No Filament assets are expected. If registered assets appear, deployment must run `cd apps/platform && php artisan filament:assets`.
- Tables must keep meaningful empty states and query-safe visible relationships.
## Test Strategy
- Feature/Livewire:
- `Spec371BackupSetProductizationTest` for detail/list hierarchy, RBAC/scope, raw metadata demotion, action safety, and item inventory.
- Existing `BackupSet*` tests remain green.
- Browser:
- `Spec371BackupSetProductizationSmokeTest` for list/detail reachability, no JS errors, first-viewport hierarchy, and screenshots.
- Validation commands:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec371`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=BackupSet`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec371BackupSetProductizationSmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail pint --dirty`
- `git diff --check`
## Rollout Considerations
- No env var changes.
- No migrations.
- No queue/scheduler/storage changes.
- No Graph permission changes.
- No Filament asset deployment step expected.
- Staging validation should include browser proof for Backup Sets list/detail because this is a strategic backup/restore trust surface.
## Risk Controls
- Do not edit completed context specs.
- Do not change restore execution or backup capture behavior.
- Do not create new backup/restore truth.
- Do not default-show raw payloads or provider/internal IDs.
- Do not make destructive/high-impact actions more prominent without confirmation/authorization/audit proof.
- Stop and update spec/plan if shared helper changes affect Environment Dashboard, Operations Hub, OperationRun, Restore Run, or Baseline Profile.
## Spec Readiness Gate
- `spec.md`, `plan.md`, `tasks.md`: required and present after preparation.
- `checklists/requirements.md`: required.
- Source and page-contract artifacts: required.
- Blocking open questions: none.
- Runtime implementation scope: bounded to Backup Set list/detail and backup-set browser proof.
- Gate result: PASS.

View File

@ -0,0 +1,330 @@
# Feature Specification: Spec 371 - Core Operator View Surfaces Productization Pass v1
**Feature Branch**: `371-core-operator-view-surfaces-productization`
**Created**: 2026-06-10
**Status**: Draft / Ready for implementation review
**Input**: User-provided Spec 371 draft plus Spec 368 Candidate B and Spec 370 Global Surface Information Architecture Contract v1.
## Candidate Selection Summary
- **Selected candidate**: Core Operator View Surfaces Productization Pass v1.
- **Source**: User-provided Spec 371 draft and `specs/368-platform-ui-signal-to-noise-browser-audit/spec-candidates.md` Candidate B.
- **Why selected**: Spec 368 identified Backup Set View as a remaining restore-critical operator surface where lifecycle, timing, related context, and technical detail still compete with backup usability and included-item truth. The user-provided Spec 371 draft explicitly asks to apply the Spec 370 decision-first contract to core operator surfaces.
- **Roadmap relationship**: Supports the roadmap's UI/product maturity polish, backup/restore safety, and decision-centered operator workflow lanes without opening new backend foundations.
- **Repo-truth adjustment**: Several surfaces named by the draft already have completed or validated productization specs. This package must not reopen those completed packages or reimplement their work. Spec 371 therefore focuses active implementation on Backup Set list/detail productization and capability-backed browser proof, while treating Environment Dashboard, Operations Hub, OperationRun View, Restore Run View, and Baseline Profile View as regression/context surfaces only.
- **Smallest viable implementation slice**: Productize the existing Backup Sets list and Backup Set detail surfaces so backup usability, included items, degradation/blocker state, and the safest next action are visible before lifecycle metadata, operation IDs, exact timestamps, or raw/technical context. Add/repair bounded browser proof only for the scoped backup surfaces.
- **Deferred close alternatives**:
- Full six-page productization pass: deferred because multiple pages already have completed specs and reopening them would violate the completed-spec guardrail.
- Environment Dashboard pass: completed by Specs 330 and 352; regression-only in this spec.
- Operations Hub pass: completed by Specs 328, 365, and 367; regression-only in this spec.
- OperationRun detail pass: completed by Specs 358-367; regression-only in this spec.
- Restore Run detail pass: completed by Spec 335; regression-only in this spec.
- Baseline Profile detail pass: completed by Spec 369; regression-only in this spec.
- Customer/auditor surface safety, diagnostic surface separation, UI bloat regression guard, provider connections, system panel, and required permissions remain follow-up candidates.
- **Completed-spec guardrail**: Related completed specs are context only and must not be edited: Specs 328, 330, 335, 352, 367, 369, and 370. Spec 368 is an audit/source artifact and must not be rewritten.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: Operators use backup sets to decide whether a captured restore point is usable and what it includes, but the current Backup Set detail can make lifecycle metadata, related operation context, and technical fields feel like peers of the actual restore decision.
- **Today's failure**: A backup can look record-complete while the operator still has to scan timing, status, operation link, quality text, and item sections to decide whether the backup is usable, degraded, or ready for guarded restore review.
- **User-visible improvement**: Backup Sets become a calm restore-point truth surface: usability/readiness first, included items second, degradations/blockers only when present, operation/evidence links reachable but secondary, and technical metadata on demand.
- **Smallest enterprise-capable version**: A page-local productization pass over existing `BackupSetResource`, `ViewBackupSet`, `ListBackupSets`, `BackupItemsRelationManager`, existing quality summary truth, and existing backup/restore actions. No new backup engine, restore engine, persisted readiness field, OperationRun type, Graph contract, or UI framework.
- **Explicit non-goals**: No restore workflow logic changes, no backup capture logic changes, no migrations, no models, no new tables, no Graph calls, no new operation types, no new restore eligibility truth, no customer/auditor surfaces, no system panel, no provider connections, no broad shell/sidebar/topbar work, and no changes to completed spec packages.
- **Permanent complexity imported**: Focused Feature/Livewire coverage, a bounded browser smoke fixture/report, optional page-local derived helper or presenter only if existing inline logic becomes hard to review, and spec-local artifacts. No new persisted truth or cross-domain UI framework is expected.
- **Why now**: Spec 370 created the reusable decision-first contract, and Spec 368's Backup Set finding remains the main restore-critical Candidate B surface not already covered by a completed productization spec.
- **Why not local**: A label-only change would not prove backup usability, included-item truth, action hierarchy, and metadata demotion together. A global pattern component would overbuild. The narrow correct slice is a repo-truth-bounded Backup Set productization pass with browser proof.
- **Approval class**: Workflow Compression.
- **Red flags triggered**: High-impact backup/restore-adjacent action surface and UI productization scope. Defense: the spec preserves existing resource-level capability checks, UI enforcement, confirmations, audit, OperationRun behavior, and restore safety language, while limiting active implementation to existing backup surfaces.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12**
- **Decision**: approve.
## Spec Scope Fields *(mandatory)*
- **Scope**: environment-owned backup/restore workflow surface.
- **Primary Routes**:
- `/admin/workspaces/{workspace}/environments/{environment}/backup-sets`
- `/admin/workspaces/{workspace}/environments/{environment}/backup-sets/{record}`
- **Regression Context Routes**:
- `/admin/workspaces/{workspace}/environments/{environment}`
- `/admin/workspaces/{workspace}/operations`
- `/admin/workspaces/{workspace}/operations/{run}`
- `/admin/workspaces/{workspace}/environments/{environment}/restore-runs/{record}`
- `/admin/baseline-profiles/{record}`
- **Data Ownership**: Existing backup sets and backup items remain environment-owned records under current workspace and managed environment scope. No data model change is planned.
- **RBAC**: Existing workspace membership, managed-environment entitlement, resource-level capability checks, `UiEnforcement`/`WorkspaceUiEnforcement`, and deny-as-not-found behavior remain authoritative. This spec does not add a `BackupSetPolicy`.
For environment-owned routes:
- **Default filter behavior when workspace context is active**: Backup Set list/detail must resolve from explicit workspace and managed-environment route context, not hidden remembered environment state.
- **Explicit entitlement checks preventing cross-tenant leakage**: Wrong workspace or inaccessible managed environment must remain denied as not found before backup-set content, operation links, or restore affordances are revealed.
## UI Surface Impact *(mandatory - UI-COV-001)*
Does this spec add, remove, rename, or materially change any reachable UI surface?
- [ ] No UI surface impact
- [x] Existing page changed
- [ ] New page/route added
- [ ] Navigation changed
- [ ] Filament panel/provider surface changed
- [ ] New modal/drawer/wizard/action added
- [x] Existing table/detail/relation display state materially changed
- [ ] Customer-facing surface changed
- [x] Dangerous action changed
- [x] Status/evidence/review presentation changed
- [x] Workspace/environment context presentation changed
## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact")*
- **Route/page/surface**: Backup Sets list and Backup Set detail under environment-owned routes; `BackupSetResource`, `ListBackupSets`, `ViewBackupSet`, and `BackupItemsRelationManager`.
- **Current or new page archetype**: Backup / Restore strategic surface, matching `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md`.
- **Design depth**: Strategic Surface.
- **Repo-truth level**: repo-verified route and code; browser-verified before-state from Spec 368 screenshot `artifacts/screenshots/admin/005-workflow-surface-view-backup-set.png`; UI audit registry currently notes fixture/capability gaps for UI-051/UI-052.
- **Existing pattern reused**: Spec 370 surface IA contract, existing backup quality summary, existing Backup Set badges, existing resource-level capability/UI enforcement, existing OperationRun links, and existing Restore Run safety language for copy alignment only.
- **New pattern required**: none beyond optional page-local derived summary/helper if needed to reduce repeated inline logic.
- **Screenshot required**: yes. Use before screenshot from Spec 368 and capture after screenshots under `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/`.
- **Page audit required**: yes, update `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md` and related coverage notes, or record an explicit no-count-change rationale if route/archetype remains unchanged.
- **Customer-safe review required**: no. This is operator/MSP restore-point truth, not a customer-facing surface.
- **Dangerous-action review required**: yes. Existing soft-delete restore of archived Backup Sets, archive/delete/force-delete, retry/capture-related actions, and add/remove policy actions must preserve existing confirmation, authorization, audit, and OperationRun behavior. This spec must not introduce or promote a new restore-from-backup action/link. No destructive action may be moved into a more prominent unsafe placement.
- **Coverage files in scope for update or explicit validation**:
- [x] `docs/ui-ux-enterprise-audit/route-inventory.md` - update only if route status/browser status changes.
- [x] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` - update if coverage status or screenshot references change.
- [x] `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md` - update after implementation/browser proof.
- [ ] `docs/ui-ux-enterprise-audit/strategic-surfaces.md`
- [ ] `docs/ui-ux-enterprise-audit/grouped-follow-up-candidates.md`
- [ ] `docs/ui-ux-enterprise-audit/unresolved-pages.md`
- [ ] `N/A - no reachable UI surface impact`
- **No-impact rationale when applicable**: N/A.
## Cross-Cutting / Shared Pattern Reuse
- **Cross-cutting feature?**: yes.
- **Interaction class(es)**: status messaging, backup quality/readiness, action links, OperationRun links, restore-adjacent safety copy, table/list empty state, evidence/diagnostics disclosure, technical metadata disclosure.
- **Systems touched**: `BackupSetResource`, `ViewBackupSet`, `ListBackupSets`, `BackupItemsRelationManager`, backup quality summary helpers, badge renderers, OperationRun links, resource-level capability/UI enforcement, browser fixture commands/tests if existing.
- **Existing pattern(s) to extend**: Spec 370 first-viewport contract; Spec 335 restore result proof separation; Spec 333 restore create safety flow; existing `BackupSetQualitySummary`; existing backup set badges and UI enforcement.
- **Shared contract / presenter / builder / renderer to reuse**: Existing Backup Set helpers first; `BadgeCatalog`/`BadgeRenderer` for status-like badges; `OperationRunLinks`/existing operation link helpers for run navigation; `UiEnforcement`/`WorkspaceUiEnforcement` for actions.
- **Why the existing shared path is sufficient or insufficient**: Existing paths appear sufficient for backup quality, item count, operation links, authorization, and action safety. The gap is hierarchy and fixture-backed browser proof, not missing backend truth.
- **Allowed deviation and why**: A small page-local derived summary/helper is allowed if it replaces scattered closure logic and remains derived-only.
- **Consistency impact**: Backup usability, restore-readiness copy, quality/degradation labels, operation links, and dangerous-action hierarchy must stay aligned with Restore Run, Restore Create, OperationRun, and audit language.
- **Review focus**: Verify no new restore eligibility truth, no false recovery/safety claims, no raw payload default visibility, no hidden cross-workspace leakage, and no action safety regression.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: link and presentation semantics only, unless existing backup actions already start operation runs.
- **Shared OperationRun UX contract/layer reused**: existing `OperationRunLinks`, `OperationUxPresenter`, and backup action OperationRun flows where already present.
- **Delegated start/completion UX behaviors**: preserve existing queued toast, `Open operation` link, already-running/dedupe feedback, browser event, and operation URL resolution.
- **Local surface-owned behavior that remains**: backup usability explanation, included-item hierarchy, and page-level action guidance.
- **Queued DB-notification policy**: unchanged / N/A.
- **Terminal notification path**: unchanged.
- **Exception required?**: none expected.
## Provider Boundary / Platform Core Check
- **Shared provider/platform boundary touched?**: no new provider seam.
- **Boundary classification**: Backup Set UI remains platform-owned restore-point truth over provider-backed captured payloads.
- **Seams affected**: display of backup content, source environment, captured item types, provider payload references, and technical diagnostics.
- **Neutral platform terms preserved or introduced**: workspace, managed environment, backup set, backup item, restore point, operation, evidence, diagnostics, degradation, usable, action needed.
- **Provider-specific semantics retained and why**: Microsoft/Intune policy type labels may remain where they describe the captured item. Raw provider IDs, Graph payloads, and provider internals are technical/support detail and must not become default primary copy.
- **Why this does not deepen provider coupling accidentally**: No Graph calls, contracts, provider adapters, persisted taxonomy, or provider-specific backup logic are introduced.
- **Follow-up path**: document-in-feature for contained copy/display exceptions; follow-up-spec if a recurring provider payload disclosure issue is found.
## UI / Surface Guardrail Impact
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Backup Sets list decision summary | yes | Native Filament resource/table | status messaging, backup quality, action links | page, table, route scope | no | Existing route and resource only |
| Backup Set detail first viewport | yes | Native Filament infolist/page, optional page-local helper | restore-point truth, evidence/diagnostics, action links | page, detail, derived display | no | Active implementation focus |
| Backup items relation/table hierarchy | yes | Native RelationManager/table | included-item inventory | relation table | no | Must keep item inventory primary |
| Backup technical metadata demotion | yes | Native sections/details/aside where available | diagnostics/progressive disclosure | detail display | no | Metadata remains accessible |
| Completed surfaces regression context | no active refactor | Existing implemented patterns | status/evidence/action hierarchy | none unless regression found | no | Do not reopen completed specs |
## Decision-First Surface Role
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|---|---|---|---|---|---|---|---|
| Backup Sets list | Secondary decision queue | Operator chooses which restore point to inspect or act on | name, usability/quality, item count, current degradation, latest operation link, safe inspect path | detailed items, operation context, raw metadata | Secondary because final trust decision happens on detail | backup/restore review | reduces table scanning before selecting a backup |
| Backup Set detail | Primary Decision Surface | Operator decides whether backup is usable and what to do next | usability/readiness, reason, impact, item count, degradation/blocker if present, one primary next action | operation trace, exact timestamps, raw IDs, source context, technical quality counters | Primary restore-point truth surface | backup review before restore | puts restore usability ahead of metadata |
| Backup items relation/table | Evidence / inventory surface | Operator verifies what is included | included items, item quality/problem state | raw payload details and provider identifiers | Primary content below summary, not metadata | restore preparation and audit | avoids separate reconstruction of contents |
## Audience-Aware Disclosure
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|---|---|---|---|---|---|---|---|
| Backup Sets list | operator-MSP, manager, readonly reviewer | backup usability, item count, degradation state, inspect/open action | operation link and compact quality detail | raw payloads not shown | open backup set | raw IDs/provider payloads/exact timestamps | one quality state per row |
| Backup Set detail | operator-MSP, support reviewer | usable/action needed state, reason, impact, item inventory, degradation/blocker | operation context, source schedule, retention/timing | raw backup payload, provider IDs, technical counters | review items or open the source operation; no new restore-start action | raw/support detail collapsed or secondary | top summary owns current decision; later sections add proof/detail |
## UI/UX Surface Classification
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Backup Sets list | List / Table / Workflow | Backup / Restore list | Open a backup set or create backup set | record URL / explicit view | expected if current resource supports it | More/detail header | More or detail header only | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets` | `/admin/workspaces/{workspace}/environments/{environment}/backup-sets/{record}` | workspace and environment route | Backup set | usability/quality, item count, degradation, latest operation | none |
| Backup Set detail | Detail / Header Actions | Restore-point detail | Review included items or open the source operation | detail page | N/A | detail header or related context | separated, confirmed, authorized, audited | same as above | same as above | workspace, environment, source operation | Backup set | usable/action needed, reason, impact, included items, current degradation | none |
## Operator Surface Contract
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| Backup Set detail | Workspace manager / backup operator | Decide whether a backup can be trusted for restore review | Backup / restore detail | Is this backup usable, and what is included? | usability, reason, impact, captured item count, item inventory, current degradation/blocker, safe next action | raw payloads, exact timestamps, source operation context, provider/internal IDs | lifecycle, backup quality, item completeness, operation availability | TenantPilot backup/restore workflows; Microsoft mutation only through existing restore flow outside this spec | Review backup items, Open operation | soft-delete restore of archived Backup Sets, archive, delete/force-delete, add/remove items if present |
## Proportionality Review
- **New source of truth?**: no.
- **New persisted entity/table/artifact?**: no runtime persistence. Spec-local Markdown artifacts and screenshots only.
- **New abstraction?**: no expected runtime abstraction; optional page-local derived helper only if it replaces scattered page logic.
- **New enum/state/reason family?**: no.
- **New cross-domain UI framework/taxonomy?**: no. This consumes Spec 370 and existing UI standards.
- **Current operator problem**: Backup usability and included-item truth do not dominate the restore-point detail page.
- **Existing structure is insufficient because**: Existing resource/table/detail paths contain the needed truth, but the current hierarchy can still put lifecycle and technical metadata near the main decision.
- **Narrowest correct implementation**: Recompose existing Backup Set list/detail output around a derived decision summary and item inventory; demote metadata; add focused tests and browser proof.
- **Ownership cost**: Focused Feature/Livewire tests, one bounded browser smoke/report, and UI audit coverage update.
- **Alternative intentionally rejected**: A global component framework, new backup readiness persistence, or reimplementation of already-completed Environment Dashboard/Operations/Restore/Baseline surfaces.
- **Release truth**: current-release operator productization.
### Compatibility posture
This feature assumes pre-production. No legacy aliases, migration shims, backfills, historical fixture migration, or dual-read compatibility path is required unless the spec is amended.
## Testing / Lane / Runtime Impact
- **Test purpose / classification**: Feature/Livewire for Backup Set list/detail rendering, action affordances, RBAC/scope; Browser for bounded backup-set first-viewport and screenshot proof.
- **Validation lane(s)**: fast-feedback/confidence for focused Pest tests; browser for backup-set smoke; `git diff --check`; Pint for touched PHP.
- **Why this classification and these lanes are sufficient**: The change is a Filament resource/detail hierarchy and action-safety pass. Feature tests prove server-rendered truth/action behavior; browser smoke proves the first-viewport signal-to-noise improvement and fixture reachability.
- **New or expanded test families**: `Spec371BackupSetProductizationTest` and `Spec371BackupSetProductizationSmokeTest`.
- **Fixture / helper cost impact**: Reuse existing workspace/environment/backup-set factories and browser harness if available. If no suitable browser fixture exists, add the smallest capability-backed fixture only for backup-set review and document the cost.
- **Heavy-family visibility / justification**: Browser coverage is explicit and bounded to Backup Sets list/detail because the source finding is browser-verified UI signal-to-noise and the UI audit registry currently lists backup-set fixture gaps.
- **Special surface test profile**: backup/restore strategic surface, shared-detail-family.
- **Standard-native relief or required special coverage**: Native Filament components should carry layout; special coverage is required for decision-first default visibility, metadata demotion, and dangerous-action preservation.
- **Reviewer handoff**: Reviewers must confirm no new backup/restore truth, no authorization regression, no technical detail removal, no raw provider payload in primary content, and no completed spec package edits.
- **Budget / baseline / trend impact**: none expected. Record any browser fixture broadening or new seed cost in the implementation close-out.
- **Escalation needed**: document-in-feature if backup-set fixture cost or UI coverage registry updates are contained; follow-up-spec if browser fixture gaps are structural across backup/restore.
- **Active feature PR close-out entry**: Guardrail / Exception / Smoke Coverage.
- **Planned validation commands**:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec371`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=BackupSet`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec371BackupSetProductizationSmokeTest.php --compact`
- `cd apps/platform && ./vendor/bin/sail pint --dirty`
- `git diff --check`
- **Runtime impact**: UI rendering and browser fixture/test coverage only; no env vars, migrations, queues, scheduler, storage, Graph calls, or Filament asset registration expected.
## User Stories & Testing
### User Story 1 - Decide backup usability quickly (Priority: P1)
As a backup operator, I want the Backup Set detail page to tell me whether the backup is usable and what it includes before lifecycle or technical metadata, so I can decide whether to inspect items or open the source operation before any separate restore workflow.
**Independent Test**: Render backup sets with healthy, degraded, and empty/incomplete item states and assert the first decision section contains usability/readiness, reason, impact, item count, current degradation/blocker if present, and one dominant next action before exact timestamps, operation ID, raw payload, or provider metadata.
**Acceptance Scenarios**:
1. **Given** a completed backup set with no degradations and backup items, **When** the detail page renders, **Then** the first viewport says the backup is usable, names item count/content, and offers review/open action before technical metadata.
2. **Given** a backup set with degraded or failed items, **When** the detail page renders, **Then** current degradation is prominent and the safest next action is review/investigation rather than optimistic restore copy.
3. **Given** a backup set with no items or incomplete capture, **When** the detail page renders, **Then** it explains why restore confidence is blocked without exposing raw payload or operation context as the primary answer.
### User Story 2 - Keep dangerous backup/restore actions safe (Priority: P1)
As a reviewer, I want existing soft-delete restore, archive/delete, and backup item mutations to keep existing confirmation, authorization, audit, and OperationRun behavior while the page hierarchy changes, so productization does not weaken governance safety.
**Independent Test**: Use Filament action tests to prove authorized actors see allowed actions, readonly or wrong-scope actors cannot execute them, destructive/high-impact actions remain confirmation-gated, and existing OperationRun/open-operation feedback remains unchanged.
**Acceptance Scenarios**:
1. **Given** a readonly or unauthorized workspace member, **When** the Backup Set list/detail renders, **Then** mutating soft-delete restore/archive/add/remove actions are hidden or disabled consistently with current resource-level capability/UI enforcement.
2. **Given** an authorized actor starts an existing high-impact backup/restore-related action, **When** the action succeeds or dedupes, **Then** existing OperationRun feedback and canonical operation links remain intact.
3. **Given** a destructive backup action remains available, **When** tests inspect the action, **Then** it still uses `->action(...)`, `->requiresConfirmation()`, server-side authorization, audit behavior, and safe placement.
### User Story 3 - Make Backup Sets list scan-first (Priority: P2)
As an operator scanning backup history, I want the Backup Sets list to surface usability/quality, item count, degradation, and the inspect path, so I can choose a backup without reading lifecycle metadata first.
**Independent Test**: Render the Backup Sets list with healthy and degraded rows and assert the visible columns/copy prioritize backup usability and included-item information while exact technical metadata remains secondary or hidden by default.
**Acceptance Scenarios**:
1. **Given** the list contains healthy and degraded backups, **When** it renders, **Then** rows distinguish usable versus action-needed backups without multiple zero/no-issue cards.
2. **Given** no backup sets exist, **When** the list renders, **Then** the empty state explains how to create or capture a backup in one clear CTA if authorized.
3. **Given** a backup row has an operation link, **When** the row renders, **Then** the operation remains reachable but does not compete with the inspect/open backup path.
### User Story 4 - Prove browser reachability and no regression on completed surfaces (Priority: P3)
As a product reviewer, I want the implementation to capture backup-set before/after proof and spot-check completed context surfaces without editing completed packages, so the pass improves the open gap without undoing finished work.
**Independent Test**: Run a bounded browser smoke for Backup Sets list/detail and record after screenshots. Spot-check completed surfaces only for obvious regression if the implementation touches shared helpers that affect them.
**Acceptance Scenarios**:
1. **Given** the backup-set browser fixture exists or is added narrowly, **When** the smoke test runs, **Then** it captures list/detail screenshots and records no JavaScript errors.
2. **Given** implementation touches only backup-specific files, **When** regression scope is reviewed, **Then** Environment Dashboard, Operations Hub, OperationRun, Restore Run, and Baseline Profile are not edited.
3. **Given** a shared helper unexpectedly affects a completed surface, **When** that is discovered, **Then** implementation stops to update spec/plan or narrows the change.
## Functional Requirements
- **FR-001**: Backup Set detail MUST render a first-read decision summary above lifecycle metadata and technical context.
- **FR-002**: The summary MUST state backup usability/readiness, reason, impact, captured item count, current degradation/blocker only if present, and one dominant next action when a safe action exists.
- **FR-003**: The detail page MUST make included backup items primary content, not an afterthought behind metadata.
- **FR-004**: The list page MUST expose scan-first backup usability/quality and item count without zero-card spam.
- **FR-005**: Technical metadata, internal IDs, source operation context, exact timestamps, provider IDs, raw payloads, and quality counters MUST be sidebar, secondary, hidden, or collapsed by default unless they are the current blocker.
- **FR-006**: Existing soft-delete restore, archive/delete, add/remove, and operation-link affordances MUST preserve current authorization, confirmation, audit, and OperationRun semantics; no new restore-from-backup action/link is introduced by this spec.
- **FR-007**: The implementation MUST NOT create a new backup readiness field, restore eligibility field, status family, persisted artifact, Graph contract, or OperationRun type.
- **FR-008**: Wrong-workspace or wrong-environment access MUST remain denied as not found before content or action state leaks.
- **FR-009**: Browser proof MUST capture Backup Sets list/detail after screenshots or document the exact fixture/reachability blocker.
- **FR-010**: Completed context specs MUST not be edited or converted back into preparation state.
- **FR-011**: If `BackupSetResource` global-search metadata is touched, its effective non-participation MUST either be preserved by explicitly disabling global search or changed only with safe View/Edit pages, scoped URLs, `$recordTitleAttribute`, and tests; no global search enablement is expected.
- **FR-012**: UI audit registry artifacts MUST be updated or explicitly marked unchanged with a rationale after implementation.
## Non-Functional Requirements
- **NFR-001**: Keep implementation page-local to backup surfaces unless a small existing shared helper is the narrowest correct place.
- **NFR-002**: No Graph calls may occur during page render.
- **NFR-003**: Use Filament v5 and Livewire v4-compatible components and tests; no Livewire v3 or Filament v3/v4 APIs.
- **NFR-004**: Panel provider registration remains in `apps/platform/bootstrap/providers.php`; no provider registration change is expected.
- **NFR-005**: No Filament asset registration is expected; if assets are unexpectedly registered, deployment must include `cd apps/platform && php artisan filament:assets`.
- **NFR-006**: Preserve calm enterprise operator UX: one dominant decision, one primary action, diagnostics/evidence reachable but separated, no false positive calmness.
## Out Of Scope
Environment Dashboard refactor, Operations Hub refactor, OperationRun actionability or detail refactor, Restore Run detail refactor, Baseline Profile detail refactor, Customer Review Workspace, Environment Review, Review Pack, Stored Report, Evidence Snapshot, Required Permissions, System Panel, Provider Connections, Environment Diagnostics, restore execution logic, backup capture logic, baseline compare engine, migrations, models, jobs, policies, Graph contracts, package changes, route additions, and shell/sidebar/topbar work.
## Acceptance Criteria
- **AC-001**: Backup Set detail first viewport answers "Is this backup usable, what is included, and what should I do next?" before technical metadata.
- **AC-002**: Backup Set list rows prioritize usability/quality, included item count, degradation if present, and inspect/open path.
- **AC-003**: Degraded/incomplete backups show action-needed truth prominently; healthy backups avoid repeated zero/no-issue blocks.
- **AC-004**: Existing destructive/high-impact actions remain confirmed, authorized, audited, and safely placed.
- **AC-005**: Existing OperationRun links/feedback remain canonical and scope-safe.
- **AC-006**: Focused Pest/Livewire tests and bounded browser smoke pass, or non-run reasons are documented.
- **AC-007**: UI audit/page-report coverage is updated or explicitly marked unchanged.
- **AC-008**: No application code outside the approved backup-surface scope changes unless the spec/plan are updated first.
## Success Criteria
- Backup Set View reaches a browser-verified score target of at least 4.0 using the Spec 368 score model, or the remaining gap is documented with a follow-up.
- Backup Sets list remains at least 4.0 and no longer requires operators to infer backup usability from lifecycle or operation metadata.
- No completed context surface is edited unless a shared-helper regression forces a documented spec/plan update.
- No new runtime persistence, status family, provider contract, or broad UI framework is introduced.
## Assumptions
- Existing backup quality and item inventory truth are sufficient to derive usability/readiness copy.
- Existing resource-level capability checks and UI enforcement already govern backup and restore-adjacent actions; this spec preserves and tests them rather than replacing them.
- A bounded backup-set browser fixture can be reused or added without broad seed/default test cost.
- The repo is pre-production, so no migration compatibility work is needed.
## Risks
- Backup-set browser fixture work may reveal broader capability/auth gaps. If structural, escalate to follow-up rather than hiding it inside this spec.
- Page-local helper extraction could drift into a backup UI framework. Keep any helper derived, local, and justified by readability.
- Restore-adjacent copy could overclaim safety. Use only repo-backed backup quality and existing action availability truth.
- Touching shared badge/action helpers could affect completed surfaces. Prefer backup-local changes and stop to update spec/plan if shared changes become necessary.
## Open Questions
- None blocking preparation. During implementation, verify whether existing browser fixture coverage can reach Backup Sets list/detail without broad seed changes.
## Follow-up Spec Candidates
- Customer/Auditor Surface Safety Pass v1.
- Diagnostic Surface Separation v1.
- UI Bloat Regression Guard v1 after at least two runtime consumers validate Spec 370.
- Backup/restore browser fixture hardening if fixture gaps affect more than Backup Sets.
- Provider Connections readiness productization.
- System Panel audit fixture and productization pass.

View File

@ -0,0 +1,111 @@
# Tasks: Spec 371 - Core Operator View Surfaces Productization Pass v1
**Input**: `specs/371-core-operator-view-surfaces-productization/spec.md`, `plan.md`, and artifacts.
**Prerequisites**: Review completed context specs as read-only: 328, 330, 335, 352, 367, 369, 370. Do not edit them.
**Tests**: Focused Feature/Livewire tests for Backup Set list/detail and bounded Browser smoke for Backup Set list/detail screenshots.
## Phase 1: Preparation Guardrails
- [x] T001 Re-read `spec.md`, `plan.md`, `tasks.md`, `checklists/requirements.md`, `.specify/memory/constitution.md`, `docs/ai-coding-rules.md`, `docs/architecture-guidelines.md`, `docs/filament-guidelines.md`, `docs/security-guidelines.md`, `docs/testing-guidelines.md`, and `docs/performance-guidelines.md`.
- [x] T002 Confirm current branch and working tree with `git status --short --branch`; do not continue over unrelated dirty files.
- [x] T003 Confirm completed context specs are read-only: `specs/328-*`, `specs/330-*`, `specs/335-*`, `specs/352-*`, `specs/367-*`, `specs/369-*`, and `specs/370-*`.
- [x] T004 Inspect Spec 368 backup-set source evidence in `audit.md`, `findings.md`, `page-scorecard.csv`, `spec-candidates.md`, `artifacts/raw/browser-captures.json`, and screenshot `artifacts/screenshots/admin/005-workflow-surface-view-backup-set.png`.
- [x] T005 Inspect current backup-set UI implementation in `apps/platform/app/Filament/Resources/BackupSetResource.php`, `Pages/ListBackupSets.php`, `Pages/ViewBackupSet.php`, and `RelationManagers/BackupItemsRelationManager.php`.
- [x] T006 Inspect existing backup-set tests under `apps/platform/tests/Feature/Filament`, `apps/platform/tests/Feature/BackupSets`, `apps/platform/tests/Unit/Badges`, and relevant browser harnesses.
- [x] T007 Confirm no migration, package, env var, queue, scheduler, storage, Graph, panel-provider, or Filament asset change is required; update spec/plan before coding if false.
- [x] T008 Confirm Filament v5 / Livewire v4 compliance and no Livewire v3 or Filament v3/v4 APIs.
- [x] T009 Confirm panel provider registration remains `apps/platform/bootstrap/providers.php`.
- [x] T010 Confirm `BackupSetResource` global-search metadata is not enabled. If touched, explicitly preserve effective non-participation or update spec/plan/tests before enabling scoped search.
## Phase 2: Source Artifacts And Page Contracts
- [x] T011 Update `artifacts/source-audit-summary.md` if new repo truth is discovered.
- [x] T012 Update `artifacts/affected-files.md` with actual touched files before runtime edits.
- [x] T013 Update `artifacts/page-contracts.md` with final Backup Sets list/detail target hierarchy and regression-only status for completed context surfaces.
- [x] T014 Update `artifacts/before-after-screenshot-index.md` with expected screenshot targets before browser work.
- [x] T015 Keep `artifacts/implementation-notes.md`, `browser-verification-report.md`, and `validation-report.md` current during implementation.
## Phase 3: User Story 1 - Decide Backup Usability Quickly (P1)
**Goal**: Backup Set detail answers usability, reason, impact, item count, current degradation/blocker, and safest next action before technical metadata.
- [x] T016 [P] Add failing Feature/Livewire coverage for a healthy usable backup set in `apps/platform/tests/Feature/Filament/Spec371BackupSetProductizationTest.php`, with a fail-hard Graph client binding or equivalent assertion proving the render path does not call Graph.
- [x] T017 [P] Add failing Feature/Livewire coverage for a degraded backup set showing action-needed truth before optimistic restore copy.
- [x] T018 [P] Add failing Feature/Livewire coverage for an empty/incomplete backup set explaining the blocker without raw payload/default technical context.
- [x] T019 Recompose Backup Set detail in `BackupSetResource` / `ViewBackupSet` so the first visible section is a decision summary with usability/readiness, reason, impact, item count, degradation/blocker if present, and one primary next action that already exists safely in repo behavior.
- [x] T020 Make included backup items the primary main content below the summary, using existing `BackupItemsRelationManager` or existing item table structures.
- [x] T021 Demote lifecycle/timing/source operation/raw IDs/provider/internal metadata into secondary, sidebar, or collapsed technical details.
- [x] T022 Ensure no false restore safety, recovery proof, customer-safe, compliant, or protected claims appear unless current repo truth supports them.
## Phase 4: User Story 2 - Preserve Dangerous Action Safety (P1)
**Goal**: Existing soft-delete restore/archive/delete/add/remove/operation actions keep existing confirmation, authorization, audit, notification, and OperationRun behavior.
- [x] T023 [P] Add or extend tests asserting readonly/unauthorized users cannot execute backup-set mutating actions.
- [x] T024 [P] Add or extend tests asserting wrong-workspace/wrong-environment backup-set access remains deny-as-not-found before content/action leakage.
- [x] T025 [P] Add or extend tests asserting destructive/high-impact actions still use confirmation, server-side authorization, audit expectations, and safe placement.
- [x] T026 Keep existing action handlers on their current service/job/audit/OperationRun paths; do not move business logic into Filament closures.
- [x] T027 Do not introduce or promote a new restore-from-backup action/link from Backup Set list/detail; keep existing soft-delete restore and restore-adjacent copy state-aware, secondary, and repo-truth based.
- [x] T028 Keep operation links canonical and scope-safe through existing OperationRun link helpers.
## Phase 5: User Story 3 - Make Backup Sets List Scan-First (P2)
**Goal**: The Backup Sets list prioritizes backup usability/quality, item count, degradation, and inspect/open path.
- [x] T029 [P] Add Feature/Livewire coverage for list rows showing quality/usability, item count, degradation when present, and an inspect/open path, with no Graph render call if not already proven by T016.
- [x] T030 [P] Add Feature/Livewire coverage that healthy rows avoid repeated zero/no-issue/no-degradation noise.
- [x] T031 [P] Add Feature/Livewire coverage for a specific capability-aware empty state and one create/capture CTA when authorized.
- [x] T032 Update `ListBackupSets` / `BackupSetResource::table()` so visible columns and row/header actions match the scan-first contract.
- [x] T033 Ensure operation links remain reachable but do not compete with the primary open/inspect backup path.
- [x] T034 Verify visible relationship-backed columns are eager loaded or query-safe.
## Phase 6: User Story 4 - Browser Proof And Completed-Surface Regression Safety (P3)
**Goal**: Capture backup-set browser proof and avoid reopening completed surfaces.
- [x] T035 Inspect existing browser harness and fixture commands before adding any fixture; prefer reuse.
- [x] T036 Add bounded browser smoke in `apps/platform/tests/Browser/Spec371BackupSetProductizationSmokeTest.php` for Backup Sets list/detail.
- [x] T037 Capture after screenshots under `specs/371-core-operator-view-surfaces-productization/artifacts/screenshots/`, including list and detail at minimum.
- [x] T038 Update `artifacts/before-after-screenshot-index.md` with before/after mappings and blocked pages if any.
- [x] T039 Update `artifacts/browser-verification-report.md` with URLs, auth/fixture, screenshots, scores before/after, remaining issues, and blocked pages.
- [x] T040 Confirm completed context surfaces are not edited. If a shared helper change affects them, stop and update `spec.md`/`plan.md` before continuing.
## Phase 7: UI Audit Registry And Validation
- [x] T041 Update `docs/ui-ux-enterprise-audit/page-reports/ui-013-environment-backup-sets.md` with the implementation result and screenshot references.
- [x] T042 Update `docs/ui-ux-enterprise-audit/route-inventory.md`, `design-coverage-matrix.md`, `strategic-surfaces.md`, or `unresolved-pages.md` only if route/browser/coverage status changes; otherwise record a no-count-change rationale in `validation-report.md`.
- [x] T043 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec371`.
- [x] T044 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=BackupSet`.
- [x] T045 Run `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec371BackupSetProductizationSmokeTest.php --compact`.
- [x] T046 Run `cd apps/platform && ./vendor/bin/sail pint --dirty`.
- [x] T047 Run `git diff --check`.
- [x] T048 Update `artifacts/validation-report.md` with branch, HEAD, dirty state, commands, test/browser results, runtime files changed, limitations, and recommended follow-up.
- [x] T049 Confirm no application/runtime implementation happened during preparation, and during implementation no out-of-scope runtime files were changed without spec/plan update.
- [x] T050 Record final Livewire v4 compliance, provider registration location, global-search posture, destructive/high-impact action confirmation/authorization/audit status, asset strategy, tests, deployment impact, and Guardrail / Exception / Smoke Coverage in the implementation close-out response.
## Dependencies
- Phase 1 before runtime edits.
- Phase 2 before browser/report close-out.
- Phase 3 and Phase 4 before Phase 6 screenshots.
- Phase 7 after runtime changes.
## Parallel Execution Examples
- T004, T005, and T006 can run in parallel.
- T016, T017, T018, T023, T024, T025, T029, T030, and T031 can be drafted in parallel if the fixture shape is coordinated.
- T041 and T042 can run after screenshots and browser report are available.
## Test Governance Checklist
- [x] Lane assignment is named and is the narrowest sufficient proof for the changed behavior.
- [x] New or changed tests stay in the smallest honest family, and browser coverage is explicit.
- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default.
- [x] Planned validation commands cover the change without pulling in unrelated lane cost.
- [x] The declared surface test profile (`backup/restore strategic surface` / `shared-detail-family`) is explicit.
- [x] Any material budget, baseline, trend, or escalation note is recorded in the active spec or PR.
## Explicit Non-Goals
- No migrations, models, services/jobs beyond existing backup action paths, policies, routes, Graph contracts, provider adapters, new operation types, new restore-from-backup action/link from Backup Set list/detail, global UI framework, customer/auditor surfaces, system panel, provider connections, restore execution logic, backup capture logic, or completed-spec package edits unless the spec/plan is updated first.