feat(ui): implement baseline profile decision view
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 15s

Added a decision-first section to the Baseline Profile detail page. Includes request caching for summary metrics and corresponding browser/feature tests.
This commit is contained in:
Ahmed Darrazi 2026-06-10 13:58:51 +02:00
parent b15d325701
commit 4dbb0249bd
9 changed files with 1192 additions and 32 deletions

View File

@ -273,6 +273,69 @@ public static function infolist(Schema $schema): Schema
{
return $schema
->schema([
Section::make('Decision')
->schema([
TextEntry::make('baseline_decision_readiness')
->label('Readiness')
->badge()
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['readiness'])
->color(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['readiness_color'])
->icon(fn (BaselineProfile $record): ?string => self::baselineDecisionSummary($record)['readiness_icon']),
TextEntry::make('baseline_decision_reason')
->label('Decision reason')
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['reason'])
->columnSpanFull(),
TextEntry::make('baseline_decision_impact')
->label('Operational impact')
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['impact'])
->columnSpanFull(),
TextEntry::make('baseline_decision_snapshot_basis')
->label('Snapshot basis')
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['snapshot_basis']),
TextEntry::make('baseline_decision_latest_attempt')
->label('Latest attempt')
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['latest_attempt']),
TextEntry::make('baseline_decision_assignments')
->label('Assignment signal')
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['assignment_signal'])
->columnSpanFull(),
TextEntry::make('baseline_decision_next_action')
->label('Dominant next action')
->state(fn (BaselineProfile $record): string => self::baselineDecisionSummary($record)['next_action'])
->columnSpanFull(),
])
->columns(2)
->columnSpanFull(),
Section::make('Baseline truth')
->schema([
TextEntry::make('current_snapshot_truth')
->label('Current snapshot')
->state(fn (BaselineProfile $record): string => self::currentSnapshotLabel($record)),
TextEntry::make('latest_attempted_snapshot_truth')
->label('Latest attempt')
->state(fn (BaselineProfile $record): string => self::latestAttemptedSnapshotLabel($record)),
TextEntry::make('compare_readiness')
->label('Compare readiness')
->badge()
->state(fn (BaselineProfile $record): string => self::compareReadinessLabel($record))
->color(fn (BaselineProfile $record): string => self::compareReadinessColor($record))
->icon(fn (BaselineProfile $record): ?string => self::compareReadinessIcon($record)),
TextEntry::make('baseline_next_step')
->label('Next step')
->state(fn (BaselineProfile $record): string => self::profileNextStep($record))
->columnSpanFull(),
])
->columns(2)
->columnSpanFull(),
Section::make('Related context')
->schema([
ViewEntry::make('related_context')
->label('')
->view('filament.infolists.entries.related-context')
->state(fn (BaselineProfile $record): array => self::detailRelatedContextEntries($record))
->columnSpanFull(),
])
->columnSpanFull(),
Section::make('Profile')
->schema([
TextEntry::make('name'),
@ -347,36 +410,6 @@ public static function infolist(Schema $schema): Schema
->placeholder('None'),
])
->columnSpanFull(),
Section::make('Baseline truth')
->schema([
TextEntry::make('current_snapshot_truth')
->label('Current snapshot')
->state(fn (BaselineProfile $record): string => self::currentSnapshotLabel($record)),
TextEntry::make('latest_attempted_snapshot_truth')
->label('Latest attempt')
->state(fn (BaselineProfile $record): string => self::latestAttemptedSnapshotLabel($record)),
TextEntry::make('compare_readiness')
->label('Compare readiness')
->badge()
->state(fn (BaselineProfile $record): string => self::compareReadinessLabel($record))
->color(fn (BaselineProfile $record): string => self::compareReadinessColor($record))
->icon(fn (BaselineProfile $record): ?string => self::compareReadinessIcon($record)),
TextEntry::make('baseline_next_step')
->label('Next step')
->state(fn (BaselineProfile $record): string => self::profileNextStep($record))
->columnSpanFull(),
])
->columns(2)
->columnSpanFull(),
Section::make('Related context')
->schema([
ViewEntry::make('related_context')
->label('')
->view('filament.infolists.entries.related-context')
->state(fn (BaselineProfile $record): array => self::detailRelatedContextEntries($record))
->columnSpanFull(),
])
->columnSpanFull(),
Section::make('Metadata')
->schema([
TextEntry::make('createdByUser.name')
@ -756,6 +789,202 @@ private static function archiveTableAction(?Workspace $workspace): Action
return $action;
}
/**
* @return array{
* readiness: string,
* readiness_color: string,
* readiness_icon: string|null,
* reason: string,
* impact: string,
* snapshot_basis: string,
* latest_attempt: string,
* assignment_signal: string,
* next_action: string,
* }
*/
private static function baselineDecisionSummary(BaselineProfile $profile): array
{
$cacheKey = self::profileRequestCacheKey($profile, 'decision-summary');
if (request()->attributes->has($cacheKey)) {
$cached = request()->attributes->get($cacheKey);
if (is_array($cached)) {
return $cached;
}
}
$reasonCode = self::compareAvailabilityReason($profile);
$summary = [
'readiness' => self::baselineDecisionReadiness($reasonCode),
'readiness_color' => self::baselineDecisionReadinessColor($reasonCode),
'readiness_icon' => self::baselineDecisionReadinessIcon($reasonCode),
'reason' => self::baselineDecisionReason($profile, $reasonCode),
'impact' => self::baselineDecisionImpact($reasonCode),
'snapshot_basis' => self::currentSnapshotLabel($profile),
'latest_attempt' => self::latestAttemptedSnapshotLabel($profile),
'assignment_signal' => self::assignmentSignal($profile),
'next_action' => self::baselineDecisionNextAction($profile, $reasonCode),
];
request()->attributes->set($cacheKey, $summary);
return $summary;
}
private static function baselineDecisionReadiness(?string $reasonCode): string
{
if ($reasonCode === null) {
return 'Ready to compare';
}
return match ($reasonCode) {
BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => 'Profile not active',
BaselineReasonCodes::COMPARE_INVALID_SCOPE,
BaselineReasonCodes::COMPARE_MIXED_SCOPE,
BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE => 'Scope review required',
BaselineReasonCodes::COMPARE_NO_ACTIVE_SNAPSHOT,
BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT,
BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING,
BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE,
BaselineReasonCodes::COMPARE_SNAPSHOT_SUPERSEDED,
BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT => 'Capture needed before compare',
BaselineReasonCodes::COMPARE_NO_ASSIGNMENT,
BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET => 'Assignment or access required',
default => 'Compare blocked',
};
}
private static function baselineDecisionReadinessColor(?string $reasonCode): string
{
return match ($reasonCode) {
null => 'success',
BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => 'gray',
BaselineReasonCodes::COMPARE_INVALID_SCOPE,
BaselineReasonCodes::COMPARE_MIXED_SCOPE,
BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE => 'danger',
default => 'warning',
};
}
private static function baselineDecisionReadinessIcon(?string $reasonCode): ?string
{
return match ($reasonCode) {
null => 'heroicon-m-check-badge',
BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => 'heroicon-m-pause-circle',
BaselineReasonCodes::COMPARE_INVALID_SCOPE,
BaselineReasonCodes::COMPARE_MIXED_SCOPE,
BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE => 'heroicon-m-no-symbol',
default => 'heroicon-m-exclamation-triangle',
};
}
private static function baselineDecisionReason(BaselineProfile $profile, ?string $reasonCode): string
{
if ($reasonCode === null) {
return 'Current snapshot is consumable and at least one assigned environment is available for compare.';
}
return match ($reasonCode) {
BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => 'Only active baseline profiles can be captured or compared.',
BaselineReasonCodes::COMPARE_INVALID_SCOPE => 'Stored governed-subject scope is invalid. Review the profile definition before compare.',
BaselineReasonCodes::COMPARE_MIXED_SCOPE => 'This profile mixes governed subjects that require different compare strategies. Narrow the selection before compare.',
BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE => 'This profile includes governed subjects that are not currently supported for compare.',
BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET => 'No assigned environment is available to this actor for compare.',
default => app(ReasonPresenter::class)->forArtifactTruth($reasonCode, 'artifact_truth')?->shortExplanation
?: 'Resolve the compare prerequisite before starting this baseline workflow.',
};
}
private static function baselineDecisionImpact(?string $reasonCode): string
{
if ($reasonCode === null) {
return 'Operators can compare assigned environments against the current baseline snapshot.';
}
return match ($reasonCode) {
BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE => 'Inactive baselines do not drive capture or compare until the profile is reactivated.',
BaselineReasonCodes::COMPARE_INVALID_SCOPE,
BaselineReasonCodes::COMPARE_MIXED_SCOPE,
BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE => 'Capture and compare are blocked until governed-subject scope is reviewed.',
BaselineReasonCodes::COMPARE_NO_ACTIVE_SNAPSHOT,
BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT,
BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING,
BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE,
BaselineReasonCodes::COMPARE_SNAPSHOT_SUPERSEDED,
BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT => 'Assignments can be prepared, but compare waits for a complete current snapshot.',
BaselineReasonCodes::COMPARE_NO_ASSIGNMENT,
BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET => 'Compare results stay unavailable until an assigned environment is visible to an authorized operator.',
default => 'Operator action is blocked until the listed prerequisite is resolved.',
};
}
private static function baselineDecisionNextAction(BaselineProfile $profile, ?string $reasonCode): string
{
if ($reasonCode === null) {
return 'Compare now';
}
if (in_array($reasonCode, [
BaselineReasonCodes::COMPARE_NO_ACTIVE_SNAPSHOT,
BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT,
BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING,
BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE,
BaselineReasonCodes::COMPARE_SNAPSHOT_SUPERSEDED,
BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT,
], true)) {
return self::hasManageCapability()
? 'Capture baseline'
: 'Ask a workspace manager to capture a baseline';
}
if (in_array($reasonCode, [
BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE,
BaselineReasonCodes::COMPARE_INVALID_SCOPE,
BaselineReasonCodes::COMPARE_MIXED_SCOPE,
BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE,
], true)) {
return self::hasManageCapability()
? 'Edit profile definition'
: 'Ask a workspace manager to review the profile';
}
if (in_array($reasonCode, [
BaselineReasonCodes::COMPARE_NO_ASSIGNMENT,
BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET,
], true)) {
return self::hasManageCapability()
? 'Assign environments or review access'
: 'Open compare matrix';
}
return app(ReasonPresenter::class)->forArtifactTruth($reasonCode, 'artifact_truth')?->guidanceText()
?? 'No action needed.';
}
private static function assignmentSignal(BaselineProfile $profile): string
{
$assignmentCount = BaselineTenantAssignment::query()
->where('workspace_id', (int) $profile->workspace_id)
->where('baseline_profile_id', (int) $profile->getKey())
->count();
if ($assignmentCount === 0) {
return 'No assigned environments. Assign a managed environment before compare results are useful.';
}
$label = $assignmentCount === 1
? 'Assigned to 1 environment'
: sprintf('Assigned to %d environments', $assignmentCount);
if (self::hasEligibleCompareTarget($profile)) {
return $label.' with compare access.';
}
return $label.', but no eligible compare target is available to this actor.';
}
private static function currentSnapshotLabel(BaselineProfile $profile): string
{
$snapshot = self::effectiveSnapshot($profile);
@ -902,31 +1131,49 @@ private static function latestBaselineCaptureEnvelope(BaselineProfile $profile):
private static function compareAvailabilityReason(BaselineProfile $profile): ?string
{
$cacheKey = self::profileRequestCacheKey($profile, 'compare-availability-reason');
if (request()->attributes->has($cacheKey)) {
$cached = request()->attributes->get($cacheKey);
return is_string($cached) ? $cached : null;
}
$status = $profile->status instanceof BaselineProfileStatus
? $profile->status
: BaselineProfileStatus::tryFrom((string) $profile->status);
if ($status !== BaselineProfileStatus::Active) {
request()->attributes->set($cacheKey, BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE);
return BaselineReasonCodes::COMPARE_PROFILE_NOT_ACTIVE;
}
try {
$scope = BaselineScope::fromJsonb(self::scopePayload($profile));
} catch (InvalidArgumentException) {
request()->attributes->set($cacheKey, BaselineReasonCodes::COMPARE_INVALID_SCOPE);
return BaselineReasonCodes::COMPARE_INVALID_SCOPE;
}
if ($scope->allTypes() === []) {
request()->attributes->set($cacheKey, BaselineReasonCodes::COMPARE_INVALID_SCOPE);
return BaselineReasonCodes::COMPARE_INVALID_SCOPE;
}
$selection = app(CompareStrategyRegistry::class)->select($scope);
if ($selection->isMixed()) {
request()->attributes->set($cacheKey, BaselineReasonCodes::COMPARE_MIXED_SCOPE);
return BaselineReasonCodes::COMPARE_MIXED_SCOPE;
}
if (! $selection->isSupported()) {
request()->attributes->set($cacheKey, BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE);
return BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE;
}
@ -934,13 +1181,19 @@ private static function compareAvailabilityReason(BaselineProfile $profile): ?st
$reasonCode = $resolution['reason_code'] ?? null;
if (is_string($reasonCode) && trim($reasonCode) !== '') {
request()->attributes->set($cacheKey, trim($reasonCode));
return trim($reasonCode);
}
if (! self::hasEligibleCompareTarget($profile)) {
request()->attributes->set($cacheKey, BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET);
return BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET;
}
request()->attributes->set($cacheKey, null);
return null;
}
@ -964,9 +1217,17 @@ private static function snapshotReference(BaselineSnapshot $snapshot): string
private static function hasEligibleCompareTarget(BaselineProfile $profile): bool
{
$cacheKey = self::profileRequestCacheKey($profile, 'eligible-compare-target');
if (request()->attributes->has($cacheKey)) {
return (bool) request()->attributes->get($cacheKey);
}
$user = auth()->user();
if (! $user instanceof User) {
request()->attributes->set($cacheKey, false);
return false;
}
@ -977,16 +1238,33 @@ private static function hasEligibleCompareTarget(BaselineProfile $profile): bool
->all();
if ($tenantIds === []) {
request()->attributes->set($cacheKey, false);
return false;
}
$resolver = app(CapabilityResolver::class);
return ManagedEnvironment::query()
$hasEligibleTarget = ManagedEnvironment::query()
->where('workspace_id', (int) $profile->workspace_id)
->whereIn('id', $tenantIds)
->get(['id'])
->contains(fn (ManagedEnvironment $tenant): bool => $resolver->can($user, $tenant, Capabilities::TENANT_SYNC));
request()->attributes->set($cacheKey, $hasEligibleTarget);
return $hasEligibleTarget;
}
private static function profileRequestCacheKey(BaselineProfile $profile, string $suffix): string
{
return sprintf(
'baseline-profile-resource:%s:%s:%s:%s',
$suffix,
(string) $profile->getKey(),
(string) (auth()->id() ?? 'guest'),
(string) (app(WorkspaceContext::class)->currentWorkspaceId(request()) ?? 'workspace-missing'),
);
}
/**

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
use App\Filament\Resources\BaselineProfileResource;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Workspaces\WorkspaceContext;
pest()->browser()->timeout(60_000);
function spec369BrowserScreenshotName(string $name): string
{
return 'spec369-baseline-profile-decision-view-'.$name;
}
function spec369CopyBrowserScreenshot(string $name): void
{
$filename = spec369BrowserScreenshotName($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/369-baseline-profile-decision-view/artifacts/screenshots');
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
if (! is_file($source)) {
$source = \Pest\Browser\Support\Screenshot::path($filename);
}
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
usleep(100_000);
clearstatcache(true, $source);
}
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
}
}
it('smokes the Baseline Profile decision-first detail view in Spec369', function (): void {
$this->withoutVite();
[$user, $tenant] = createUserWithTenant(role: 'owner');
[$profile] = seedActiveBaselineForTenant($tenant);
$profile->forceFill([
'name' => 'Spec369 Browser Decision Baseline',
'capture_mode' => BaselineCaptureMode::Opportunistic->value,
])->save();
$this->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$path = parse_url(
BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'),
PHP_URL_PATH,
);
$page = visit(is_string($path) ? $path : '/admin/baseline-profiles/'.$profile->getKey())
->waitForText('Decision')
->assertSee('Ready to compare')
->assertSee('Assignment signal')
->assertSee('Assigned to 1 environment with compare access.')
->assertSee('Dominant next action')
->assertSee('Compare now')
->assertSee('Related context')
->assertScript(
"(() => {
const text = document.body.innerText;
return text.indexOf('Decision') !== -1
&& text.indexOf('Decision') < text.indexOf('Normalization lineage')
&& text.indexOf('Decision') < text.indexOf('Metadata')
&& text.indexOf('Ready to compare') < text.indexOf('Scope');
})()",
true,
)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec369BrowserScreenshotName('decision-first-detail'));
spec369CopyBrowserScreenshot('decision-first-detail');
});

View File

@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
use App\Filament\Resources\BaselineProfileResource;
use App\Filament\Resources\BaselineProfileResource\Pages\ViewBaselineProfile;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineTenantAssignment;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
uses(RefreshDatabase::class);
/**
* @return array{0: \App\Models\User, 1: \App\Models\ManagedEnvironment, 2: BaselineProfile, 3: BaselineSnapshot}
*/
function spec369ReadyProfile(): array
{
[$user, $tenant] = createUserWithTenant(role: 'owner');
[$profile, $snapshot] = seedActiveBaselineForTenant($tenant);
$profile->forceFill([
'name' => 'Spec369 Ready Baseline',
'capture_mode' => BaselineCaptureMode::Opportunistic->value,
])->save();
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
return [$user, $tenant, $profile->fresh(), $snapshot->fresh()];
}
it('renders a ready baseline profile with a decision-first summary before technical scope', function (): void {
[$user, , $profile, $snapshot] = spec369ReadyProfile();
$this->actingAs($user)
->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
->assertOk()
->assertSee('Decision')
->assertSee('Ready to compare')
->assertSee('Current snapshot is consumable and at least one assigned environment is available for compare.')
->assertSee('Operators can compare assigned environments against the current baseline snapshot.')
->assertSee('Snapshot #'.$snapshot->getKey().' (Complete)')
->assertSee('Matches current snapshot')
->assertSee('Assigned to 1 environment with compare access.')
->assertSee('Dominant next action')
->assertSee('Compare now')
->assertSeeInOrder([
'Decision',
'Ready to compare',
'Baseline truth',
'Related context',
'Profile',
'Scope',
'Normalization lineage',
'Metadata',
]);
});
it('shows capture-oriented decision guidance when no consumable snapshot exists', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $tenant->workspace_id,
'name' => 'Spec369 Capture Needed Baseline',
]);
BaselineTenantAssignment::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'baseline_profile_id' => (int) $profile->getKey(),
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
$this->actingAs($user)
->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
->assertOk()
->assertSee('Decision')
->assertSee('Capture needed before compare')
->assertSee('Assignments can be prepared, but compare waits for a complete current snapshot.')
->assertSee('No complete snapshot')
->assertSee('No capture attempts yet')
->assertSee('Assigned to 1 environment with compare access.')
->assertSee('Capture baseline')
->assertSeeInOrder(['Decision', 'Capture needed before compare', 'Baseline truth', 'Scope']);
});
it('shows scope repair guidance for invalid governed-subject scope without exposing raw scope json', function (): void {
[$user, , $profile] = spec369ReadyProfile();
DB::table('baseline_profiles')
->where('id', (int) $profile->getKey())
->update([
'scope_jsonb' => json_encode([
'version' => 2,
'entries' => [],
], JSON_THROW_ON_ERROR),
'updated_at' => now(),
]);
$profile = $profile->fresh();
$this->actingAs($user)
->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
->assertOk()
->assertSee('Scope review required')
->assertSee('Stored governed-subject scope is invalid. Review the profile definition before compare.')
->assertSee('Capture and compare are blocked until governed-subject scope is reviewed.')
->assertSee('Edit profile definition')
->assertDontSee('subject_type_keys')
->assertDontSee('canonical_scope')
->assertSeeInOrder(['Decision', 'Scope review required', 'Scope', 'Stored scope is invalid']);
});
it('keeps readonly members on non-mutating decision copy and disabled high-impact actions', function (): void {
[$owner, $tenant, $profile] = spec369ReadyProfile();
[$readonly] = createUserWithTenant(tenant: $tenant, user: null, role: 'readonly', workspaceRole: 'readonly');
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
Livewire::actingAs($readonly)
->test(ViewBaselineProfile::class, ['record' => $profile->getKey()])
->assertSee('Assignment or access required')
->assertSee('No assigned environment is available to this actor for compare.')
->assertSee('Open compare matrix')
->assertActionVisible('capture')
->assertActionDisabled('capture')
->assertActionVisible('compareNow')
->assertActionDisabled('compareNow');
$this->actingAs($owner);
});
it('keeps related snapshot and compare matrix context available below the decision', function (): void {
[$user, , $profile] = spec369ReadyProfile();
$this->actingAs($user);
$entryKeys = collect(BaselineProfileResource::detailRelatedContextEntries($profile))
->pluck('key')
->all();
expect($entryKeys)->toContain('baseline_snapshot', 'compare_matrix');
$this->get(BaselineProfileResource::getUrl('view', ['record' => $profile], panel: 'admin'))
->assertOk()
->assertSee('Related context')
->assertSee('View snapshot')
->assertSee('Open compare matrix')
->assertSeeInOrder(['Decision', 'Related context', 'View snapshot', 'Compare matrix', 'Open compare matrix']);
});

