diff --git a/apps/platform/app/Support/OpsUx/OperationRunProgressContract.php b/apps/platform/app/Support/OpsUx/OperationRunProgressContract.php index ba559c8c..7b87e825 100644 --- a/apps/platform/app/Support/OpsUx/OperationRunProgressContract.php +++ b/apps/platform/app/Support/OpsUx/OperationRunProgressContract.php @@ -38,6 +38,13 @@ public static function forRun(OperationRun $run): array $context = is_array($run->context) ? $run->context : []; $capability = self::capabilityForRun($run, $summaryCounts, $context); + if ($run->isCurrentlyActive() && $run->freshnessState()->isLikelyStale()) { + return self::indeterminateModel( + $capability, + RunDurationInsights::stuckGuidance($run) ?? 'Past the lifecycle window. Review worker health and logs before retrying.', + ); + } + return match ($capability) { self::COUNTED => self::countedModel($summaryCounts), self::PHASED => self::phasedModel($run, $context), @@ -411,4 +418,4 @@ private static function intOrNull(mixed $value): ?int { return is_int($value) ? $value : null; } -} \ No newline at end of file +} diff --git a/apps/platform/app/Support/OpsUx/OperationUxPresenter.php b/apps/platform/app/Support/OpsUx/OperationUxPresenter.php index c7c41f84..31e17978 100644 --- a/apps/platform/app/Support/OpsUx/OperationUxPresenter.php +++ b/apps/platform/app/Support/OpsUx/OperationUxPresenter.php @@ -201,7 +201,8 @@ private static function buildSurfaceGuidance(OperationRun $run): ?string $freshnessState = self::freshnessState($run); if ($freshnessState->isLikelyStale()) { - return 'This operation is past its lifecycle window. Review worker health and logs before retrying from the start surface.'; + return RunDurationInsights::stuckGuidance($run) + ?? 'Past the lifecycle window. Review worker health and logs before retrying.'; } if ($freshnessState->isReconciledFailed()) { diff --git a/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php b/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php index 60bcef5d..000872c4 100644 --- a/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php +++ b/apps/platform/resources/views/livewire/bulk-operation-progress.blade.php @@ -13,8 +13,14 @@ $operationsIndexUrl = $tenant ? \App\Support\OpsUx\OperationRunUrl::index($tenant) : null; $primaryActionLabel = $usesCollectivePrimaryAction ? 'Review operations' : 'View operation'; $bannerTitle = $hasActiveVisibleRuns && ! $hasTerminalVisibleRuns ? 'Active operations' : 'Operation updates'; + $hasStaleActiveVisibleRuns = $runs->contains( + fn ($run): bool => $run instanceof \App\Models\OperationRun + && $run->isCurrentlyActive() + && $run->problemClass() === \App\Models\OperationRun::PROBLEM_CLASS_ACTIVE_STALE_ATTENTION + ); $bannerHelper = match (true) { $hasActiveVisibleRuns && $hasTerminalFollowUpVisibleRuns => 'Active and recent operation updates that may need review.', + $hasStaleActiveVisibleRuns => 'One or more active operations are past their lifecycle window and need review.', $hasActiveVisibleRuns => 'Queued and running work stays here until diagnostics are needed.', $hasTerminalFollowUpVisibleRuns => 'Recent operation updates that may need review.', $hasTerminalVisibleRuns => 'Successful operation updates stay briefly visible so you can confirm completion and keep working.', @@ -111,8 +117,7 @@ class="inline-flex items-center justify-center rounded-lg border border-transpar $progress = \App\Support\OpsUx\OperationRunProgressContract::forRun($run); $hasDeterminateProgress = $progress['display'] === 'counted'; $lifecycleAttention = \App\Support\OpsUx\OperationUxPresenter::lifecycleAttentionSummary($run); - $showsLifecycleAttention = $lifecycleAttention !== null - && ($lifecycleAttention !== 'Likely stale' || $run->status === 'queued' || ! $hasDeterminateProgress); + $showsLifecycleAttention = $lifecycleAttention !== null; $progressLabel = $progress['label']; $progressPercent = $progress['percent']; $showsIndeterminateProgress = $progress['display'] === 'indeterminate'; diff --git a/apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php b/apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php index 2f032863..32a27fd3 100644 --- a/apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php +++ b/apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php @@ -243,6 +243,34 @@ function baselineCompareGapContext(array $overrides = []): array ->and($bannerPosition)->toBeLessThan($decisionPosition); }); +it('keeps stale canonical detail aligned with lifecycle guidance instead of ordinary queued copy', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + setAdminPanelContext(); + + $run = OperationRun::factory()->create([ + 'workspace_id' => (int) $tenant->workspace_id, + 'managed_environment_id' => (int) $tenant->getKey(), + 'type' => 'inventory_sync', + 'status' => OperationRunStatus::Queued->value, + 'outcome' => OperationRunOutcome::Pending->value, + 'created_at' => now()->subWeeks(2), + ]); + + $response = $this->actingAs($user) + ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) + ->get(\App\Support\OperationRunLinks::tenantlessView($run)) + ->assertOk() + ->assertSee('Likely stale operation') + ->assertSee('Decision'); + + $pageText = visiblePageText($response); + + expect($pageText)->toContain('past its lifecycle window') + ->not->toContain('No action needed yet. The operation is waiting for a worker.') + ->not->toContain('Waiting for worker.'); +}); + it('renders explicit sparse-data fallbacks for operation runs', function (): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); diff --git a/apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php b/apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php index 8f9ebe8b..17ae09c8 100644 --- a/apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php +++ b/apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php @@ -115,3 +115,29 @@ ->assertDontSee('Operation finished Unknown') ->assertDontSee('Completed with follow-up Unknown'); }); + +it('subordinates stale running progress to lifecycle guidance on the operations workbench', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + OperationRun::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + $this->actingAs($user) + ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) + ->get(\App\Support\OperationRunLinks::index()) + ->assertOk() + ->assertSee('Past the lifecycle window. Review worker health and logs before retrying.') + ->assertDontSee('4 / 10 processed (40%)') + ->assertDontSee('Progress details pending.'); +}); diff --git a/apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php b/apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php index 16f10702..17e799d0 100644 --- a/apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php +++ b/apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php @@ -45,6 +45,8 @@ ->get(\App\Support\OperationRunLinks::index()) ->assertOk() ->assertSee('Likely stale') + ->assertSee('Past the lifecycle window. Review worker health and logs before retrying.') + ->assertDontSee('Progress details pending.') ->assertSee('belong in terminal follow-up'); $this->actingAs($user) @@ -58,7 +60,9 @@ ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(\App\Support\OperationRunLinks::tenantlessView($staleRun)) ->assertOk() - ->assertSee('Likely stale operation'); + ->assertSee('Likely stale operation') + ->assertDontSee('No action needed yet. The operation is currently in progress.') + ->assertDontSee('Progress details pending.'); }); it('renders lifecycle outcome fallbacks when historical runs are missing stored outcomes', function (): void { diff --git a/apps/platform/tests/Feature/MonitoringOperationsTest.php b/apps/platform/tests/Feature/MonitoringOperationsTest.php index 76873717..26f4b17a 100644 --- a/apps/platform/tests/Feature/MonitoringOperationsTest.php +++ b/apps/platform/tests/Feature/MonitoringOperationsTest.php @@ -186,3 +186,32 @@ ->toContain('data-shared-detail-family="verification-report"') ->toContain('data-host-kind="operation_run_detail"'); }); + +it('keeps workspace monitoring guidance stale-first when an active run is beyond its lifecycle window', function (): void { + $tenant = ManagedEnvironment::factory()->create(); + [$user, $tenant] = createUserWithTenant($tenant, role: 'owner'); + + OperationRun::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + Filament::setTenant(null, true); + + $this->actingAs($user) + ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) + ->get(route('admin.operations.index', ['workspace' => $tenant->workspace])) + ->assertSuccessful() + ->assertSee('Past the lifecycle window. Review worker health and logs before retrying.') + ->assertDontSee('4 / 10 processed (40%)') + ->assertDontSee('Progress details pending.'); +}); diff --git a/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php b/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php index fd845ae6..0004cb6f 100644 --- a/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php +++ b/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php @@ -272,6 +272,40 @@ expect(mb_substr_count($pageText, 'Automatically reconciled'))->toBe(1); }); +it('keeps stale active detail aligned with lifecycle guidance when determinate progress data exists', function (): void { + $tenant = ManagedEnvironment::factory()->create(); + [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); + + $run = OperationRun::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => OperationRunStatus::Running->value, + 'outcome' => OperationRunOutcome::Pending->value, + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + Filament::setTenant(null, true); + + $this->actingAs($user) + ->withSession([ + WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, + ]) + ->get(OperationRunLinks::tenantlessView((int) $run->getKey())) + ->assertSuccessful() + ->assertSee('Likely stale operation') + ->assertSee('past its lifecycle window') + ->assertDontSee('No action needed yet. The operation is currently in progress.') + ->assertDontSee('4 / 10 processed (40%)') + ->assertDontSee('Progress details pending.'); +}); + it('keeps a canonical run viewer accessible when the remembered environment differs from the run tenant', function (): void { $workspace = Workspace::factory()->create(); $tenantA = ManagedEnvironment::factory()->for($workspace)->create(); diff --git a/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php b/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php index 803a1a8f..dc5c13eb 100644 --- a/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php +++ b/apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php @@ -344,7 +344,7 @@ 'total' => 10, 'processed' => 4, ], - 'started_at' => now()->subWeeks(2), + 'started_at' => now()->subMinute(), ]); $component = Livewire::actingAs($user) @@ -364,6 +364,70 @@ ->and($html)->not->toContain('Likely stale'); })->group('ops-ux'); +it('renders stale queued activity with lifecycle guidance instead of ordinary waiting copy', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + $this->actingAs($user); + Filament::setTenant($tenant, true); + + OperationRun::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => 'queued', + 'outcome' => 'pending', + 'created_at' => now()->subWeeks(2), + ]); + + $component = Livewire::actingAs($user) + ->test(BulkOperationProgress::class) + ->call('refreshRuns'); + + $html = html_entity_decode($component->html(), ENT_QUOTES | ENT_HTML5); + $pageText = preg_replace('/\s+/', ' ', strip_tags($html)); + + expect($html)->toContain('Likely stale') + ->and($pageText)->toContain('One or more active operations are past their lifecycle window and need review.') + ->and($pageText)->toContain('Past the lifecycle window. Review worker health and logs before retrying.') + ->and($pageText)->not->toContain('Waiting for worker.'); +})->group('ops-ux'); + +it('shows lifecycle attention for determinate stale-running shell cases without keeping the ordinary progress bar', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + $this->actingAs($user); + Filament::setTenant($tenant, true); + + OperationRun::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'user_id' => (int) $user->getKey(), + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + $component = Livewire::actingAs($user) + ->test(BulkOperationProgress::class) + ->call('refreshRuns'); + + $html = html_entity_decode($component->html(), ENT_QUOTES | ENT_HTML5); + $pageText = preg_replace('/\s+/', ' ', strip_tags($html)); + + expect($html)->toContain('Likely stale') + ->and($html)->toContain('data-testid="ops-ux-activity-feedback-indeterminate"') + ->and($pageText)->toContain('Past the lifecycle window. Review worker health and logs before retrying.') + ->and($pageText)->not->toContain('4 / 10 processed (40%)') + ->and($html)->not->toContain('aria-valuenow="40"'); +})->group('ops-ux'); + it('renders phased fallback progress without inventing a counted percentage', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); diff --git a/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php b/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php index a9c88366..feca5899 100644 --- a/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php +++ b/apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php @@ -215,9 +215,40 @@ ->test(BulkOperationProgress::class) ->call('refreshRuns') ->assertSet('hasActiveRuns', true) + ->assertSee('One or more active operations are past their lifecycle window and need review.') ->assertSee('Inventory sync') ->assertSee('Likely stale') - ->assertSee('Waiting for worker.'); + ->assertSee('Past the lifecycle window. Review worker health and logs before retrying.') + ->assertDontSee('Waiting for worker.'); +})->group('ops-ux'); + +it('subordinates determinate stale-running shell progress to lifecycle guidance', function () { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + $this->actingAs($user); + Filament::setTenant($tenant, true); + + OperationRun::factory()->create([ + 'managed_environment_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + Livewire::actingAs($user) + ->test(BulkOperationProgress::class) + ->call('refreshRuns') + ->assertSet('hasActiveRuns', true) + ->assertSee('Likely stale') + ->assertSee('Past the lifecycle window. Review worker health and logs before retrying.') + ->assertDontSee('4 / 10 processed (40%)') + ->assertDontSeeHtml('aria-valuenow="40"'); })->group('ops-ux'); it('clamps counted progress at the shell host when processed exceeds total', function () { diff --git a/apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php b/apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php index fd78851d..221ffc76 100644 --- a/apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php +++ b/apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php @@ -67,6 +67,22 @@ ->and($progress['percent'])->toBeNull(); }); +it('replaces queued waiting copy with stale lifecycle guidance once the lifecycle window is exceeded', function (): void { + $run = OperationRun::factory()->create([ + 'type' => 'inventory_sync', + 'status' => 'queued', + 'outcome' => 'pending', + 'created_at' => now()->subWeeks(2), + ]); + + $progress = OperationRunProgressContract::forRun($run); + + expect($progress['capability'])->toBe('activity') + ->and($progress['display'])->toBe('indeterminate') + ->and($progress['label'])->toBe('Past the lifecycle window. Review worker health and logs before retrying.') + ->and($progress['percent'])->toBeNull(); +}); + it('returns no progress for terminal runs even when retained counts exist', function (): void { $run = OperationRun::factory()->create([ 'status' => 'completed', @@ -87,6 +103,32 @@ ->and($progress['percent'])->toBeNull(); }); +it('returns no progress for reconciled terminal runs even when stale lineage is recorded', function (): void { + $run = OperationRun::factory()->create([ + 'status' => 'completed', + 'outcome' => 'failed', + 'context' => [ + 'reconciliation' => [ + 'reconciled_at' => now()->toIso8601String(), + 'source' => 'adapter_reconciler', + ], + ], + 'summary_counts' => [ + 'total' => 10, + 'processed' => 10, + ], + 'started_at' => now()->subMinutes(2), + 'completed_at' => now()->subSecond(), + ]); + + $progress = OperationRunProgressContract::forRun($run); + + expect($progress['capability'])->toBe('none') + ->and($progress['display'])->toBe('none') + ->and($progress['label'])->toBeNull() + ->and($progress['percent'])->toBeNull(); +}); + it('does not let outcome counters masquerade as counted progress', function (): void { $run = OperationRun::factory()->create([ 'status' => 'running', @@ -107,6 +149,67 @@ ->and($progress['percent'])->toBeNull(); }); +it('replaces ordinary activity copy with stale lifecycle guidance for stale running runs', function (): void { + $run = OperationRun::factory()->create([ + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + $progress = OperationRunProgressContract::forRun($run); + + expect($progress['capability'])->toBe('activity') + ->and($progress['display'])->toBe('indeterminate') + ->and($progress['label'])->toBe('Past the lifecycle window. Review worker health and logs before retrying.') + ->and($progress['percent'])->toBeNull(); +}); + +it('subordinates determinate stale-running progress to lifecycle guidance', function (): void { + $run = OperationRun::factory()->create([ + 'type' => 'inventory_sync', + 'status' => 'running', + 'outcome' => 'pending', + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + $progress = OperationRunProgressContract::forRun($run); + + expect($progress['capability'])->toBe('counted') + ->and($progress['display'])->toBe('indeterminate') + ->and($progress['label'])->toBe('Past the lifecycle window. Review worker health and logs before retrying.') + ->and($progress['processed'])->toBeNull() + ->and($progress['total'])->toBeNull() + ->and($progress['percent'])->toBeNull(); +}); + +it('does not overclaim stale lifecycle guidance for unsupported running types', function (): void { + $run = OperationRun::factory()->create([ + 'type' => 'unsupported.operation', + 'status' => 'running', + 'outcome' => 'pending', + 'summary_counts' => [ + 'total' => 10, + 'processed' => 4, + ], + 'started_at' => now()->subWeeks(2), + 'created_at' => now()->subWeeks(2), + ]); + + $progress = OperationRunProgressContract::forRun($run); + + expect($progress['capability'])->toBe('counted') + ->and($progress['display'])->toBe('counted') + ->and($progress['label'])->toBe('4 / 10 processed (40%)') + ->and($progress['percent'])->toBe(40); +}); + it('classifies repo-real baseline evidence capture runs as phased fallback', function (): void { $run = OperationRun::factory()->create([ 'type' => 'baseline_capture', diff --git a/specs/358-operationrun-queue-truth-foundation/checklists/requirements.md b/specs/358-operationrun-queue-truth-foundation/checklists/requirements.md new file mode 100644 index 00000000..e7b7983b --- /dev/null +++ b/specs/358-operationrun-queue-truth-foundation/checklists/requirements.md @@ -0,0 +1,64 @@ +# Requirements Checklist: Spec 358 - OperationRun Queue Truth Foundation + +**Purpose**: Preparation analysis for Spec 358 readiness +**Created**: 2026-06-06 +**Feature**: `specs/358-operationrun-queue-truth-foundation/spec.md` + +## Candidate Selection And Guardrails + +- [x] CHK001 The candidate source is explicit: direct user-provided draft plus repo-verified current code truth. +- [x] CHK002 No `specs/358-*` package existed before this prep. +- [x] CHK003 Related historical specs are treated as context only: 149, 160, 233, 268, 270, and 272. +- [x] CHK004 The selected slice is narrow and reviewable: generic queue-truth alignment only, with no new persistence or adapter framework. +- [x] CHK005 Repo-truth deviations from the user draft are recorded in `spec.md`. + +## Required Prep Artifacts + +- [x] CHK006 `spec.md` exists and contains no template placeholders. +- [x] CHK007 `plan.md` exists and is repo-aware. +- [x] CHK008 `tasks.md` exists and is ordered, small, and verifiable. +- [x] CHK009 This checklist exists. + +## Spec Quality + +- [x] CHK010 Spec Candidate Check is completed. +- [x] CHK011 The spec keeps `OperationRun` truth derived-only and does not introduce new persisted lifecycle state. +- [x] CHK012 The spec clearly separates current repo reconciliation truth from the user's broader future-framework wording. +- [x] CHK013 The spec states clear goals, non-goals, requirements, risks, and acceptance criteria. +- [x] CHK014 The proportionality review rejects a new queue-health subsystem or adapter registry by default. + +## Plan / Task Alignment + +- [x] CHK015 The plan identifies the actual repo surfaces likely to change. +- [x] CHK016 The plan explicitly preserves existing scheduled and adapter reconciliation commands as out-of-scope context. +- [x] CHK017 The plan keeps Filament v5 / Livewire v4 posture and provider-registration location visible. +- [x] CHK018 The tasks include tests-first work and explicit runtime validation commands. +- [x] CHK019 The task list keeps scope bounded and includes anti-creep guardrails against persistence or framework expansion. + +## UI / Monitoring Coverage + +- [x] CHK020 UI Surface Impact is completed and does not claim no-impact. +- [x] CHK021 The changed surfaces are correctly classified as existing monitoring family follow-through, not a new strategic customer surface. +- [x] CHK022 No new page-report identity or route-inventory expansion is required for this bounded monitoring-family wording fix. +- [x] CHK023 Audience-aware disclosure and no-overclaim wording boundaries are explicit. + +## Test Governance + +- [x] CHK024 The declared test families are the narrowest honest proof: Unit plus focused Feature. +- [x] CHK025 No broad new browser or heavy-governance family is introduced. +- [x] CHK026 Planned validation commands are explicit and file-scoped. + +## Readiness Gate Outcome + +- [x] CHK027 Candidate Selection Gate passes. +- [x] CHK028 Spec Readiness Gate passes. +- [x] CHK029 Runtime implementation has not started in this preparation step. +- [x] CHK030 Recommended next step is implementation, not more prep. + +## Review Outcome + +- [x] Outcome class: acceptable-special-case +- [x] Workflow outcome: keep +- [x] Final note location: active feature PR close-out entry `Guardrail / Smoke Coverage` +- [x] Preparation analyze result: pass via repo-based artifact review checklist; no standalone local `speckit.analyze` command was available in this repo surface +- [x] Tooling note: the repo-generated branch number advanced to `1000`, and local `setup-plan.sh` rejected that 4-digit prefix; the final prep artifacts were therefore authored manually under the user-requested `358` package without changing application runtime code diff --git a/specs/358-operationrun-queue-truth-foundation/plan.md b/specs/358-operationrun-queue-truth-foundation/plan.md new file mode 100644 index 00000000..90d875b8 --- /dev/null +++ b/specs/358-operationrun-queue-truth-foundation/plan.md @@ -0,0 +1,209 @@ +# Implementation Plan: OperationRun Queue Truth Foundation + +**Branch**: `358-operationrun-queue-truth-foundation` | **Date**: 2026-06-06 | **Spec**: `specs/358-operationrun-queue-truth-foundation/spec.md` +**Input**: Feature specification from `specs/358-operationrun-queue-truth-foundation/spec.md` + +## Summary + +Implement a narrow follow-up that aligns generic `OperationRun` queue truth across the existing lifecycle, freshness, progress, and monitoring-detail helpers. The slice must reuse current `OperationRun` persistence, current scheduled and adapter reconciliation behavior, current run links, and current authorization boundaries while removing contradictory active-work wording from shell, list, and canonical detail surfaces. + +The repo already contains the foundations from Specs 149, 160, 233, 268, 270, and 272. This plan must not reopen those packages or invent a new reconciliation framework. It only corrects the remaining generic truth gap in current runtime code. + +## Technical Context + +**Language/Version**: PHP 8.4.15, Laravel 12.52, Filament 5.2.1, Livewire 4.1.4 +**Primary Dependencies**: existing `OperationRun` monitoring helpers, `OperationRunProgressContract`, `RunDurationInsights`, `OperationUxPresenter`, current Filament Monitoring pages and shared `OperationRun` implementation seams, Pest 4.3 +**Storage**: PostgreSQL `operation_runs`; no schema change planned +**Testing**: Pest Unit + Feature +**Validation Lanes**: fast-feedback, confidence +**Target Platform**: Laravel monolith in `apps/platform` +**Project Type**: web application +**Performance Goals**: keep current polling/query posture unchanged; no new queries or background work required for render-time truth +**Constraints**: no new `OperationRun` status/outcome fields, no new adapter registry, no new queue/job commands, no Graph calls, no notification policy change +**Scale/Scope**: one shared derived truth path plus current shell/list/detail monitoring surfaces and focused regression coverage + +## Likely Affected Repo Surfaces + +- `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php` +- `apps/platform/app/Support/OpsUx/RunDurationInsights.php` +- `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` +- `apps/platform/app/Support/Operations/OperationRunFreshnessState.php` only if the current derivation needs a small bounded clarification +- `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php` +- `apps/platform/app/Filament/Pages/Monitoring/Operations.php` as the current route-backed Operations hub surface +- `apps/platform/resources/views/filament/pages/monitoring/operations.blade.php` only if current workbench/list wording needs a bounded adjustment +- `apps/platform/app/Filament/Resources/OperationRunResource.php` only as the reused table/detail payload seam, not as a route-backed surface +- `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` +- focused tests under: + - `apps/platform/tests/Unit/Support/OpsUx/` + - `apps/platform/tests/Feature/OpsUx/` + - `apps/platform/tests/Feature/Monitoring/` + - `apps/platform/tests/Feature/Filament/` + - `apps/platform/tests/Feature/Operations/` + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: existing operator-facing monitoring surfaces only +- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: + - tenant shell activity hint + - `/admin/workspaces/{workspace}/operations` via `App\Filament\Pages\Monitoring\Operations` + - `/admin/workspaces/{workspace}/operations/{run}` via `App\Filament\Pages\Operations\TenantlessOperationRunViewer` +- **No-impact class, if applicable**: N/A +- **Native vs custom classification summary**: native Filament surfaces plus one existing Livewire shell host +- **Shared-family relevance**: monitoring-state messaging, queue guidance, active-run hinting +- **State layers in scope**: shell, page, detail +- **Audience modes in scope**: operator-MSP, support-platform +- **Decision/diagnostic/raw hierarchy plan**: decision-first lifecycle truth before diagnostics; raw context remains secondary +- **Raw/support gating plan**: unchanged; raw/support evidence stays on current detail-only surfaces +- **One-primary-action / duplicate-truth control**: no new primary actions; existing row open and current shell open links remain authoritative +- **Handling modes by drift class or surface**: + - contradictory stale vs queue guidance is `hard-stop-candidate` + - wording-only cleanup with no behavioral effect is `review-mandatory` +- **Repository-signal treatment**: review-mandatory +- **Special surface test profiles**: monitoring-state-page, shared-detail-family +- **Required tests or manual smoke**: focused unit and feature proof only +- **Exception path and spread control**: any move toward new persistence, new adapter abstractions, or new queue-health orchestration is `reject-or-split` +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage +- **Navigation / Filament provider-panel handling**: unchanged; no provider registration or panel path work + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: yes +- **Systems touched**: lifecycle freshness, progress derivation, queue guidance, shell render, canonical monitoring detail +- **Shared abstractions reused**: + - `OperationRunFreshnessState` + - `OperationRunProgressContract` + - `RunDurationInsights` + - `OperationUxPresenter` + - existing `OperationRunLinks` and detail routes +- **New abstraction introduced? why?**: none by default; if one tiny queue-truth helper is required, it must stay inside `App\Support\OpsUx` and remain a derivation helper rather than a strategy or adapter registry +- **Why the existing abstraction was sufficient or insufficient**: the repo already centralizes the right ingredients, but the final operator wording remains split across helpers and render sites +- **Bounded deviation / spread control**: do not create a new framework layer where extending the current presenter or progress contract is sufficient + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: yes, reuse-only +- **Central contract reused**: current shared run-link, queued-toast, terminal-notification, and detail-route contract +- **Delegated UX behaviors**: queued toast wording, run links, browser events, and terminal notifications remain unchanged +- **Surface-owned behavior kept local**: only density and render ordering for shell/list/detail +- **Queued DB-notification policy**: unchanged +- **Terminal notification path**: unchanged +- **Exception path**: none + +## Provider Boundary & Portability Fit + +- **Shared provider/platform boundary touched?**: no +- **Provider-owned seams**: N/A +- **Platform-core seams**: platform-owned `OperationRun` monitoring truth only +- **Neutral platform terms / contracts preserved**: queued, running, lifecycle window, active operation, review worker health +- **Retained provider-specific semantics and why**: none +- **Bounded extraction or follow-up path**: none + +## Constitution Check + +- Inventory-first: unchanged; no new source of truth. +- Read/write separation: unchanged; this is read-only monitoring truth. +- Deterministic capabilities: unchanged. +- Proportionality / anti-bloat: pass only if the implementation extends current helpers instead of creating a new framework. +- Persisted truth: no new tables, entities, or status families. +- State discipline: derived-only; no new persisted lifecycle category. +- Shared pattern first: extend existing Ops UX helpers and current monitoring surfaces. +- Workspace / tenant isolation: unchanged and still mandatory. +- RBAC-UX: no client-side authorization expansion; server-side boundaries remain the only access control. +- Test governance: unit + feature remain the narrowest honest proof. +- Filament v5 / Livewire v4 posture: unchanged; no provider-registration or asset change. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: + - Unit: generic queue-truth derivation + - Feature: current shell/list/detail rendering and authorization-safe presentation +- **Affected validation lanes**: fast-feedback, confidence +- **Why this lane mix is the narrowest sufficient proof**: the slice corrects shared derivation and rendered copy on current surfaces without introducing JS-heavy or browser-only behavior +- **Narrowest proving command(s)**: + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/MonitoringOperationsTest.php tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php tests/Feature/Monitoring/MonitoringOperationsTest.php tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php` + - `git diff --check` +- **Fixture / helper / factory / seed / context cost risks**: existing `OperationRun` fixtures and workspace membership helpers are sufficient; avoid widening defaults +- **Expensive defaults or shared helper growth introduced?**: none planned +- **Heavy-family additions, promotions, or visibility changes**: none +- **Surface-class relief / special coverage rule**: monitoring-state-page and shared-detail-family coverage is explicit +- **Closing validation and reviewer handoff**: reviewers should compare stale-active, fresh-active, and reconciled-terminal cases across shell, list, and detail and confirm that stale-active wording does not coexist with ordinary queue/progress reassurance +- **Budget / baseline / trend follow-up**: none expected +- **Review-stop questions**: Did the change add persistence? Did it invent a new adapter framework? Does any stale-active surface still combine stale attention with ordinary `Waiting for worker.`, `Progress details pending.`, or equivalent calm queue/progress reassurance? +- **Escalation path**: `reject-or-split` if scope widens into queue runtime redesign +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage + +## Implementation Phases + +### Phase 1 — Confirm the shared queue-truth inputs + +- Inventory the exact contradictory paths in: + - `OperationRunProgressContract` + - `RunDurationInsights` + - `OperationUxPresenter` + - current shell/list/detail renderers +- Freeze the current shared vocabulary for: + - fresh queued/running + - stale active + - reconciled terminal +- Confirm that existing scheduled and adapter reconciliation commands remain context only + +### Phase 2 — Align the generic queue-truth derivation + +- Extend existing helpers so freshness, queue guidance, and progress availability tell one story +- Keep phase/composite/determinate progress derived-only and subordinate to stale lifecycle truth +- Ensure unsupported or low-evidence cases remain cautious rather than overclaiming orphaned state + +### Phase 3 — Retrofit current monitoring surfaces + +- Update the current shell banner so stale active work does not read as ordinary queue wait +- Update canonical list/detail guidance so they confirm the same lifecycle meaning +- Keep existing links, filters, and diagnostics structure unchanged + +### Phase 4 — Validate and lock the contract + +- Add or update targeted unit + feature tests +- Run the focused validation commands +- Record any remaining bounded wording choices in the active feature close-out + +## Risks and Mitigations + +- **Over-escalation of healthy work**: mitigate with explicit fresh-active negative assertions. +- **Residual renderer drift**: mitigate by covering shell, list, and detail in the same test set. +- **Accidental framework expansion**: mitigate by rejecting new registries, adapters, or persisted truth in review. + +## Project Structure + +### Documentation + +```text +specs/358-operationrun-queue-truth-foundation/ +|-- spec.md +|-- plan.md +|-- tasks.md +`-- checklists/ + `-- requirements.md +``` + +### Runtime surfaces likely to change later + +```text +apps/platform/app/Support/OpsUx/ +|-- OperationRunProgressContract.php +|-- OperationUxPresenter.php +`-- RunDurationInsights.php + +apps/platform/app/Support/Operations/ +`-- OperationRunFreshnessState.php + +apps/platform/app/Filament/ +|-- Pages/Monitoring/Operations.php +|-- Pages/Operations/TenantlessOperationRunViewer.php +`-- Resources/OperationRunResource.php + +apps/platform/resources/views/filament/pages/monitoring/ +`-- operations.blade.php + +apps/platform/resources/views/livewire/ +`-- bulk-operation-progress.blade.php +``` diff --git a/specs/358-operationrun-queue-truth-foundation/spec.md b/specs/358-operationrun-queue-truth-foundation/spec.md new file mode 100644 index 00000000..af74af13 --- /dev/null +++ b/specs/358-operationrun-queue-truth-foundation/spec.md @@ -0,0 +1,298 @@ +# Feature Specification: OperationRun Queue Truth Foundation + +**Feature Branch**: `358-operationrun-queue-truth-foundation` +**Created**: 2026-06-06 +**Status**: Draft +**Input**: User-provided OperationRun queue-truth draft, reconciled against current repo truth + +## Spec Candidate Check *(mandatory — SPEC-GATE-001)* + +- **Problem**: Current `OperationRun` UX still allows contradictory generic queue truth. The repo can show stale-attention semantics and ordinary queue/progress reassurance for the same active run, depending on which helper or surface renders first. +- **Today's failure**: A run can surface `Likely stale` lifecycle attention while generic copy still says `Waiting for worker.` or `No action needed yet. The operation is waiting for a worker.` This creates a false-calm operator message on active monitoring surfaces. +- **User-visible improvement**: Queued and running `OperationRun` records will render one honest generic lifecycle story across shell hints, monitoring rows, and canonical detail without overclaiming domain success or orphaned queue state. +- **Smallest enterprise-capable version**: Align the existing generic lifecycle helpers (`OperationRunFreshnessState`, `OperationRunProgressContract`, `RunDurationInsights`, and `OperationUxPresenter`) and apply the resulting truth to current monitoring and shell surfaces only. +- **Explicit non-goals**: No new persisted `OperationRun` statuses or outcomes, no queue schema redesign, no new worker-health subsystem, no new notification family, no new adapter framework, no new domain-success reconciliation, no restore/review/backup auto-completion logic, and no destructive action changes. +- **Permanent complexity imported**: One bounded derived queue-truth path over existing `OperationRun` state, plus focused tests that lock the contract across shared monitoring surfaces. +- **Why now**: Historical specs already improved stale visibility and shared progress, but current repo truth still contains contradictory generic wording. This is an active operator-trust gap in currently shipped runtime paths. +- **Why not local**: Fixing only one Blade view, one banner, or one detail card would leave the contradiction between progress, freshness, and queue-guidance helpers intact. +- **Approval class**: Core Enterprise +- **Red flags triggered**: shared interaction family, monitoring-state semantics, and possible helper consolidation. Defense: the slice remains derived-only, persistence-free, and narrower than a new reconciliation framework. +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12** +- **Decision**: approve + +## Repo Truth Reconciliation + +The user draft is directionally correct, but current repo truth changes the exact framing: + +1. The repo helper advanced to `1000` because `specs/999-seeder-external-id/` already exists, but this package is intentionally finalized as user-requested Spec `358`. +2. Generic stale reconciliation already exists via `TenantpilotReconcileOperationRuns` and `OperationLifecycleReconciler`; adapter-backed reconciliation already exists via `OpsReconcileAdapterRuns` and `AdapterRunReconciler`. +3. A shared progress helper already exists via `OperationRunProgressContract`, and Spec 272 already extended its active-progress precedence for phased/composite truth; this spec aligns stale/fresh queue guidance with that current shared contract instead of inventing a new one. +4. The current canonical Monitoring routes are workspace-scoped: `/admin/workspaces/{workspace}/operations` and `/admin/workspaces/{workspace}/operations/{run}`. `OperationRunResource` remains an implementation seam, not a route-backed surface. +5. No follow-up `Spec 359` promise is recorded here. Any later adapter or domain-success work must be promoted from fresh repo truth rather than pre-allocating a speculative framework sequence. + +## Spec Scope Fields *(mandatory)* + +- **Scope**: workspace, tenant, canonical-view +- **Primary Routes**: + - workspace-admin surfaces that host the shared shell activity hint while an entitled managed environment is active, including current `/admin/workspaces/{workspace}/environments/{environment}/...` starts + - `/admin/workspaces/{workspace}/operations` + - `/admin/workspaces/{workspace}/operations/{run}` + - the current canonical Monitoring surfaces `App\Filament\Pages\Monitoring\Operations` and `App\Filament\Pages\Operations\TenantlessOperationRunViewer` +- **Data Ownership**: + - `operation_runs` remain the only lifecycle and freshness source of truth + - `OperationRun` context remains the only place where legitimacy or reconciliation evidence may already exist + - no new persisted queue-truth mirror, no new audit artifact, and no new worker-health record are introduced +- **RBAC**: + - existing workspace and managed-environment entitlement rules remain authoritative + - non-members and out-of-scope actors remain `404` + - this spec changes wording and derived presentation only; it does not widen access or add new capability strings + +For canonical-view specs, the spec MUST define: + +- **Default filter behavior when tenant-context is active**: Existing environment-prefilter and page-state behavior on `/admin/workspaces/{workspace}/operations` remain unchanged. This spec changes queue truth, not monitoring state ownership. +- **Explicit entitlement checks preventing cross-tenant leakage**: Derived stale/queued guidance must use only runs the actor is already authorized to view. No new wording may reveal hidden tenant scope or hidden run existence. + +## UI Surface Impact *(mandatory — UI-COV-001)* + +- [ ] 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 +- [ ] New table/form/state added +- [ ] Customer-facing surface changed +- [ ] 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**: + - tenant shell activity hint (`BulkOperationProgress`) + - workspace operations hub (`App\Filament\Pages\Monitoring\Operations`) at `/admin/workspaces/{workspace}/operations` + - canonical operation detail (`App\Filament\Pages\Operations\TenantlessOperationRunViewer`) at `/admin/workspaces/{workspace}/operations/{run}` + - shared implementation seam only: `App\Filament\Resources\OperationRunResource` for the reused table/detail payload contract +- **Current or new page archetype**: existing monitoring/workbench family only +- **Design depth**: Domain Pattern Surface +- **Repo-truth level**: repo-verified +- **Existing pattern reused**: current `OperationRun` monitoring family, current shell activity hint, current monitoring detail banners +- **New pattern required**: none; this is a truth-alignment follow-up within the existing pattern family +- **Screenshot required**: no; the slice corrects shared wording/derived truth inside existing monitoring surfaces +- **Page audit required**: no new page-report identity; existing monitoring family coverage is sufficient +- **Customer-safe review required**: no; operator-only monitoring surfaces +- **Dangerous-action review required**: no; no action hierarchy or destructive behavior changes +- **Coverage files updated or explicitly not needed**: no new coverage file is required because the existing monitoring-family anchors already cover the touched reachable surfaces: `docs/ui-ux-enterprise-audit/route-inventory.md` (`UI-016`, `UI-017`), `docs/ui-ux-enterprise-audit/page-reports/ui-003-operations.md`, `docs/ui-ux-enterprise-audit/strategic-surfaces.md`, and `specs/313-workspace-environment-context-browser-verification/surface-inventory.md` +- **No-impact rationale when applicable**: N/A + +## Cross-Cutting / Shared Pattern Reuse *(mandatory)* + +- **Cross-cutting feature?**: yes +- **Interaction class(es)**: status messaging, queue/progress guidance, monitoring detail guidance, shell active-work hint +- **Systems touched**: + - `OperationRunFreshnessState` + - `OperationRunProgressContract` + - `RunDurationInsights` + - `OperationUxPresenter` + - current shell banner and canonical monitoring/detail surfaces +- **Existing pattern(s) to extend**: current lifecycle policy, freshness derivation, the Spec-270/272 shared progress contract, monitoring detail banner, and shared run-link/presenter paths +- **Shared contract / presenter / builder / renderer to reuse**: `OperationRunProgressContract`, `OperationUxPresenter`, `OperationRunFreshnessState`, and current monitoring/view renderer paths +- **Why the existing shared path is sufficient or insufficient**: The repo already has the right ownership boundaries, but the generic queue truth is split across multiple helpers that can disagree in wording and emphasis. +- **Allowed deviation and why**: none by default; if one tiny helper is required to keep existing presenters reviewable, it must remain local to current Ops UX truth and must not become an adapter registry +- **Consistency impact**: queued/running/stale wording, progress availability, lifecycle attention, and detail guidance must remain aligned across shell, list, and canonical detail +- **Review focus**: no surface may reassure with ordinary queue copy after freshness has already escalated the same active run to stale attention + +## OperationRun UX Impact *(mandatory)* + +- **Touches OperationRun start/completion/link UX?**: yes, reuse-only +- **Shared OperationRun UX contract/layer reused**: current `OperationRun` link, freshness, presenter, and progress helper paths +- **Delegated start/completion UX behaviors**: existing queued toasts, canonical run links, browser events, and terminal notification paths remain unchanged +- **Local surface-owned behavior that remains**: density and placement only +- **Queued DB-notification policy**: unchanged +- **Terminal notification path**: unchanged central lifecycle mechanism +- **Exception required?**: none + +## Provider Boundary / Platform Core Check *(mandatory)* + +- **Shared provider/platform boundary touched?**: no +- **Boundary classification**: N/A +- **Seams affected**: N/A +- **Neutral platform terms preserved or introduced**: `operation`, `queued`, `running`, `lifecycle window`, `review worker health` +- **Provider-specific semantics retained and why**: none +- **Why this does not deepen provider coupling accidentally**: the slice works entirely on platform-owned `OperationRun` truth and existing shared monitoring helpers +- **Follow-up path**: none + +## UI / Surface Guardrail Impact *(mandatory)* + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| Tenant shell activity hint | yes | Native Filament + existing Livewire view | shared active-work hint family | shell, page | no | no new action or route | +| Operations list / resource detail summary | yes | Native Filament resource/detail | shared monitoring family | page, detail | no | wording-only inside existing collection/detail | +| Canonical tenantless run viewer banners | yes | Native Filament page | shared monitoring detail family | detail | no | no new diagnostics section family | + +## Decision-First Surface Role *(mandatory)* + +| 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 | +|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | Secondary Context Surface | Decide whether an active run needs inspection now | active-state truth, one open link, honest progress availability | full diagnostics stay on canonical monitoring/detail pages | secondary because it supports ongoing work rather than owning diagnostics | follows existing start-surface workflow | removes false calmness | +| Operations list | Primary Decision Surface | Decide which active run needs inspection first | lifecycle attention, queue truth, scope, and run identity | full detail after drill-through | primary because it is the canonical monitoring queue | aligns with current monitoring triage | removes open-every-row guesswork | +| Canonical run detail | Tertiary Evidence / Diagnostics Surface | Confirm what the stale or active state really means | one honest lifecycle explanation before deep diagnostics | raw context, history, evidence, and related links | tertiary because inspection already happened | preserves current detail role | removes banner/guidance contradiction | + +## Audience-Aware Disclosure *(mandatory)* + +| 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 | +|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | operator-MSP | active-state summary plus one open action | minimal guidance only | raw/support data stays off-surface | `View operation` or current collective review action | raw detail stays on canonical monitoring surfaces | do not repeat stale explanation multiple ways | +| Operations list | operator-MSP | row-level stale/queued truth and scope | detail remains secondary | raw payloads stay on detail | row open | raw and related evidence stay on detail | one row summary, not multiple competing summaries | +| Canonical run detail | operator-MSP, support-platform | one honest lifecycle banner | diagnostics sections below the banner | raw context remains lower-priority | existing return/open actions | support/raw detail remains secondary to the top summary | lifecycle banner and queue guidance must not disagree | + +## UI/UX Surface Classification *(mandatory)* + +| 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 | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | Monitoring hint | Activity shell hint | Open the active run if guidance escalates | explicit open link | forbidden | existing shell secondary actions only | none | `/admin/workspaces/{workspace}/operations` with current contextual prefilter rules | `/admin/workspaces/{workspace}/operations/{run}` | current tenant/workspace shell context | Operation | whether the run is ordinary active work or already stale | none | +| Operations list | List / Table / Monitoring | Read-only monitoring registry | Open the run that needs follow-up | full-row open | required | existing table controls only | none | `/admin/workspaces/{workspace}/operations` | `/admin/workspaces/{workspace}/operations/{run}` | workspace scope and current filters | Operation run | honest queued/running lifecycle truth | none | +| Canonical run detail | Record / Detail / Monitoring | Diagnostics-first detail surface | Inspect lifecycle truth before deeper diagnosis | canonical detail page | N/A | existing header/related links only | none | `/admin/workspaces/{workspace}/operations` | `/admin/workspaces/{workspace}/operations/{run}` | workspace scope plus entitled tenant context | Operation run | honest active-state explanation | none | + +## Operator Surface Contract *(mandatory)* + +| 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 | +|---|---|---|---|---|---|---|---|---|---|---| +| Tenant shell activity hint | tenant operator | Decide whether to inspect active work now | shell hint | Is this active work still ordinary, or is it already stale? | label, status, progress availability, open link | deep diagnostics remain elsewhere | lifecycle, freshness, progress availability | none | current open/review action | none | +| Operations list | workspace operator | Prioritize which active run to inspect | monitoring registry | Which run is actually waiting, progressing, or already stale? | row summary, scope, lifecycle attention | raw payloads on detail | lifecycle, freshness, queue truth | none | open row | none | +| Canonical run detail | workspace operator | Confirm generic lifecycle truth before diagnosing | detail surface | Why does this run read as stale or still-active? | one lifecycle banner and queue guidance | raw context, failure payloads, related artifacts | lifecycle, freshness, legitimacy evidence | none | existing navigation and related links | none | + +## Proportionality Review *(mandatory when structural complexity is introduced)* + +- **New source of truth?**: no +- **New persisted entity/table/artifact?**: no +- **New abstraction?**: no by default; prefer extending existing helpers +- **New enum/state/reason family?**: no +- **New cross-domain UI framework/taxonomy?**: no +- **Current operator problem**: current generic queue truth can contradict itself across existing shared monitoring surfaces +- **Existing structure is insufficient because**: lifecycle, freshness, progress, and queue guidance currently live in separate helpers that can drift in wording and emphasis +- **Narrowest correct implementation**: align existing shared helpers and current monitoring renderers without adding a new framework, status family, or persistence layer +- **Ownership cost**: bounded shared-helper review burden plus focused tests +- **Alternative intentionally rejected**: a new reconciliation framework or queue-health persistence layer was rejected because the current issue is derived wording drift, not missing stored truth +- **Release truth**: current-release operator-trust correction + +## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* + +- **Test purpose / classification**: Unit + Feature +- **Validation lane(s)**: fast-feedback, confidence +- **Why this classification and these lanes are sufficient**: the feature changes derived queue truth and current rendered monitoring output; focused unit and feature proof are sufficient without a new browser or heavy-governance lane +- **New or expanded test families**: + - `apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` + - `apps/platform/tests/Unit/Support/OpsUx/RunDurationInsightsTest.php` or adjacent focused unit coverage if the repo keeps these assertions in an existing file + - `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` + - `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` + - `apps/platform/tests/Feature/MonitoringOperationsTest.php` + - `apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php` + - `apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php` + - `apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php` + - `apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php` +- **Fixture / helper cost impact**: low to moderate; reuse existing `OperationRun` factories, workspace membership helpers, and monitoring fixtures only +- **Heavy-family visibility / justification**: none +- **Special surface test profile**: monitoring-state-page plus shared-detail-family +- **Standard-native relief or required special coverage**: feature coverage is sufficient; no browser smoke is required unless implementation later proves a render-only regression that unit/feature coverage cannot catch +- **Reviewer handoff**: reviewers must confirm the same stale run no longer mixes stale attention with ordinary queue/progress reassurance on shell, list, and detail surfaces +- **Budget / baseline / trend impact**: none expected beyond small feature-local coverage growth +- **Escalation needed**: `reject-or-split` if the slice expands into new persistence, adapter orchestration, or queue runtime redesign +- **Active feature PR close-out entry**: Guardrail / Smoke Coverage +- **Planned validation commands**: + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/MonitoringOperationsTest.php tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php tests/Feature/Monitoring/MonitoringOperationsTest.php tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php` + - `git diff --check` + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - See honest queued and stale truth on active monitoring surfaces (Priority: P1) + +As an operator, I need active monitoring surfaces to distinguish normal queued/running work from stale lifecycle drift without mixing calm queue reassurance into the same state. + +**Why this priority**: This is the direct trust gap visible in current runtime surfaces. + +**Independent Test**: Seed fresh and stale queued/running runs, open the shell activity hint and monitoring list, and verify that stale-active runs do not mix stale attention with ordinary queue or progress reassurance. + +**Acceptance Scenarios**: + +1. **Given** a queued or running run is past its lifecycle window, **When** the shell or monitoring list renders it, **Then** the visible stale-active state does not also reassure with ordinary `Waiting for worker.`, `Progress details pending.`, or equivalent calm queue/progress copy. +2. **Given** a fresh queued or running run is still within its lifecycle window, **When** the same surfaces render it, **Then** the copy remains calm and does not falsely escalate it as stale. + +### User Story 2 - Keep canonical run detail aligned with generic queue truth (Priority: P1) + +As an operator, I need canonical run detail to confirm the same generic lifecycle meaning that list and shell surfaces already communicate before I inspect deeper diagnostics. + +**Why this priority**: The current contradiction is most damaging when detail and compact surfaces disagree. + +**Independent Test**: Open canonical detail for fresh, stale, and reconciled runs and verify that lifecycle banners, queue guidance, and top summary agree with the generic monitoring truth. + +**Acceptance Scenarios**: + +1. **Given** a stale queued or stale running run, **When** the detail page opens, **Then** the top summary explains that the run is past its lifecycle window before deeper diagnostics render. +2. **Given** a reconciled terminal run, **When** the detail page opens, **Then** it preserves the existing reconciled truth and does not regress to ordinary active-work guidance. + +### User Story 3 - Keep proof-backed reconciliation separate from generic queue truth (Priority: P2) + +As a product owner, I need generic queue truth to stay separate from domain-success or adapter reconciliation so that stale monitoring copy does not silently become a business-completion engine. + +**Why this priority**: The slice must stay narrow and avoid speculative framework growth. + +**Independent Test**: Compare a stale generic run and an adapter-reconciled run, then verify that generic stale wording remains cautious while existing reconciled evidence paths still drive terminal truth where already implemented. + +**Acceptance Scenarios**: + +1. **Given** a stale active run without proof-backed queue legitimacy evidence, **When** the UI renders it, **Then** the wording stays at `past lifecycle window` or equivalent cautious language and does not claim orphaned or domain-complete truth. +2. **Given** a run already completed through scheduled or adapter reconciliation, **When** the UI renders it, **Then** the existing reconciled terminal semantics remain intact and are not rewritten as generic queue waiting. + +### Edge Cases + +- A queued run is stale but has no trustworthy worker correlation or job identifier. +- A running run has determinate counts but is still past the lifecycle window. +- A run is unsupported by current lifecycle policy and therefore should not receive stale overclaiming. +- A run became terminal through `scheduled_reconciler` or `adapter_reconciler`. +- A system-initiated run has no human initiator but still needs honest lifecycle wording. +- Existing phase or composite progress hints exist in context and must not override stale lifecycle truth with false reassurance. + +## Requirements *(mandatory)* + +- **FR-358-001**: The system MUST keep `OperationRun.status` and `OperationRun.outcome` as the only persisted lifecycle fields for this slice. +- **FR-358-002**: Generic queue truth MUST be derived from current lifecycle, freshness, and trusted context only. +- **FR-358-003**: A run that is `likely_stale` by current lifecycle policy MUST NOT render ordinary queue or ordinary progress reassurance as visible stale-active guidance on shell, list, or canonical detail surfaces, whether that reassurance appears alone or alongside a stale label, banner, or attention cue. +- **FR-358-004**: Fresh queued and fresh running runs MUST remain visibly calm and MUST NOT inherit stale emphasis. +- **FR-358-005**: Existing scheduled or adapter reconciliation paths MUST remain authoritative for terminal truth where they already exist. +- **FR-358-006**: This feature MUST NOT introduce a new queue-orphaned, reconciled, or business-success persistence family. +- **FR-358-007**: This feature MUST NOT claim hard orphaned queue state unless current repo truth already provides trustworthy legitimacy evidence for that specific run. +- **FR-358-008**: Existing `OperationRun` links, queued toasts, browser events, and terminal notification paths MUST remain unchanged. +- **FR-358-009**: Existing workspace and tenant authorization boundaries MUST remain unchanged. +- **FR-358-010**: The canonical monitoring list, shell hint, and canonical run detail MUST use one aligned generic lifecycle vocabulary for fresh queued/running work, stale active work, and reconciled terminal work. +- **FR-358-011**: The implementation MUST stay within current helper ownership unless one small local helper is required to avoid unreadable presenter logic. +- **FR-358-012**: Focused unit and feature coverage MUST prove both positive and negative cases for fresh vs stale queue truth. + +## Success Criteria *(mandatory)* + +- **SC-358-001**: In focused regression coverage, stale queued/running runs no longer mix stale attention with ordinary queue/progress reassurance on the affected surfaces. +- **SC-358-002**: In focused regression coverage, fresh queued/running runs remain calm and do not falsely escalate to stale. +- **SC-358-003**: Existing reconciled terminal behavior remains intact for runs already completed by scheduled or adapter truth. +- **SC-358-004**: No new persisted lifecycle field, queue artifact, or adapter framework is introduced. + +## Assumptions + +- The current contradiction is a derived UX-truth problem, not a missing persistence problem. +- Existing lifecycle policy thresholds remain valid for this slice. +- Existing generic and adapter reconciliation commands remain out of scope except as historical context the UI must respect. + +## Risks + +- **Over-correction**: the slice could make all active work sound problematic. Mitigation: explicit fresh vs stale negative assertions. +- **Framework creep**: the slice could drift into a new generic queue-health subsystem. Mitigation: no new persistence, no new adapter registry, and explicit out-of-scope boundaries. +- **Detail/list drift survives**: one renderer could remain on old wording. Mitigation: focused list + detail + shell coverage in the same package. + +## Out of Scope + +- Queue worker health automation +- New scheduler, command, or job families +- New `OperationRun` statuses or outcomes +- Domain-success reconciliation for review, restore, backup, sync, or export runs +- Adapter framework expansion +- New Filament resources, routes, or destructive actions diff --git a/specs/358-operationrun-queue-truth-foundation/tasks.md b/specs/358-operationrun-queue-truth-foundation/tasks.md new file mode 100644 index 00000000..a4c8742e --- /dev/null +++ b/specs/358-operationrun-queue-truth-foundation/tasks.md @@ -0,0 +1,160 @@ +# Tasks: OperationRun Queue Truth Foundation + +**Input**: `specs/358-operationrun-queue-truth-foundation/spec.md`, `plan.md`, and `checklists/requirements.md` +**Prerequisites**: `spec.md` and `plan.md` +**Tests**: REQUIRED (Pest). Keep proof bounded to one unit family plus focused monitoring/detail feature tests. +**Operations**: No new `OperationRun` type, no new queue family, no new notification path, no new reconciliation command, and no new persisted lifecycle state. +**RBAC**: Reuse current workspace and managed-environment authorization rules; presentation must never reveal unauthorized runs. +**Shared Pattern Reuse**: Reuse `OperationRunProgressContract`, `RunDurationInsights`, `OperationUxPresenter`, `OperationRunFreshnessState`, and existing monitoring renderers. Do not introduce a new adapter registry or queue-health framework. +**Filament / Panel Guardrails**: Filament remains v5 on Livewire v4. Provider registration stays in `apps/platform/bootstrap/providers.php`. No new panel, route family, or asset strategy is allowed. +**Organization**: Tasks are grouped by user story so the generic derivation, surface retrofit, and follow-up guardrails remain independently reviewable. + +## 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 no hidden browser or heavy-governance proof is introduced. +- [x] Shared helpers, fixtures, and context defaults stay cheap by default. +- [x] Planned validation commands cover the change without widening into unrelated lanes. +- [x] The declared monitoring/detail surface test profile is explicit. +- [x] Any material budget, baseline, or escalation note is recorded in the active feature close-out. + +## Phase 1: Setup (Shared Truth Inventory) + +**Purpose**: confirm the current contradiction and the exact shared helper boundaries before runtime edits begin. + +- [x] T001 Review `specs/358-operationrun-queue-truth-foundation/spec.md`, `plan.md`, `checklists/requirements.md`, `.specify/memory/constitution.md`, `docs/ai-coding-rules.md`, `specs/149-queued-execution-reauthorization/spec.md`, `specs/160-operation-lifecycle-guarantees/spec.md`, `specs/233-stale-run-visibility/spec.md`, `specs/268-operationrun-activity-feedback/spec.md`, `specs/270-operationrun-progress-contract/spec.md`, and `specs/272-operationrun-phase-composite-progress/spec.md` together so the implementation stays on current repo truth. +- [x] T002 [P] Confirm the current generic derivation seams in `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`, `apps/platform/app/Support/OpsUx/RunDurationInsights.php`, `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`, and `apps/platform/app/Support/Operations/OperationRunFreshnessState.php`. +- [x] T003 [P] Confirm the current render seams in `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php`, `apps/platform/app/Filament/Pages/Monitoring/Operations.php`, `apps/platform/resources/views/filament/pages/monitoring/operations.blade.php`, `apps/platform/app/Filament/Resources/OperationRunResource.php` as the shared table/detail seam, and `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php`. +- [x] T004 [P] Confirm the focused proof owners in `apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`, `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`, `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`, `apps/platform/tests/Feature/MonitoringOperationsTest.php`, `apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php`, `apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php`, `apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php`, and `apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php`. + +--- + +## Phase 2: Foundational (Blocking Queue-Truth Contract) + +**Purpose**: settle one generic queue-truth contract before any individual surface is updated. + +**Critical**: No user-story runtime work should begin until this phase is complete. + +- [x] T005 [P] Add or extend failing unit coverage in `apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` for fresh queued, stale queued, fresh running, stale running, determinate-progress stale running, unsupported lifecycle, and reconciled-terminal cases. +- [x] T006 [P] Add or extend focused feature coverage in `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`, `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`, and `apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php` proving that stale-active runs do not mix stale attention with ordinary queue/progress reassurance. +- [x] T007 Freeze the canonical derivation boundary in `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`, `apps/platform/app/Support/OpsUx/RunDurationInsights.php`, `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`, and `apps/platform/app/Support/Operations/OperationRunFreshnessState.php` without introducing new persisted state or a new registry. + +**Checkpoint**: Generic queue truth is derived from one aligned helper path before shell/list/detail surfaces are edited. + +--- + +## Phase 3: User Story 1 - See honest queued and stale truth on active monitoring surfaces (Priority: P1) + +**Goal**: shell and list surfaces no longer mix stale-active attention with calm queue or progress reassurance. + +**Independent Test**: seed fresh and stale queued/running runs, render the shell hint and operations list, and verify that only fresh active work keeps calm queue/progress copy. + +### Tests for User Story 1 + +- [x] T008 [P] [US1] Extend `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` and `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` for stale queued, stale running, and determinate-progress stale-running shell cases. +- [x] T009 [P] [US1] Extend `apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php` and `apps/platform/tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php` for operations-list row truth and lifecycle-attention alignment. + +### Implementation for User Story 1 + +- [x] T010 [US1] Align generic progress and queue guidance in `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`, `apps/platform/app/Support/OpsUx/RunDurationInsights.php`, and `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` so stale-active work no longer presents ordinary queue/progress reassurance. +- [x] T011 [US1] Update `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php` so the shell activity hint consumes the aligned generic queue truth without local contradictory fallback copy. +- [x] T012 [US1] Update `apps/platform/app/Filament/Pages/Monitoring/Operations.php` and `apps/platform/app/Filament/Resources/OperationRunResource.php` only as needed so the route-backed operations hub plus the shared table/detail summaries expose the same generic queue truth. + +**Checkpoint**: User Story 1 is independently functional when stale-active work reads honestly on shell and list surfaces. + +--- + +## Phase 4: User Story 2 - Keep canonical run detail aligned with generic queue truth (Priority: P1) + +**Goal**: canonical run detail confirms the same lifecycle meaning before deeper diagnostics render. + +**Independent Test**: open canonical detail for fresh, stale, and reconciled runs and verify that top-level lifecycle guidance matches shell/list semantics. + +### Tests for User Story 2 + +- [x] T013 [P] [US2] Extend `apps/platform/tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php` and `apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php` for fresh, stale, and reconciled-terminal top-summary behavior. +- [x] T014 [P] [US2] Extend `apps/platform/tests/Feature/Monitoring/MonitoringOperationsTest.php` for compact-to-detail continuity where the same run is opened from the list after stale-active presentation. + +### Implementation for User Story 2 + +- [x] T015 [US2] Update `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` so lifecycle banners and detail guidance reflect the aligned generic queue truth before raw diagnostics. +- [x] T016 [US2] Update `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` and any touched `OperationRunResource` summary helpers so canonical detail and resource detail stay aligned. + +**Checkpoint**: User Story 2 is independently functional when canonical detail confirms, rather than contradicts, generic queue truth. + +--- + +## Phase 5: User Story 3 - Keep proof-backed reconciliation separate from generic queue truth (Priority: P2) + +**Goal**: generic stale wording stays cautious, while existing scheduled/adapter reconciliation remains authoritative for terminal truth. + +**Independent Test**: compare stale active runs and already-reconciled terminal runs to verify that cautious generic wording and existing reconciled semantics both survive. + +### Tests for User Story 3 + +- [x] T017 [P] [US3] Extend unit or feature coverage, including `apps/platform/tests/Feature/MonitoringOperationsTest.php` where the operations aggregate wording is already asserted, so stale active runs without strong legitimacy evidence do not claim orphaned or domain-complete truth while existing scheduled/adapter-reconciled terminal runs keep their terminal semantics. +- [x] T018 [P] [US3] Extend `apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php` or the nearest focused monitoring suite for reconciled-terminal detail language regression protection. + +### Implementation for User Story 3 + +- [x] T019 [US3] Tighten cautious stale-active wording in `apps/platform/app/Support/OpsUx/RunDurationInsights.php` and `apps/platform/app/Support/OpsUx/OperationUxPresenter.php` so the generic path says only what current lifecycle truth can prove. +- [x] T020 [US3] Confirm `apps/platform/app/Console/Commands/TenantpilotReconcileOperationRuns.php` and `apps/platform/app/Console/Commands/OpsReconcileAdapterRuns.php` need no runtime change and document that no command-layer change was required. + +**Checkpoint**: User Story 3 is independently functional when generic stale wording stays cautious and existing terminal reconciliation remains intact. + +--- + +## Phase 6: Polish & Cross-Cutting Validation + +**Purpose**: validate the bounded slice, stop drift, and hand off a clean implementation path. + +- [x] T021 [P] Refresh `specs/358-operationrun-queue-truth-foundation/spec.md`, `plan.md`, and `checklists/requirements.md` only if implementation proves a thinner or broader touched-file boundary. +- [x] T022 [P] Run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`. +- [x] T023 [P] Run `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/MonitoringOperationsTest.php tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php tests/Feature/Monitoring/MonitoringOperationsTest.php tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php`. +- [x] T024 [P] Run `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` for touched platform files. +- [x] T025 [P] Run `git diff --check`. +- [x] T026 [P] Record the final queue-truth wording, proof boundaries, and any retained cautious-language decisions in the active feature close-out entry `Guardrail / Smoke Coverage`. + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: no dependencies +- **Foundational (Phase 2)**: depends on Setup and blocks all story work +- **US1 (Phase 3)**: depends on Foundational completion +- **US2 (Phase 4)**: depends on Foundational completion and is easiest after US1 settles the shared wording +- **US3 (Phase 5)**: depends on US1 and US2 because it confirms the final generic truth boundary +- **Polish (Phase 6)**: depends on all desired user stories + +### Parallel Opportunities + +- `T002`, `T003`, and `T004` can run in parallel. +- `T005` and `T006` can run in parallel after the current contradiction is confirmed. +- `T008` and `T009` can run in parallel. +- `T013` and `T014` can run in parallel. +- `T022`, `T023`, `T024`, and `T025` can run in parallel after implementation is stable. + +### Implementation Strategy + +1. Freeze the shared queue-truth derivation first. +2. Ship US1 to remove the stale-vs-queue contradiction on shell/list surfaces. +3. Ship US2 so canonical detail confirms the same truth. +4. Ship US3 to protect the narrow boundary against overclaiming or framework creep. +5. Finish with the focused validation commands and close-out notes. + +## Guardrail / Smoke Coverage + +- Final queue-truth wording keeps stale-active guidance at `Past the lifecycle window. Review worker health and logs before retrying.` and removes competing calm queue/progress copy from shell, list, and top-level detail surfaces. +- Proof boundary remains shared-first: `OperationRunProgressContract` owns stale-active progress truth, `OperationUxPresenter` owns aligned lifecycle/detail guidance, and the shell/list/detail surfaces consume those shared seams instead of local fallback wording. +- Browser smoke path used the real local route `GET /admin/local/smoke-login` and verified `/admin/workspaces/38/operations` followed by the primary `Open operation` drilldown to `/admin/workspaces/38/operations/82`. +- Browser smoke confirmed stale wording is visible on both hub and detail, while `Waiting for worker.`, `Progress details pending.`, and `4 / 10 processed (40%)` do not appear as primary stale-active guidance. +- No runtime change was required in `apps/platform/app/Filament/Pages/Monitoring/Operations.php`, `apps/platform/app/Filament/Resources/OperationRunResource.php`, `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php`, `apps/platform/app/Console/Commands/TenantpilotReconcileOperationRuns.php`, or `apps/platform/app/Console/Commands/OpsReconcileAdapterRuns.php`; the existing shared seams consumed the contract/presenter updates directly. +- Validation evidence: + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/MonitoringOperationsTest.php tests/Feature/Monitoring/OperationLifecycleFreshnessPresentationTest.php tests/Feature/Monitoring/MonitoringOperationsTest.php` + - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php` + - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` + - `git diff --check`