feat: implement operation run queue truth foundation (spec 358) (#429)

Implements platform feature branch `358-operationrun-queue-truth-foundation`.

Target branch: `platform-dev`.

Follow-up integration path after merge:

`platform-dev` -> `dev`.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #429
This commit is contained in:
ahmido 2026-06-06 12:03:11 +00:00
parent b7907bd69d
commit 2a12729dc5
16 changed files with 1093 additions and 7 deletions

View File

@ -2,3 +2,26 @@
command = "./scripts/platform-sail"
args = ["artisan", "boost:mcp"]
cwd = "/Users/ahmeddarrazi/Documents/projects/wt-plattform"
[mcp_servers.gitea]
command = "bash"
args = [
"-lc",
'''
set -euo pipefail
ROOT="/Users/ahmeddarrazi/Documents/projects/wt-plattform"
for candidate in "$ROOT/.env" "$ROOT/apps/platform/.env"; do
if [ -f "$candidate" ] && grep -q '^GITEA_ACCESS_TOKEN=' "$candidate"; then
set -a
. "$candidate"
set +a
export GITEA_HOST="https://git.cloudarix.de"
exec docker run -i --rm -e GITEA_HOST -e GITEA_ACCESS_TOKEN docker.gitea.com/gitea-mcp-server -t stdio
fi
done
echo "GITEA_ACCESS_TOKEN not found in repo .env files" >&2
exit 1
''',
]

View File

@ -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;
}
}
}

View File

@ -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()) {

View File

@ -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';

View File

@ -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();

View File

@ -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.');
});

View File

@ -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 {

View File

@ -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.');
});

View File

@ -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();

View File

@ -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');

View File

@ -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 () {

View File

@ -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',

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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`