View File

@ -23,6 +23,12 @@ ## Productization Review
- Customer/auditor safety: not directly customer-facing.
- Diagnostics: capture/compare evidence should be available on demand.
## Spec 369 Detail Update
The Baseline Profile detail page now opens with a Decision section before profile metadata or governed-subject normalization detail. The first read answers readiness, decision reason, operational impact, snapshot basis, assignment usefulness, and one dominant next action while keeping Baseline truth and Related context immediately below it.
Screenshot evidence: `../../specs/369-baseline-profile-decision-view/artifacts/screenshots/decision-first-detail.png`.
## Information Inventory
Default content should include profile status, capture mode, latest snapshot, assignment count, compare posture, and next action.
@ -40,7 +46,7 @@ ## Scores
## Top Issues
1. Generic resource feel hides the product role of baselines.
2. Snapshot truth and assigned-environment impact need stronger hierarchy.
2. Snapshot truth and assigned-environment impact need stronger hierarchy. Spec 369 improves this on the detail page.
3. Dangerous capture/compare actions need target-state review.
## Target Direction

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

View File

@ -0,0 +1,47 @@
# Specification Quality Checklist: Spec 369 - Baseline Profile Decision View
**Purpose**: Validate specification completeness and readiness before implementation
**Created**: 2026-06-09
**Feature**: `specs/369-baseline-profile-decision-view/spec.md`
## Candidate Selection Gate
- [x] CHK001 Selected candidate exists in repo source material (`specs/368-platform-ui-signal-to-noise-browser-audit`).
- [x] CHK002 Selected candidate is not already covered by an active or completed spec.
- [x] CHK003 Completed specs are treated as historical context only.
- [x] CHK004 Scope is the smallest viable slice: existing Baseline Profile detail page only.
- [x] CHK005 Close alternatives are deferred instead of hidden in scope.
- [x] CHK006 Candidate aligns with platform productization and governance operator value.
## Content Quality
- [x] CHK007 Spec has clear problem statement and user value.
- [x] CHK008 Spec includes primary users/operators and user stories.
- [x] CHK009 Functional and non-functional requirements are testable.
- [x] CHK010 Out-of-scope boundaries are explicit.
- [x] CHK011 Assumptions and risks are documented.
- [x] CHK012 No `[NEEDS CLARIFICATION]` markers remain.
## Constitution Alignment
- [x] CHK013 Workspace ownership and RBAC expectations are stated.
- [x] CHK014 Existing high-impact actions retain confirmation, authorization, audit, and OperationRun behavior.
- [x] CHK015 No new persisted truth, enum/status family, or broad abstraction is introduced.
- [x] CHK016 Provider/platform boundary impact is bounded and documented.
- [x] CHK017 UI/Productization Coverage is completed because a reachable UI surface changes.
- [x] CHK018 Test-governance lane classification is present.
## Spec Readiness Gate
- [x] CHK019 `spec.md`, `plan.md`, and `tasks.md` exist.
- [x] CHK020 Plan identifies likely affected repo surfaces and does not contradict current architecture.
- [x] CHK021 Tasks are ordered, small, verifiable, and include tests/validation.
- [x] CHK022 Open questions do not block safe implementation.
- [x] CHK023 Scope is bounded enough for a later implementation loop.
- [x] CHK024 Recommended next step is implementation, not additional preparation.
## Review Outcome
- **Candidate Selection Gate**: PASS.
- **Spec Readiness Gate**: PASS after preparation analysis.
- **Manual note**: Implementation must stop and update the spec first if it needs new persistence, a new readiness taxonomy, a global UI framework, or baseline engine changes.

View File

@ -0,0 +1,198 @@
# Implementation Plan: Spec 369 - Baseline Profile Decision View
**Branch**: `369-baseline-profile-decision-view` | **Date**: 2026-06-09 | **Spec**: `specs/369-baseline-profile-decision-view/spec.md`
**Input**: Feature specification from `specs/369-baseline-profile-decision-view/spec.md`
## Summary
Productize the existing Baseline Profile detail page into a decision-first governance surface. The implementation should keep the existing route, resource, actions, policies, OperationRun start UX, and baseline domain truth intact while changing first-read hierarchy: readiness, reason, impact, snapshot basis, assignment/usefulness, and one safe next action must appear before governed-subject normalization lineage and metadata.
## Technical Context
**Language/Version**: PHP 8.4.15
**Primary Dependencies**: Laravel 12.52, Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, PostgreSQL via Sail
**Storage**: PostgreSQL, no schema changes expected
**Testing**: Pest Feature/Livewire tests plus bounded Pest Browser smoke if implementation changes rendered layout
**Target Platform**: Laravel Sail local development, Dokploy container deployment later
**Project Type**: Laravel monolith, Filament admin panel
**Performance Constraints**: No new Graph calls or expensive queries during render; reuse existing eager loading and helper methods where possible
**Constraints**: Page-local UI/productization only; no baseline engine, OperationRun lifecycle, provider, shell, route, or migration changes
**Scale/Scope**: Existing Baseline Profile detail route and regression coverage only
## Candidate Selection Gate
- **Selected candidate exists in source material**: yes, Spec 368 finding `UI-AUDIT-368-F03` and Candidate B.
- **Not already covered by active/completed spec**: yes. Existing specs cover baseline engine/truth/compare (`116`, `159`, `336`) but not the Spec 368 browser-verified Baseline Profile detail signal-to-noise finding.
- **Completed-spec guardrail**: Related completed specs are context only and must not be edited.
- **Roadmap/product alignment**: yes. This supports the current platform productization direction and closes a high-value governance operator surface gap.
- **Small, reviewable slice**: yes. One existing detail page plus focused tests/smoke.
- **Deferred adjacent concerns**: Backup Set, OperationRun View, Provider Connections, Diagnostics, and global UI rule pack remain follow-ups.
- **Result**: PASS.
## Constitution Check
- **Inventory-first, snapshots-second**: PASS - the page continues to display existing Baseline Profile/Snapshot truth and does not fetch Graph data.
- **Read/write separation by default**: PASS - no new mutation is introduced; existing capture/compare high-impact actions retain confirmation and authorization.
- **Single Contract Path to Graph**: PASS - no Graph calls are introduced.
- **Deterministic Capabilities**: PASS - existing `WorkspaceUiEnforcement`, capability resolvers, and policies remain the action truth.
- **Proportionality / Anti-bloat**: PASS - no new persistence, status family, or cross-domain UI framework is planned.
- **Workspace isolation**: PASS - Baseline Profile remains workspace-owned and workspace-scoped.
- **Tenant isolation**: PASS - tenant/environment data appears only through existing assignments, snapshot links, and action targets.
- **RBAC-UX**: PASS - UI state remains non-authoritative; server-side action authorization remains required.
- **UI-COV-001**: PASS with required page-report/coverage update or explicit no-route-change rationale.
- **TEST-GOV-001**: PASS with focused Feature/Browser lane classification.
## Existing Repository Surfaces
- `apps/platform/app/Filament/Resources/BaselineProfileResource.php`
- Current infolist sections: Profile, Scope, Baseline truth, Related context, Metadata.
- Existing helper methods already expose scope summary, support readiness, current snapshot, latest attempt, compare readiness, and next step.
- `protected static bool $isGloballySearchable = false`; keep unchanged.
- `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php`
- Header actions: capture, compare now, More group with compare assigned environments and edit.
- Capture/compare use `->requiresConfirmation()`, shared OperationRun feedback, and workspace capability enforcement.
- Existing tests:
- `apps/platform/tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php`
- `apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php`
- `apps/platform/tests/Feature/Baselines/BaselineProfileAuthorizationTest.php`
- `apps/platform/tests/Browser/Spec192RecordPageHeaderDisciplineSmokeTest.php`
- `apps/platform/tests/Browser/Spec202GovernanceSubjectTaxonomySmokeTest.php`
- UI audit sources:
- `specs/368-platform-ui-signal-to-noise-browser-audit/*`
- `docs/ui-ux-enterprise-audit/page-reports/ui-010-baseline-profiles.md`
- `docs/ui-ux-enterprise-audit/route-inventory.md`
- `docs/ui-ux-enterprise-audit/design-coverage-matrix.md`
## Technical Approach
1. Verify the current Baseline Profile detail states that need to drive the decision summary:
- active with consumable snapshot
- active without consumable snapshot
- draft/inactive/archived
- invalid/unsupported/mixed governed-subject scope
- assigned and unassigned environments
- authorized and readonly actor states
2. Introduce the smallest derived decision summary shape:
- Prefer existing methods in `BaselineProfileResource`.
- Add a page-local helper only if repeated inline `TextEntry` closures become hard to review.
- Keep it derived-only and request-scoped.
3. Recompose the infolist:
- Add a decision-first section above `Profile`/`Scope`.
- Move normalization lineage and technical metadata below the decision and proof sections.
- Keep current snapshot/latest attempt/compare readiness and related context accessible.
4. Preserve action safety:
- Do not change capture/compare modal behavior except copy/hierarchy if required.
- Keep confirmation, capability checks, notifications, OperationRun links, and browser events.
5. Update focused tests and browser smoke:
- Assert decision content is visible and technical content is secondary.
- Assert readonly/owner action behavior remains stable.
- Capture a bounded browser smoke screenshot for the improved detail page.
6. Update UI audit coverage artifacts:
- Update page report/coverage note for UI-057 detail work.
- If route inventory counts are unchanged, document no route change rather than touching route count tables unnecessarily.
## Domain / Model Implications
- No model, migration, table, enum, persisted status, or source-of-truth change.
- Baseline readiness remains derived from existing Baseline Profile/Snapshot/Assignment/Compare truth.
- No data backfill or compatibility behavior is needed.
## UI / Filament Implications
- Filament v5 / Livewire v4.1.4 compliance is required.
- Panel providers remain in `apps/platform/bootstrap/providers.php`; no provider registration change.
- `BaselineProfileResource` global search stays disabled. It already has a View page and `$recordTitleAttribute`, but global search is intentionally off for this workspace-owned governance surface.
- Detail page should use native Filament sections/infolist entries where possible.
- Header action discipline remains: one state-sensitive primary action where possible, secondary actions in More or related context, dangerous/destructive actions confirmed.
- No asset registration is expected; `filament:assets` is not newly required by this spec.
## RBAC / Policy Implications
- Workspace member access and baseline capabilities remain source of truth.
- Non-member/wrong-workspace access stays deny-as-not-found.
- Readonly members may view but not mutate according to existing tests.
- High-impact actions must continue to enforce authorization server-side, not only through disabled UI state.
## OperationRun / Observability Implications
- Existing capture/compare actions continue to create/reuse `OperationRun` values through existing services.
- Existing `OperationUxPresenter`, `OperationRunLinks`, and `OpsUxBrowserEvents` continue to own queued/already-queued feedback.
- No new OperationRun type, lifecycle state, queued notification policy, or reconciliation behavior is introduced.
## Audit / Evidence Implications
- No new audit action is expected for pure page render changes.
- Existing capture/compare/archive audit behavior must remain intact.
- Current snapshot and compare matrix links remain evidence/proof navigation, not duplicated truth.
## Test Strategy
- Add or update Feature/Livewire tests for:
- ready profile decision summary
- blocked/no snapshot decision summary
- invalid/unsupported scope decision summary
- readonly/member action state
- no raw scope JSON or normalization lineage in the primary decision summary
- Preserve existing tests for:
- capture start authorization and OperationRun feedback
- compare start authorization and OperationRun feedback
- Baseline Profile RBAC 404/403 behavior
- record-page header discipline
- Add bounded browser smoke if rendered hierarchy changes:
- login/fixture using existing helpers
- visit Baseline Profile detail
- assert decision summary appears before technical lineage/metadata in visible page order
- assert no JavaScript errors
- save screenshot in `specs/369-baseline-profile-decision-view/artifacts/screenshots/`
## Rollout / Deployment Considerations
- **Migrations**: none expected.
- **Env vars**: none expected.
- **Queues**: no new queues or workers; existing capture/compare queue behavior unchanged.
- **Scheduler**: none.
- **Storage/volumes**: none.
- **Assets**: no new Filament assets expected; no deploy-specific `filament:assets` change.
- **Staging/Production**: Validate on staging with focused Baseline Profile view smoke before production promotion once production exists.
## UI Audit Close-Out
- **Route/design coverage counts**: no count change. Spec 369 recomposes the existing Baseline Profile detail view under the existing Filament resource route; it does not add, remove, or reclassify a route.
- **Design coverage matrix**: no count change. `UI-010 Baseline Profiles` remains a Strategic Surface, with the detail-page hierarchy updated inside the existing coverage row.
- **Page report**: `docs/ui-ux-enterprise-audit/page-reports/ui-010-baseline-profiles.md` records the decision-first detail update.
- **Screenshot evidence**: `specs/369-baseline-profile-decision-view/artifacts/screenshots/decision-first-detail.png`.
## Risk Controls
- Stop implementation if improving the page requires new persistence, a new readiness enum, a generic UI framework, or broad baseline engine changes.
- Preserve action names and tests unless a naming change is explicitly required by user-facing copy.
- Keep browser fixture cost bounded; do not create a platform-wide fixture suite inside this spec.
- Do not rewrite completed specs or Spec 368 audit history.
## Implementation Phases
### Phase 1 - Repo Truth And Test Baseline
Confirm existing Baseline Profile detail states, helpers, action safety, and current browser evidence. Add failing tests for the desired decision-first first viewport.
### Phase 2 - Decision Summary Composition
Recompose the infolist to lead with readiness, reason, impact, snapshot basis, assignment signal, and next action. Demote technical scope/metadata.
### Phase 3 - Action Safety And Coverage
Verify capture/compare/archive confirmations, authorization, OperationRun feedback, and global-search posture remain unchanged.
### Phase 4 - Browser Smoke And UI Audit Close-Out
Capture before/after proof, update UI audit coverage notes, and record guardrail/smoke outcome.
## Spec Readiness Gate
- `spec.md`: present and scoped.
- `plan.md`: present and repo-aware.
- `tasks.md`: generated for later implementation.
- RBAC, workspace isolation, OperationRun semantics, evidence truth, UI coverage, destructive/high-impact action handling, and test lanes are addressed.
- No open question blocks safe implementation.
- Scope is small enough for a bounded implementation loop.
- **Result**: PASS after tasks/checklist generation and preparation analysis.

View File

@ -0,0 +1,289 @@
# Feature Specification: Spec 369 - Baseline Profile Decision View
**Feature Branch**: `369-baseline-profile-decision-view`
**Created**: 2026-06-09
**Status**: Draft
**Input**: Spec 368 Platform UI Signal-to-Noise Browser Audit, Candidate B narrowed to the P1 Baseline Profile View finding.
## Candidate Selection Summary
- **Selected candidate**: Baseline Profile View decision-first productization.
- **Source**: `specs/368-platform-ui-signal-to-noise-browser-audit/findings.md` finding `UI-AUDIT-368-F03`, `audit.md` prioritized refactor candidate 1, and `spec-candidates.md` Candidate B.
- **Why selected**: It is the highest-priority browser-verified reachable page from Spec 368: the Baseline Profile detail view is a governance source-of-truth page, but its first viewport emphasizes long names, capture mode, normalization lineage, foundations, and metadata before the operator can answer readiness and next action.
- **Smallest viable slice**: Productize only the existing `BaselineProfileResource` detail view (`ViewBaselineProfile`) so it leads with baseline readiness, snapshot truth, assignment usefulness, compare/capture availability, and one safe next action before technical scope and metadata.
- **Deferred close alternatives**:
- Backup Set View: restore-critical but P2 and better handled after the baseline view pattern proves itself.
- OperationRun View: strong current foundation and recently changed by Specs 358-367; avoid reopening OperationRun surfaces immediately.
- Customer Review Workspace: materially covered by Specs 312, 326, 342, 343, 344, 349, and 351; only narrow polish remains.
- Provider Connections and Diagnostics: valid later slices, but lower priority than the P1 Baseline Profile finding for this preparation.
- Global Surface Information Architecture Contract v1: useful as a rule pack, but higher risk of over-generalization; this spec keeps the work page-local.
- **Completed-spec guardrail**: Related baseline truth and compare specs (`116`, `159`, `336`) are completed historical/runtime context and must not be rewritten. Spec `368` is an audit artifact with no application implementation and is used only as candidate source.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: Operators open a Baseline Profile detail page to decide whether a governance standard is usable, assigned, captured, comparable, and safe to act on, but the current default layout makes them parse technical scope and metadata before the decision.
- **Today's failure**: An operator can see status, capture mode, governed subject summary, normalization lineage, current snapshot, latest attempt, compare readiness, related context, and metadata as peer sections without a single first-read answer about readiness and the next safe action.
- **User-visible improvement**: The Baseline Profile detail page starts with a decision summary that states readiness, reason, impact, evidence/snapshot basis, assignment/usefulness, and one dominant next action. Technical details remain available but are secondary.
- **Smallest enterprise-capable version**: Reorder and lightly reshape the existing detail view using current Baseline Profile, Snapshot, Assignment, Compare readiness, and OperationRun link truth. Do not create new persistence, engines, statuses, Graph calls, or broad UI frameworks.
- **Explicit non-goals**: No Baseline engine rewrite, no compare algorithm changes, no new BaselineProfile table/columns, no new OperationRun type, no new Graph contract, no restore/backup UI work, no shell/sidebar work, no broad global UI standard, and no customer portal.
- **Permanent complexity imported**: Focused page-local presentation helpers may be added only if the existing resource methods become hard to review. Tests and one browser smoke may be added. No new persisted entity, enum/status family, cross-domain UI framework, or provider abstraction is expected.
- **Why now**: Spec 368 browser-verified this as the highest-priority reachable P1 productization gap after the platform-wide signal-to-noise audit.
- **Why not local**: A purely cosmetic reorder without explicit spec guardrails could leave dangerous actions, OperationRun links, snapshot truth, and technical metadata competing again. This spec defines the bounded decision-first contract and proof requirements.
- **Approval class**: Workflow Compression.
- **Red flags triggered**: UI framework risk (mitigated by page-local scope), high-impact action surface risk (mitigated by preserving existing confirmation/authorization/audit/OperationRun behavior).
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 1 | **Gesamt: 10/12**
- **Decision**: approve.
## Spec Scope Fields *(mandatory)*
- **Scope**: workspace.
- **Primary Routes**: `/admin/baseline-profiles/{record}` (`BaselineProfileResource` view page).
- **Secondary Routes For Regression Only**: `/admin/baseline-profiles`, `/admin/baseline-profiles/{record}/compare-matrix`, `/admin/workspaces/{workspace}/environments/{environment}/baseline-compare`.
- **Data Ownership**: `baseline_profiles`, `baseline_snapshots`, and baseline assignments remain workspace-owned or existing tenant/environment-linked truth as currently modeled. No schema change is introduced.
- **RBAC**: Workspace membership plus existing baseline capabilities continue to govern view/manage actions. Non-member/wrong-workspace access remains deny-as-not-found; members missing capabilities remain forbidden or disabled according to existing policy/UI enforcement.
## 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] New table/form/state added
- [ ] Customer-facing surface changed
- [x] Dangerous action changed
- [x] Status/evidence/review presentation changed
- [ ] Workspace/environment context presentation changed
## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact")*
- **Route/page/surface**: `/admin/baseline-profiles/{record}`, `apps/platform/app/Filament/Resources/BaselineProfileResource.php`, `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php`.
- **Current or new page archetype**: Drift / Diff strategic detail surface, existing `UI-057 Baseline Profile Detail/Edit`.
- **Design depth**: Strategic Surface.
- **Repo-truth level**: repo-verified and browser-verified by Spec 368 screenshot `artifacts/screenshots/admin/008-decision-surface-view-baseline-profile.png`.
- **Existing pattern reused**: Baseline Compare decision-first summary from Spec 336, existing `ActionSurfaceDeclaration`, `WorkspaceUiEnforcement`, `BadgeCatalog`/`BadgeRenderer`, `OperationUxPresenter`, and existing Baseline Profile truth helpers.
- **New pattern required**: none beyond a page-local decision summary if existing methods cannot express the hierarchy cleanly.
- **Screenshot required**: yes, before/after desktop; mobile or narrow viewport if the implementation materially changes responsive structure.
- **Page audit required**: yes, update `docs/ui-ux-enterprise-audit/page-reports/ui-010-baseline-profiles.md` and the relevant UI-057 coverage note if runtime changes are implemented.
- **Customer-safe review required**: no; this is an operator/MSP governance surface, not a customer-facing page.
- **Dangerous-action review required**: yes; capture, compare, compare assigned environments, and archive must retain confirmation, authorization, audit/OperationRun behavior, and action hierarchy.
- **Coverage files updated or explicitly not needed**:
- [x] `docs/ui-ux-enterprise-audit/route-inventory.md` - no route change expected; implementation must document no route-inventory change or update only if classification changes.
- [x] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` - update only if coverage counts/screenshot references change.
- [x] `docs/ui-ux-enterprise-audit/page-reports/...` - update `ui-010-baseline-profiles.md` / UI-057 note after implementation.
- [ ] `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, header actions, action links, evidence/snapshot viewer links, OperationRun links.
- **Systems touched**: Baseline Profile detail infolist, header actions, related context entries, compare/capture notifications, existing baseline action tests, browser smoke.
- **Existing pattern(s) to extend**: Baseline Compare decision-first flow, `ActionSurfaceDeclaration`, `WorkspaceUiEnforcement`, `OperationUxPresenter`, `OperationRunLinks`, `BadgeCatalog`/`BadgeRenderer`.
- **Shared contract / presenter / builder / renderer to reuse**: Existing resource methods first; use `BadgeCatalog`/`BadgeRenderer` for status-like badges; use `OperationRunLinks`/`OperationUxPresenter` for run links/toasts.
- **Why the existing shared path is sufficient or insufficient**: Existing helpers already know snapshot truth, compare readiness, next step, action authorization, and run linking. The gap is hierarchy, not missing domain truth.
- **Allowed deviation and why**: A small page-local derived summary helper is allowed if it replaces scattered inline logic and remains derived-only.
- **Consistency impact**: Baseline detail wording must stay aligned with Baseline Profiles list, Baseline Compare Landing, Baseline Snapshot detail, OperationRun detail, and audit/notification wording.
- **Review focus**: Verify no page-local status taxonomy, duplicate readiness model, or second action eligibility source is introduced.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, existing capture/compare start and links only.
- **Shared OperationRun UX contract/layer reused**: existing `OperationUxPresenter`, `OperationRunLinks`, `OpsUxBrowserEvents`, and `OperationRunService`-backed capture/compare flows.
- **Delegated start/completion UX behaviors**: queued toast, already-queued toast, `Open operation` link, run-enqueued browser event, and tenant/workspace-safe URL resolution remain delegated to existing shared paths.
- **Local surface-owned behavior that remains**: initiation inputs and page-level guidance only.
- **Queued DB-notification policy**: no new policy; preserve current capture/compare behavior.
- **Terminal notification path**: unchanged.
- **Exception required?**: none expected.
## Provider Boundary / Platform Core Check
- **Shared provider/platform boundary touched?**: no material boundary change.
- **Boundary classification**: Baseline Profile UI remains platform-core governance truth; provider-specific details remain under governed subject / Intune scope copy already present.
- **Seams affected**: Display wording for governed subject scope and normalization lineage only.
- **Neutral platform terms preserved or introduced**: workspace, managed environment, baseline, snapshot, compare readiness, governed subject, operation.
- **Provider-specific semantics retained and why**: Intune-specific policy/foundation scope terms remain where they describe current baseline subject support.
- **Why this does not deepen provider coupling accidentally**: The spec does not change contracts, persistence, provider adapters, Graph calls, or subject taxonomy.
- **Follow-up path**: none.
## 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 |
|---|---|---|---|---|---|---|
| Baseline Profile detail decision summary | yes | Native Filament infolist/resource page, page-local helper if needed | status messaging, action links, evidence links | page, derived display state | no | Existing route and resource only |
| Header action hierarchy | yes | Native Filament actions | dangerous/high-impact action surface | page/action state | no | Preserve existing confirmation/authorization |
| Technical metadata demotion | yes | Native Sections/details/aside where possible | diagnostics/progressive disclosure | page display | no | No raw data removal, only default hierarchy |
## 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 |
|---|---|---|---|---|---|---|---|
| Baseline Profile detail | Primary Decision Surface | Operator decides whether this baseline is ready to capture, compare, assign, or inspect | readiness, reason, impact, snapshot basis, assigned environment signal, one next action | governed subject scope, normalization lineage, metadata, detailed related links | Primary for one baseline's governance standard truth | Baseline setup, capture, compare, and drift workflow | Removes first-read parsing across scope, metadata, and action rows |
## 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 |
|---|---|---|---|---|---|---|---|
| Baseline Profile detail | operator-MSP, workspace manager, readonly reviewer | readiness, reason, impact, current snapshot, latest attempt, assignment count/state, compare/capture availability | normalization lineage, governed subject detail, related context | operation proof, raw snapshot/detail pages through existing links | capture baseline, compare now, open snapshot, open compare matrix, or edit profile depending on state/capability | technical IDs/timestamps, normalization lineage, low-level scope payload | One summary states decision; existing sections add proof/detail without restating the same decision |
## 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Baseline Profile detail | Detail / Header Actions | Drift / Diff decision detail | Capture, compare, open current snapshot, inspect compare matrix, or edit profile | Existing record view | N/A | More group or related context | Existing archive action remains in More/list flow with confirmation | `/admin/baseline-profiles` | `/admin/baseline-profiles/{record}` | workspace-owned profile, assigned managed environments | Baseline profile | readiness, reason, impact, snapshot basis, assignment/usefulness, one next action | 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Baseline Profile detail | Workspace manager / governance operator | Decide if the baseline can be captured, compared, or needs setup | Baseline governance detail | Is this baseline ready and what should I do next? | readiness, reason, impact, current snapshot truth, latest attempt, assignment state, compare/capture availability | normalization lineage, technical metadata, raw snapshot/run diagnostics through existing related routes | lifecycle, snapshot usability, assignment coverage, compare readiness, operation availability | TenantPilot queues capture/compare OperationRuns; no Microsoft configuration mutation | Capture baseline, Compare now, Open current snapshot, Open compare matrix, Edit profile | Capture/compare/compare assigned remain high-impact confirmed actions; archive remains destructive and confirmed |
## Proportionality Review
- **New source of truth?**: no.
- **New persisted entity/table/artifact?**: no.
- **New abstraction?**: no expected; optional page-local helper only if it reduces current inline complexity.
- **New enum/state/reason family?**: no.
- **New cross-domain UI framework/taxonomy?**: no.
- **Current operator problem**: The page does not answer readiness and next action before technical detail.
- **Existing structure is insufficient because**: Existing helper methods expose individual truths, but the page hierarchy still presents them as peer sections.
- **Narrowest correct implementation**: Recompose the existing detail view around a derived decision summary and demote technical detail; preserve all domain behavior.
- **Ownership cost**: Focused tests, screenshot evidence, and one page-report update.
- **Alternative intentionally rejected**: A global UI framework/rule system from Spec 368 Candidate A; this spec keeps the fix page-local.
- **Release truth**: current-release productization truth.
### Compatibility posture
This feature assumes a pre-production environment. Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by the implementation loop.
## Testing / Lane / Runtime Impact
- **Test purpose / classification**: Feature for Filament/Livewire rendering and action state; Browser for integrated first-viewport/screenshot proof.
- **Validation lane(s)**: fast-feedback for focused Pest tests; browser for bounded smoke; `git diff --check`; Pint for touched PHP if implementation changes PHP.
- **Why this classification and these lanes are sufficient**: The change is a Filament detail-view hierarchy and action-affordance change. Feature tests prove server-rendered text/action behavior; browser smoke proves first-viewport signal-to-noise and no JS/layout regression.
- **New or expanded test families**: `Spec369BaselineProfileDecisionViewTest` and optional `Spec369BaselineProfileDecisionViewSmokeTest`.
- **Fixture / helper cost impact**: Use existing baseline profile, snapshot, assignment, and workspace helpers/factories. Do not introduce broad seeded demo fixtures.
- **Heavy-family visibility / justification**: Browser coverage is explicit and limited to the Baseline Profile detail page because the source finding is browser-verified UI signal-to-noise.
- **Special surface test profile**: shared-detail-family / strategic-surface.
- **Standard-native relief or required special coverage**: Native Filament components should carry layout; special coverage required for decision-first default visibility and dangerous-action preservation.
- **Reviewer handoff**: Reviewers must confirm no new readiness truth, no action authorization regression, no technical detail hidden from permitted users, and no raw metadata in the primary decision block.
- **Budget / baseline / trend impact**: none expected; record if browser smoke or fixture setup expands materially.
- **Planned validation commands**:
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec369`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec369BaselineProfileDecisionViewSmokeTest.php --compact` if browser test is added
- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=BaselineProfile`
- `cd apps/platform && ./vendor/bin/sail pint --dirty`
- `git diff --check`
- **Runtime impact**: UI rendering and action hierarchy only; no migrations, env vars, queues, scheduler, storage, or external API changes expected.
## User Stories & Testing
### User Story 1 - Decide baseline readiness quickly (Priority: P1)
As a workspace manager, I want the Baseline Profile detail page to summarize whether the baseline is ready and what I should do next, so I can act without parsing technical scope and metadata first.
**Independent Test**: Render a profile with current snapshot, latest attempt, assignments, and compare readiness. Assert the first decision section contains readiness, reason, impact, snapshot basis, assignment signal, and one dominant next action before normalization lineage or metadata.
**Acceptance Scenarios**:
1. **Given** an active baseline profile with a consumable current snapshot and assigned environments, **When** the detail page renders, **Then** it shows a ready/comparable decision summary and the dominant safe action is compare-oriented when authorized.
2. **Given** an active profile without a consumable snapshot, **When** the detail page renders, **Then** it shows why compare is blocked and the dominant safe action is capture-oriented when authorized.
3. **Given** an invalid or unsupported governed-subject scope, **When** the detail page renders, **Then** it explains the blocker and points to profile review/edit without exposing raw scope JSON in the decision block.
### User Story 2 - Preserve high-impact action safety (Priority: P1)
As a reviewer, I want capture/compare/archive actions to keep their confirmation, authorization, audit, and OperationRun behavior while the page layout changes, so productization does not weaken governance safety.
**Independent Test**: Use existing Livewire action tests plus new assertions to prove capture, compare, and compare assigned environments remain confirmed/capability-gated and keep `Open operation` links/toasts when a run starts or is already queued.
**Acceptance Scenarios**:
1. **Given** a readonly workspace member, **When** the detail page renders, **Then** high-impact actions are disabled or hidden exactly as existing policy requires and the decision summary does not imply the actor can mutate.
2. **Given** an owner starts capture or compare, **When** the action succeeds, **Then** the existing OperationRun queued/duplicate feedback and canonical operation link behavior remains unchanged.
3. **Given** an archive/destructive action remains available through existing placement, **When** it is inspected in tests, **Then** it still requires confirmation and authorization.
### User Story 3 - Keep technical proof accessible but secondary (Priority: P2)
As a support-oriented operator, I want normalization lineage, timestamps, related context, and technical proof links available after the main decision, so I can diagnose without making every operator parse internals first.
**Independent Test**: Render the detail page and assert normalization lineage, metadata, related snapshot, compare matrix, and operation proof links remain accessible but are not the first decision content.
**Acceptance Scenarios**:
1. **Given** a profile with canonical governed-subject scope, **When** the detail page renders, **Then** normalization lineage is visible in a secondary section or disclosure, not in the main decision block.
2. **Given** a profile has a current snapshot or compare matrix route, **When** the page renders, **Then** related context links remain available and capability-safe.
3. **Given** browser smoke captures the page, **When** the first viewport is reviewed, **Then** technical metadata no longer competes with the primary readiness decision.
## Functional Requirements
- **FR-001**: The Baseline Profile detail page MUST render a first-read decision summary above technical scope and metadata.
- **FR-002**: The decision summary MUST state baseline readiness, reason, impact, current snapshot basis, latest attempt state, assignment/usefulness signal, and one dominant next action when a safe action exists.
- **FR-003**: The summary MUST derive only from existing `BaselineProfile`, `BaselineSnapshot`, `BaselineTenantAssignment`, compare readiness, and OperationRun/link truth.
- **FR-004**: The page MUST NOT create a new persisted readiness field, status family, or source of truth.
- **FR-005**: Technical scope detail, normalization lineage, timestamps, raw IDs, and support diagnostics MUST be secondary to the primary decision.
- **FR-006**: Existing related context links to current snapshot and compare matrix MUST remain available and capability-safe.
- **FR-007**: Capture baseline, compare now, compare assigned environments, edit, and archive action availability MUST remain governed by existing server-side authorization and UI enforcement.
- **FR-008**: High-impact capture/compare actions MUST retain `->action(...)`, `->requiresConfirmation()`, existing modal inputs, existing OperationRun queued/duplicate feedback, and canonical `Open operation` links.
- **FR-009**: No existing destructive action may move into a more prominent unsafe placement without explicit confirmation, authorization, and audit proof.
- **FR-010**: The implementation MUST keep `BaselineProfileResource` global search disabled unless a separate spec changes global search posture.
- **FR-011**: The page MUST remain workspace-owned and MUST not inherit hidden environment context for authorization or primary scope.
- **FR-012**: The implementation MUST update UI audit coverage notes or explicitly document why route/design-coverage counts are unchanged.
## Non-Functional Requirements
- **NFR-001**: The change MUST remain page-local to Baseline Profile detail unless implementation proves a minimal shared helper is required.
- **NFR-002**: The decision summary MUST remain scan-first and avoid duplicating the same status/reason/next-action content across multiple first-viewport sections.
- **NFR-003**: No Graph calls may occur during UI rendering.
- **NFR-004**: The page MUST continue to render using Filament v5 / Livewire v4-compatible components and tests.
- **NFR-005**: Browser smoke MUST stay bounded to the Baseline Profile detail view and required before/after screenshot evidence.
## Out Of Scope
- Baseline capture engine, compare engine, snapshot storage, assignment model, OperationRun lifecycle, provider contracts, migrations, queues, restore/backup workflows, system panel fixtures, global shell density work, and broad UI standards.
## Acceptance Criteria
- **AC-001**: Baseline Profile detail first viewport answers "Is this baseline ready and what should I do next?" before showing normalization lineage or metadata.
- **AC-002**: Authorized and unauthorized users see action affordances consistent with existing policy/capability behavior.
- **AC-003**: Capture and compare actions still create or reuse OperationRun feedback through existing shared paths.
- **AC-004**: Current snapshot, latest attempt, compare readiness, assigned environments, and related context remain accurate and accessible.
- **AC-005**: UI audit/page-report coverage is updated or explicitly marked unchanged with a rationale.
- **AC-006**: Focused Pest/Livewire tests and bounded browser smoke pass or a non-run reason is documented.
## Success Criteria
- Operators can identify readiness, blocker/reason, impact, and next action from the first decision block without reading normalization lineage.
- No new persisted truth, status family, or broad UI framework is introduced.
- Existing Baseline Profile action safety tests remain green.
- The page earns a materially better signal-to-noise assessment than Spec 368's `2.8` baseline in a follow-up browser review.
## Risks
- Moving sections could hide technical evidence needed by support users.
- A derived summary helper could accidentally become a second readiness source.
- Header action hierarchy could regress existing high-impact action safety.
- Browser smoke may require a stable baseline fixture with snapshot and assignment state.
## Assumptions
- The existing baseline helpers (`currentSnapshotLabel`, `latestAttemptedSnapshotLabel`, `compareReadinessLabel`, `profileNextStep`, related context entries) are sufficient starting points.
- No schema change is needed.
- `BaselineProfileResource` remains globally searchable disabled.
- Spec 368's browser screenshot is sufficient before-state evidence for candidate selection.
## Open Questions
- None blocking preparation. Implementation should verify whether the decision summary can be expressed cleanly inside `BaselineProfileResource::infolist()` or needs one small page-local helper.
## Follow-up Spec Candidates
- Backup Set View decision-first productization.
- OperationRun View metadata/proof separation polish, only after Spec 367 actionability settles.
- Global Surface Information Architecture Contract v1 if repeated bloat persists after page-local fixes.
- Diagnostic Surface Separation v1 for Environment Diagnostics, Required Permissions, and system panel fixture coverage.

View File

@ -0,0 +1,101 @@
# Tasks: Spec 369 - Baseline Profile Decision View
**Input**: `specs/369-baseline-profile-decision-view/spec.md`, `specs/369-baseline-profile-decision-view/plan.md`
**Prerequisites**: Do not implement until this preparation package has been reviewed. Related completed specs (`116`, `159`, `336`, `368`) are context only and must not be rewritten.
## Phase 1: Setup And Repo Truth
**Purpose**: Confirm current implementation truth and protect the existing action safety baseline.
- [x] T001 Re-read `specs/369-baseline-profile-decision-view/spec.md`, `plan.md`, and `tasks.md` together with `.specify/memory/constitution.md`, `docs/ai-coding-rules.md`, `docs/architecture-guidelines.md`, `docs/filament-guidelines.md`, and `docs/testing-guidelines.md`.
- [x] T002 [P] Inspect current Baseline Profile detail implementation in `apps/platform/app/Filament/Resources/BaselineProfileResource.php` and `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php`.
- [x] T003 [P] Inspect existing Baseline Profile action and RBAC tests in `apps/platform/tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php`, `apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php`, and `apps/platform/tests/Feature/Baselines/BaselineProfileAuthorizationTest.php`.
- [x] T004 [P] Inspect Spec 368 before-state evidence in `specs/368-platform-ui-signal-to-noise-browser-audit/findings.md`, `audit.md`, `page-scorecard.csv`, and screenshot `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/screenshots/admin/008-decision-surface-view-baseline-profile.png`.
- [x] T005 Confirm `BaselineProfileResource::$isGloballySearchable` stays disabled and provider registration remains unchanged in `apps/platform/bootstrap/providers.php`.
---
## Phase 2: User Story 1 - Decide Baseline Readiness Quickly (P1)
**Goal**: The Baseline Profile detail page answers readiness, reason, impact, snapshot basis, assignment/usefulness, and one next action before technical detail.
**Independent Test**: Render profiles in ready, blocked/no-snapshot, and invalid-scope states and assert the decision section appears before normalization lineage or metadata.
- [x] T006 [P] [US1] Add failing Feature/Livewire coverage for a ready profile decision summary in `apps/platform/tests/Feature/Filament/Spec369BaselineProfileDecisionViewTest.php`.
- [x] T007 [P] [US1] Add failing Feature/Livewire coverage for a no-consumable-snapshot profile showing blocked compare reason and capture-oriented next action in `apps/platform/tests/Feature/Filament/Spec369BaselineProfileDecisionViewTest.php`.
- [x] T008 [P] [US1] Add failing Feature/Livewire coverage for invalid or unsupported governed-subject scope showing review/edit guidance without raw scope JSON in `apps/platform/tests/Feature/Filament/Spec369BaselineProfileDecisionViewTest.php`.
- [x] T009 [US1] Recompose `BaselineProfileResource::infolist()` in `apps/platform/app/Filament/Resources/BaselineProfileResource.php` so the first section is a decision summary with readiness, reason, impact, snapshot basis, assignment signal, and one next action.
- [x] T010 [US1] Reuse existing helper truth in `apps/platform/app/Filament/Resources/BaselineProfileResource.php` for current snapshot, latest attempt, compare readiness, and profile next step before adding any new helper.
- [x] T011 [US1] If repeated inline closures become hard to review, add one derived page-local helper in `apps/platform/app/Filament/Resources/BaselineProfileResource.php` for the decision summary; keep it non-persisted and resource-local.
- [x] T012 [US1] Ensure the decision summary does not render `scope_jsonb`, `subject_type_keys`, `canonical_scope`, raw IDs, or normalization lineage as default primary decision content.
---
## Phase 3: User Story 2 - Preserve High-Impact Action Safety (P1)
**Goal**: Capture/compare/archive behavior remains confirmed, authorized, audited, and OperationRun-linked while the page hierarchy changes.
**Independent Test**: Existing capture/compare tests plus new Spec 369 assertions prove actions keep current visibility/disabled/execution semantics.
- [x] T013 [P] [US2] Extend `apps/platform/tests/Feature/Filament/Spec369BaselineProfileDecisionViewTest.php` or existing BaselineProfile action tests to assert readonly users see non-mutating decision copy and cannot execute capture/compare.
- [x] T014 [P] [US2] Extend action hierarchy assertions in `apps/platform/tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php` and `apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php` only if header action order or placement changes.
- [x] T015 [US2] Keep `capture`, `compareNow`, and `compareAssignedTenants` in `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php` on existing `->action(...)`, `->requiresConfirmation()`, capability enforcement, notification, and `OperationRunLinks` paths.
- [x] T016 [US2] Keep archive behavior in `apps/platform/app/Filament/Resources/BaselineProfileResource.php` confirmed and authorization-gated; do not make destructive actions more visually prominent.
- [x] T017 [US2] Verify queued/already-queued capture and compare feedback still uses `OperationUxPresenter`, `OperationRunLinks`, and `OpsUxBrowserEvents` in `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php`.
---
## Phase 4: User Story 3 - Keep Technical Proof Accessible But Secondary (P2)
**Goal**: Technical proof remains reachable after the primary decision, without competing in the first viewport.
**Independent Test**: Render the page and assert related context, normalization lineage, and metadata remain accessible but are not first-decision content.
- [x] T018 [P] [US3] Add assertions in `apps/platform/tests/Feature/Filament/Spec369BaselineProfileDecisionViewTest.php` that related context links still include current snapshot and compare matrix when available.
- [x] T019 [P] [US3] Add assertions in `apps/platform/tests/Feature/Filament/Spec369BaselineProfileDecisionViewTest.php` that normalization lineage and metadata remain visible in secondary placement.
- [x] T020 [US3] Adjust section ordering or disclosure treatment in `apps/platform/app/Filament/Resources/BaselineProfileResource.php` so Profile/Scope/Metadata no longer precede the decision summary.
- [x] T021 [US3] Keep `BaselineProfileResource::detailRelatedContextEntries()` links capability-safe and pointed at existing snapshot / compare matrix routes.
---
## Phase 5: UI Audit, Browser Smoke, And Validation
**Purpose**: Prove the productization change and close the active UI guardrail loop.
- [x] T022 [P] Add bounded browser smoke in `apps/platform/tests/Browser/Spec369BaselineProfileDecisionViewSmokeTest.php` that visits a Baseline Profile detail with a snapshot and assignment, asserts no JavaScript errors, and verifies the decision summary appears before technical metadata.
- [x] T023 [P] Save after screenshot evidence under `specs/369-baseline-profile-decision-view/artifacts/screenshots/` during browser smoke or manual verification.
- [x] T024 Update `docs/ui-ux-enterprise-audit/page-reports/ui-010-baseline-profiles.md` with the Baseline Profile detail decision-view change and the Spec 369 screenshot/reference.
- [x] T025 Update `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` or record an explicit no-count-change rationale in `specs/369-baseline-profile-decision-view/plan.md` if route/design coverage counts do not change.
- [x] T026 Confirm the implementation does not add Graph/provider client calls to the Baseline Profile detail render path.
- [x] T027 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=Spec369`.
- [x] T028 Run `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=BaselineProfile`.
- [x] T029 Run `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec369BaselineProfileDecisionViewSmokeTest.php --compact` if the browser smoke test is added.
- [x] T030 Run `cd apps/platform && ./vendor/bin/sail pint --dirty`.
- [x] T031 Run `git diff --check`.
- [x] T032 Record the final Livewire v4 compliance, provider registration impact, global-search posture, destructive/high-impact action confirmation/authorization/audit status, asset strategy, tests, deployment impact, and Guardrail / Smoke Coverage result in the implementation close-out response.
## Dependencies
- Phase 1 before all implementation tasks.
- US1 before US3 section-order finalization.
- US2 can run in parallel with US1 after action baseline is understood.
- Phase 5 after runtime changes.
## Parallel Execution Examples
- T002, T003, and T004 can run in parallel.
- T006, T007, and T008 can be authored in parallel in the same test file only if coordinated carefully; otherwise sequence them to avoid conflicts.
- T013 and T018/T019 can be authored after the primary test fixture shape exists.
## 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 (`shared-detail-family` / strategic surface) 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, policies, routes, Graph contracts, provider adapters, global UI framework, customer portal, backup/restore productization, or OperationRun lifecycle changes unless the spec is updated first.