Enforce operation run link contract
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 54s

This commit is contained in:
Ahmed Darrazi 2026-04-23 15:08:43 +02:00
parent 421261a517
commit 0c81051426
26 changed files with 1974 additions and 32 deletions

View File

@ -240,6 +240,8 @@ ## Active Technologies
- Filesystem only (`specs/226-astrodeck-inventory-planning/*`) (226-astrodeck-inventory-planning)
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + `App\Models\Finding`, `App\Filament\Resources\FindingResource`, `App\Services\Findings\FindingWorkflowService`, `App\Services\Baselines\BaselineAutoCloseService`, `App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator`, `App\Services\PermissionPosture\PermissionPostureFindingGenerator`, `App\Jobs\CompareBaselineToTenantJob`, `App\Filament\Pages\Reviews\ReviewRegister`, `App\Filament\Resources\TenantReviewResource`, `BadgeCatalog`, `BadgeRenderer`, `AuditLog` metadata via `AuditLogger` (231-finding-outcome-taxonomy)
- PostgreSQL via existing `findings`, `finding_exceptions`, `tenant_reviews`, `stored_reports`, and audit-log tables; no schema changes planned (231-finding-outcome-taxonomy)
- PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Widgets, Pest v4, `App\Support\OperationRunLinks`, `App\Support\System\SystemOperationRunLinks`, `App\Support\Navigation\CanonicalNavigationContext`, `App\Support\Navigation\RelatedNavigationResolver`, existing workspace and tenant authorization helpers (232-operation-run-link-contract)
- PostgreSQL-backed existing `operation_runs`, `tenants`, and `workspaces` records plus current session-backed canonical navigation state; no new persistence (232-operation-run-link-contract)
- PHP 8.4.15 (feat/005-bulk-operations)
@ -274,9 +276,9 @@ ## Code Style
PHP 8.4.15: Follow standard conventions
## Recent Changes
- 232-operation-run-link-contract: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4 + Filament Resources/Pages/Widgets, Pest v4, `App\Support\OperationRunLinks`, `App\Support\System\SystemOperationRunLinks`, `App\Support\Navigation\CanonicalNavigationContext`, `App\Support\Navigation\RelatedNavigationResolver`, existing workspace and tenant authorization helpers
- 231-finding-outcome-taxonomy: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + `App\Models\Finding`, `App\Filament\Resources\FindingResource`, `App\Services\Findings\FindingWorkflowService`, `App\Services\Baselines\BaselineAutoCloseService`, `App\Services\EntraAdminRoles\EntraAdminRolesFindingGenerator`, `App\Services\PermissionPosture\PermissionPostureFindingGenerator`, `App\Jobs\CompareBaselineToTenantJob`, `App\Filament\Pages\Reviews\ReviewRegister`, `App\Filament\Resources\TenantReviewResource`, `BadgeCatalog`, `BadgeRenderer`, `AuditLog` metadata via `AuditLogger`
- 226-astrodeck-inventory-planning: Added Markdown artifacts + Astro 6.0.0 + TypeScript 5.9 context for source discovery + Repository spec workflow (`.specify`), Astro website source tree under `apps/website/src`, existing component taxonomy (`primitives`, `content`, `sections`, `layout`)
- 225-assignment-hygiene: Added PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade + `Finding`, `FindingResource`, `MyFindingsInbox`, `FindingsIntakeQueue`, `WorkspaceOverviewBuilder`, `EnsureFilamentTenantSelected`, `FindingWorkflowService`, `AuditLog`, `TenantMembership`, Filament page and table primitives
<!-- MANUAL ADDITIONS START -->
### Pre-production compatibility check

View File

@ -20,6 +20,7 @@
use App\Support\Badges\TagBadgeDomain;
use App\Support\Inventory\TenantCoverageTruth;
use App\Support\Inventory\TenantCoverageTruthResolver;
use App\Support\OperationRunLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\ActionSurfaceDefaults;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -535,7 +536,7 @@ public function basisRunSummary(): array
: 'The coverage basis is current, but your role cannot open the cited run detail.',
'badgeLabel' => $badge->label,
'badgeColor' => $badge->color,
'runUrl' => $canViewRun ? route('admin.operations.view', ['run' => (int) $truth->basisRun->getKey()]) : null,
'runUrl' => $canViewRun ? OperationRunLinks::view($truth->basisRun, $tenant) : null,
'historyUrl' => $canViewRun ? $this->inventorySyncHistoryUrl($tenant) : null,
'inventoryItemsUrl' => InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant),
];
@ -560,13 +561,6 @@ protected function coverageTruth(): ?TenantCoverageTruth
private function inventorySyncHistoryUrl(Tenant $tenant): string
{
return route('admin.operations.index', [
'tenant_id' => (int) $tenant->getKey(),
'tableFilters' => [
'type' => [
'value' => 'inventory_sync',
],
],
]);
return OperationRunLinks::index($tenant, operationType: 'inventory_sync');
}
}

View File

@ -110,14 +110,14 @@ protected function getHeaderActions(): array
$actions[] = Action::make('operate_hub_back_to_operations')
->label('Back to Operations')
->color('gray')
->url(fn (): string => route('admin.operations.index'));
->url(fn (): string => OperationRunLinks::index());
}
if ($activeTenant instanceof Tenant) {
$actions[] = Action::make('operate_hub_show_all_operations')
->label('Show all operations')
->color('gray')
->url(fn (): string => route('admin.operations.index'));
->url(fn (): string => OperationRunLinks::index());
}
$actions[] = Action::make('refresh')
@ -126,7 +126,7 @@ protected function getHeaderActions(): array
->color('primary')
->url(fn (): string => isset($this->run)
? OperationRunLinks::tenantlessView($this->run, $navigationContext)
: route('admin.operations.index'));
: OperationRunLinks::index());
if (! isset($this->run)) {
return $actions;

View File

@ -17,6 +17,7 @@
use App\Support\Badges\TagBadgeRenderer;
use App\Support\Filament\FilterOptionCatalog;
use App\Support\Inventory\InventoryPolicyTypeMeta;
use App\Support\OperationRunLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -148,7 +149,13 @@ public static function infolist(Schema $schema): Schema
return null;
}
return route('admin.operations.view', ['run' => (int) $record->last_seen_operation_run_id]);
$tenant = $record->tenant;
if ($tenant instanceof Tenant) {
return OperationRunLinks::view((int) $record->last_seen_operation_run_id, $tenant);
}
return OperationRunLinks::tenantlessView((int) $record->last_seen_operation_run_id);
})
->openUrlInNewTab(),
TextEntry::make('support_restore')

View File

@ -13,6 +13,7 @@
use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationUxPresenter;
use App\Support\Rbac\UiEnforcement;
use App\Support\ReviewPackStatus;
@ -199,9 +200,19 @@ public static function infolist(Schema $schema): Schema
->placeholder('—'),
TextEntry::make('operationRun.id')
->label('Operation')
->url(fn (ReviewPack $record): ?string => $record->operation_run_id
? route('admin.operations.view', ['run' => (int) $record->operation_run_id])
: null)
->url(function (ReviewPack $record): ?string {
if (! $record->operation_run_id) {
return null;
}
$tenant = $record->tenant;
if ($tenant instanceof Tenant) {
return OperationRunLinks::view((int) $record->operation_run_id, $tenant);
}
return OperationRunLinks::tenantlessView((int) $record->operation_run_id);
})
->openUrlInNewTab()
->placeholder('—'),
TextEntry::make('fingerprint')->label('Fingerprint')->copyable()->placeholder('—'),

View File

@ -41,7 +41,7 @@ protected function getViewData(): array
return [
'tenant' => null,
'runs' => collect(),
'operationsIndexUrl' => route('admin.operations.index'),
'operationsIndexUrl' => OperationRunLinks::index(),
'operationsIndexLabel' => OperationRunLinks::openCollectionLabel(),
'operationsIndexDescription' => OperationRunLinks::collectionScopeDescription(),
];
@ -68,7 +68,7 @@ protected function getViewData(): array
return [
'tenant' => $tenant,
'runs' => $runs,
'operationsIndexUrl' => route('admin.operations.index'),
'operationsIndexUrl' => OperationRunLinks::index($tenant),
'operationsIndexLabel' => OperationRunLinks::openCollectionLabel(),
'operationsIndexDescription' => OperationRunLinks::collectionScopeDescription(),
];

View File

@ -6,19 +6,89 @@
class PanelThemeAsset
{
/**
* @var array<string, bool>
*/
private static array $hotAssetReachability = [];
public static function resolve(string $entry): ?string
{
if (app()->runningUnitTests()) {
return static::resolveFromManifest($entry);
}
if (is_file(public_path('hot'))) {
if (static::shouldUseHotAsset($entry)) {
return Vite::asset($entry);
}
return static::resolveFromManifest($entry);
}
private static function shouldUseHotAsset(string $entry): bool
{
$hotFile = public_path('hot');
if (! is_file($hotFile)) {
return false;
}
$hotUrl = trim((string) file_get_contents($hotFile));
if ($hotUrl === '') {
return false;
}
$assetUrl = Vite::asset($entry);
if ($assetUrl === '') {
return false;
}
if (array_key_exists($assetUrl, static::$hotAssetReachability)) {
return static::$hotAssetReachability[$assetUrl];
}
$parts = parse_url($assetUrl);
if (! is_array($parts)) {
return static::$hotAssetReachability[$assetUrl] = false;
}
$host = $parts['host'] ?? null;
if (! is_string($host) || $host === '') {
return static::$hotAssetReachability[$assetUrl] = false;
}
$scheme = $parts['scheme'] ?? 'http';
$port = $parts['port'] ?? ($scheme === 'https' ? 443 : 80);
$transport = $scheme === 'https' ? 'ssl://' : '';
$connection = @fsockopen($transport.$host, $port, $errorNumber, $errorMessage, 0.2);
if (! is_resource($connection)) {
return static::$hotAssetReachability[$assetUrl] = false;
}
$path = ($parts['path'] ?? '/').(isset($parts['query']) ? '?'.$parts['query'] : '');
$hostHeader = isset($parts['port']) ? $host.':'.$port : $host;
stream_set_timeout($connection, 0, 200000);
fwrite(
$connection,
"HEAD {$path} HTTP/1.1\r\nHost: {$hostHeader}\r\nConnection: close\r\n\r\n",
);
$statusLine = fgets($connection);
fclose($connection);
if (! is_string($statusLine)) {
return static::$hotAssetReachability[$assetUrl] = false;
}
return static::$hotAssetReachability[$assetUrl] = preg_match('/^HTTP\/\d\.\d\s+[23]\d\d\b/', $statusLine) === 1;
}
private static function resolveFromManifest(string $entry): ?string
{
$manifest = public_path('build/manifest.json');

View File

@ -202,7 +202,7 @@ public function auditTargetLink(AuditLog $record): ?array
->whereKey($resourceId)
->where('workspace_id', (int) $workspace->getKey())
->exists()
? ['label' => OperationRunLinks::openLabel(), 'url' => route('admin.operations.view', ['run' => $resourceId])]
? ['label' => OperationRunLinks::openLabel(), 'url' => OperationRunLinks::tenantlessView($resourceId)]
: null,
'baseline_profile' => $workspace instanceof Workspace
&& $this->workspaceCapabilityResolver->isMember($user, $workspace)

View File

@ -81,6 +81,7 @@ public static function index(
?string $activeTab = null,
bool $allTenants = false,
?string $problemClass = null,
?string $operationType = null,
): string {
$parameters = $context?->toQuery() ?? [];
@ -106,6 +107,10 @@ public static function index(
}
}
if (is_string($operationType) && $operationType !== '') {
$parameters['tableFilters']['type']['value'] = $operationType;
}
return route('admin.operations.index', $parameters);
}

View File

@ -7,6 +7,7 @@
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\Tenant;
use App\Support\OperationRunLinks;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
@ -63,7 +64,7 @@ public function test_shows_only_generic_links_for_tenantless_runs_on_canonical_d
->get(route('admin.operations.view', ['run' => (int) $run->getKey()]))
->assertOk()
->assertSee('Operations')
->assertSee(route('admin.operations.index'), false)
->assertSee(OperationRunLinks::index(), false)
->assertDontSee('View restore run');
}

View File

@ -75,6 +75,34 @@ public function test_trusts_notification_style_run_links_with_no_selected_tenant
->assertSee('Canonical workspace view');
}
public function test_uses_canonical_collection_link_for_default_back_and_show_all_fallbacks(): void
{
$runTenant = Tenant::factory()->create();
[$user, $runTenant] = createUserWithTenant(tenant: $runTenant, role: 'owner');
$otherTenant = Tenant::factory()->create([
'workspace_id' => (int) $runTenant->workspace_id,
]);
createUserWithTenant(tenant: $otherTenant, user: $user, role: 'owner');
$run = OperationRun::factory()->create([
'workspace_id' => (int) $runTenant->workspace_id,
'tenant_id' => (int) $runTenant->getKey(),
'type' => 'inventory_sync',
]);
Filament::setTenant($otherTenant, true);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $runTenant->workspace_id])
->get(OperationRunLinks::tenantlessView($run))
->assertOk()
->assertSee('Back to Operations')
->assertSee('Show all operations')
->assertSee(OperationRunLinks::index(), false);
}
public function test_trusts_verification_surface_run_links_with_no_selected_tenant_context(): void
{
$tenant = Tenant::factory()->create();

View File

@ -4,9 +4,11 @@
use App\Filament\Pages\InventoryCoverage;
use App\Filament\Resources\InventoryItemResource;
use App\Models\InventoryItem;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Support\Inventory\InventoryCoverage as InventoryCoveragePayload;
use App\Support\OperationRunLinks;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
@ -40,21 +42,14 @@ function seedCoverageBasisRun(Tenant $tenant): OperationRun
$run = seedCoverageBasisRun($tenant);
$historyUrl = route('admin.operations.index', [
'tenant_id' => (int) $tenant->getKey(),
'tableFilters' => [
'type' => [
'value' => 'inventory_sync',
],
],
]);
$historyUrl = OperationRunLinks::index($tenant, operationType: 'inventory_sync');
$this->actingAs($user)
->get(InventoryCoverage::getUrl(tenant: $tenant))
->assertOk()
->assertSee('Latest coverage-bearing sync completed')
->assertSee('Open basis run')
->assertSee(route('admin.operations.view', ['run' => (int) $run->getKey()]), false)
->assertSee(OperationRunLinks::view($run, $tenant), false)
->assertSee($historyUrl, false)
->assertSee('Review the cited inventory sync to inspect provider or permission issues in detail.');
});
@ -78,6 +73,26 @@ function seedCoverageBasisRun(Tenant $tenant): OperationRun
->assertDontSee('Open basis run');
});
it('shows the last inventory sync as a canonical admin operation detail link', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$run = OperationRun::factory()->forTenant($tenant)->create([
'type' => 'inventory_sync',
]);
$item = InventoryItem::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'last_seen_operation_run_id' => (int) $run->getKey(),
]);
$this->actingAs($user)
->get(InventoryItemResource::getUrl('view', ['record' => $item], tenant: $tenant))
->assertOk()
->assertSee('Last inventory sync')
->assertSee(OperationRunLinks::view($run, $tenant), false);
});
it('keeps the no-basis fallback explicit on the inventory items list', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');

View File

@ -16,7 +16,7 @@
$this->actingAs($user);
OperationRun::factory()->create([
$run = OperationRun::factory()->create([
'workspace_id' => (int) $tenant->workspace_id,
'tenant_id' => (int) $tenant->getKey(),
'type' => 'provider.connection.check',
@ -32,6 +32,8 @@
->assertSee('Open operation')
->assertSee(OperationRunLinks::openCollectionLabel())
->assertSee(OperationRunLinks::collectionScopeDescription())
->assertSee(OperationRunLinks::index($tenant), false)
->assertSee(OperationRunLinks::tenantlessView($run), false)
->assertSee('No action needed.')
->assertDontSee('No operations yet.');
});

View File

@ -0,0 +1,160 @@
<?php
declare(strict_types=1);
use Tests\Support\OpsUx\SourceFileScanner;
/**
* @return array<string, string>
*/
function operationRunLinkContractIncludePaths(): array
{
$root = SourceFileScanner::projectRoot();
return [
'tenant_recent_operations_summary' => $root.'/app/Filament/Widgets/Tenant/RecentOperationsSummary.php',
'inventory_coverage' => $root.'/app/Filament/Pages/InventoryCoverage.php',
'inventory_item_resource' => $root.'/app/Filament/Resources/InventoryItemResource.php',
'review_pack_resource' => $root.'/app/Filament/Resources/ReviewPackResource.php',
'tenantless_operation_run_viewer' => $root.'/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php',
'related_navigation_resolver' => $root.'/app/Support/Navigation/RelatedNavigationResolver.php',
'system_directory_tenant' => $root.'/app/Filament/System/Pages/Directory/ViewTenant.php',
'system_directory_workspace' => $root.'/app/Filament/System/Pages/Directory/ViewWorkspace.php',
'system_ops_runs' => $root.'/app/Filament/System/Pages/Ops/Runs.php',
'system_ops_view_run' => $root.'/app/Filament/System/Pages/Ops/ViewRun.php',
'admin_panel_provider' => $root.'/app/Providers/Filament/AdminPanelProvider.php',
'tenant_panel_provider' => $root.'/app/Providers/Filament/TenantPanelProvider.php',
'ensure_filament_tenant_selected' => $root.'/app/Support/Middleware/EnsureFilamentTenantSelected.php',
'clear_tenant_context_controller' => $root.'/app/Http/Controllers/ClearTenantContextController.php',
'operation_run_url_delegate' => $root.'/app/Support/OpsUx/OperationRunUrl.php',
];
}
/**
* @return array<string, string>
*/
function operationRunLinkContractAllowlist(): array
{
$paths = operationRunLinkContractIncludePaths();
return [
$paths['admin_panel_provider'] => 'Admin panel navigation is bootstrapping infrastructure and intentionally links to the canonical collection route before request-scoped navigation context exists.',
$paths['tenant_panel_provider'] => 'Tenant panel navigation is bootstrapping infrastructure and intentionally links to the canonical collection route before tenant-specific helper context is owned by the source surface.',
$paths['ensure_filament_tenant_selected'] => 'Tenant-selection middleware owns redirect/navigation fallback infrastructure and must not fabricate source-surface navigation context.',
$paths['clear_tenant_context_controller'] => 'Clear-tenant redirects preserve an explicit redirect contract and cannot depend on UI helper context.',
];
}
/**
* @param array<string, string> $paths
* @param array<string, string> $allowlist
* @return list<array{file: string, line: int, snippet: string, expectedHelper: string, reason: string}>
*/
function operationRunLinkContractViolations(array $paths, array $allowlist = []): array
{
$patterns = [
[
'pattern' => '/route\(\s*[\'"]admin\.operations\.index[\'"]/',
'expectedHelper' => 'OperationRunLinks::index(...)',
'reason' => 'Raw admin operations collection route assembly bypasses the canonical admin link helper.',
],
[
'pattern' => '/route\(\s*[\'"]admin\.operations\.view[\'"]/',
'expectedHelper' => 'OperationRunLinks::view(...) or OperationRunLinks::tenantlessView(...)',
'reason' => 'Raw admin operation detail route assembly bypasses the canonical admin link helper.',
],
[
'pattern' => '/[\'"]\/system\/ops\/runs(?:\/[^\'"]*)?[\'"]/',
'expectedHelper' => 'SystemOperationRunLinks::index() or SystemOperationRunLinks::view(...)',
'reason' => 'Direct system operations path assembly bypasses the canonical system link helper.',
],
[
'pattern' => '/\b(?:Runs|ViewRun)::getUrl\(/',
'expectedHelper' => 'SystemOperationRunLinks::index() or SystemOperationRunLinks::view(...)',
'reason' => 'Direct system operations page URL generation belongs behind the canonical system link helper.',
],
];
$violations = [];
foreach ($paths as $path) {
if (array_key_exists($path, $allowlist)) {
continue;
}
$source = SourceFileScanner::read($path);
$lines = preg_split('/\R/', $source) ?: [];
foreach ($lines as $index => $line) {
foreach ($patterns as $pattern) {
if (preg_match($pattern['pattern'], $line) !== 1) {
continue;
}
$violations[] = [
'file' => SourceFileScanner::relativePath($path),
'line' => $index + 1,
'snippet' => SourceFileScanner::snippet($source, $index + 1),
'expectedHelper' => $pattern['expectedHelper'],
'reason' => $pattern['reason'],
];
}
}
}
return $violations;
}
it('keeps covered operation run link producers on canonical helper families', function (): void {
$paths = operationRunLinkContractIncludePaths();
$allowlist = operationRunLinkContractAllowlist();
$violations = operationRunLinkContractViolations($paths, $allowlist);
expect($violations)->toBeEmpty();
})->group('surface-guard');
it('keeps the operation run link exception boundary explicit and infrastructure-owned', function (): void {
$allowlist = operationRunLinkContractAllowlist();
expect(array_keys($allowlist))->toHaveCount(4);
foreach ($allowlist as $reason) {
expect($reason)
->not->toBe('')
->not->toContain('convenience');
}
foreach (array_keys($allowlist) as $path) {
expect(SourceFileScanner::read($path))->toContain("route('admin.operations.index')");
}
})->group('surface-guard');
it('reports actionable file and snippet output for a representative raw bypass', function (): void {
$probePath = storage_path('framework/testing/OperationRunLinkContractProbe.php');
if (! is_dir(dirname($probePath))) {
mkdir(dirname($probePath), 0777, true);
}
file_put_contents($probePath, <<<'PHP'
<?php
return route('admin.operations.view', ['run' => 123]);
PHP);
try {
$violations = operationRunLinkContractViolations([
'probe' => $probePath,
]);
} finally {
@unlink($probePath);
}
expect($violations)->toHaveCount(1)
->and($violations[0]['file'])->toContain('OperationRunLinkContractProbe.php')
->and($violations[0]['line'])->toBe(3)
->and($violations[0]['snippet'])->toContain("route('admin.operations.view'")
->and($violations[0]['expectedHelper'])->toContain('OperationRunLinks::view')
->and($violations[0]['reason'])->toContain('bypasses the canonical admin link helper');
})->group('surface-guard');

View File

@ -5,6 +5,8 @@
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OperationRunUrl;
use App\Support\System\SystemOperationRunLinks;
use Illuminate\Support\Facades\File;
it('routes all OperationRun view links through OperationRunLinks', function (): void {
@ -82,3 +84,38 @@
'problemClass' => OperationRun::PROBLEM_CLASS_TERMINAL_FOLLOW_UP,
]));
})->group('ops-ux');
it('preserves helper-owned operation type filters on canonical operations collection links', function (): void {
$tenant = Tenant::factory()->create();
expect(OperationRunLinks::index($tenant, operationType: 'inventory_sync'))
->toBe(route('admin.operations.index', [
'tenant_id' => (int) $tenant->getKey(),
'tableFilters' => [
'type' => [
'value' => 'inventory_sync',
],
],
]));
})->group('ops-ux');
it('keeps the thin operation URL delegate on the canonical admin helpers', function (): void {
$tenant = Tenant::factory()->create();
$run = OperationRun::factory()->for($tenant)->create();
expect(OperationRunUrl::view($run, $tenant))
->toBe(OperationRunLinks::view($run, $tenant))
->and(OperationRunUrl::index($tenant))
->toBe(OperationRunLinks::index($tenant));
})->group('ops-ux');
it('resolves system operation links through the canonical system helper family', function (): void {
$run = OperationRun::factory()->create();
expect(SystemOperationRunLinks::index())
->toBe(\App\Filament\System\Pages\Ops\Runs::getUrl(panel: 'system'))
->and(SystemOperationRunLinks::view($run))
->toBe(\App\Filament\System\Pages\Ops\ViewRun::getUrl(['run' => (int) $run->getKey()], panel: 'system'))
->and(SystemOperationRunLinks::view((int) $run->getKey()))
->toBe(\App\Filament\System\Pages\Ops\ViewRun::getUrl(['run' => (int) $run->getKey()], panel: 'system'));
})->group('ops-ux');

View File

@ -15,6 +15,7 @@
use App\Services\ReviewPackService;
use App\Support\Auth\UiTooltips;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunLinks;
use App\Support\ReviewPackStatus;
use Filament\Actions\ActionGroup;
use Filament\Facades\Filament;
@ -320,12 +321,16 @@ function seedReviewPackEvidence(Tenant $tenant): EvidenceSnapshot
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$snapshot = seedReviewPackEvidence($tenant);
$run = OperationRun::factory()->forTenant($tenant)->create([
'type' => 'tenant.review_pack.generate',
]);
$pack = ReviewPack::factory()->ready()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'initiated_by_user_id' => (int) $user->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'operation_run_id' => (int) $run->getKey(),
'summary' => [
'finding_count' => 5,
'report_count' => 2,
@ -352,6 +357,7 @@ function seedReviewPackEvidence(Tenant $tenant): EvidenceSnapshot
->assertDontSee('Artifact truth')
->assertSee('Publishable')
->assertSee('#'.$snapshot->getKey())
->assertSee(OperationRunLinks::view($run, $tenant), false)
->assertSee('resolved');
});

View File

@ -2,9 +2,11 @@
declare(strict_types=1);
use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Models\User;
use App\Support\Auth\PlatformCapabilities;
use App\Support\System\SystemOperationRunLinks;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
@ -35,6 +37,46 @@
'/system/ops/runs',
]);
it('returns 404 when a tenant session accesses a system operation detail route', function () {
$user = User::factory()->create();
$run = OperationRun::factory()->create();
$this->actingAs($user)
->get(SystemOperationRunLinks::view($run))
->assertNotFound();
});
it('returns 403 when a platform user lacks operations capability on system operation detail', function () {
$platformUser = PlatformUser::factory()->create([
'capabilities' => [
PlatformCapabilities::ACCESS_SYSTEM_PANEL,
],
'is_active' => true,
]);
$run = OperationRun::factory()->create();
$this->actingAs($platformUser, 'platform')
->get(SystemOperationRunLinks::view($run))
->assertForbidden();
});
it('returns 200 on system operation detail when a platform user has operations capability', function () {
$platformUser = PlatformUser::factory()->create([
'capabilities' => [
PlatformCapabilities::ACCESS_SYSTEM_PANEL,
PlatformCapabilities::OPERATIONS_VIEW,
],
'is_active' => true,
]);
$run = OperationRun::factory()->create();
$this->actingAs($platformUser, 'platform')
->get(SystemOperationRunLinks::view($run))
->assertSuccessful();
});
it('returns 200 when a platform user has the required capability', function () {
$platformUser = PlatformUser::factory()->create([
'capabilities' => [

View File

@ -5,11 +5,13 @@
beforeEach(function (): void {
$this->originalPublicPath = public_path();
$this->originalEnvironment = app()->environment();
$this->temporaryPublicPath = null;
});
afterEach(function (): void {
app()->usePublicPath($this->originalPublicPath);
app()->instance('env', $this->originalEnvironment);
if (is_string($this->temporaryPublicPath) && File::isDirectory($this->temporaryPublicPath)) {
File::deleteDirectory($this->temporaryPublicPath);
@ -69,6 +71,27 @@ function useTemporaryPublicPath(): string
->not->toContain(':5173');
});
it('falls back to the built manifest asset when the Vite hot server is unreachable', function (): void {
$publicPath = useTemporaryPublicPath();
app()->instance('env', 'local');
File::ensureDirectoryExists($publicPath.'/build');
File::put($publicPath.'/hot', 'http://127.0.0.1:1');
File::put(
$publicPath.'/build/manifest.json',
json_encode([
'resources/css/filament/admin/theme.css' => [
'file' => 'assets/theme-test.css',
],
], JSON_THROW_ON_ERROR),
);
expect(PanelThemeAsset::resolve('resources/css/filament/admin/theme.css'))
->toEndWith('/build/assets/theme-test.css')
->not->toContain(':1');
});
it('returns null when the build manifest contains invalid json', function (): void {
$publicPath = useTemporaryPublicPath();

View File

@ -0,0 +1,36 @@
# Specification Quality Checklist: Operation Run Link Contract Enforcement
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-04-23
**Feature**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/232-operation-run-link-contract/spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Validation pass 1 completed on 2026-04-23.
- The spec stays intentionally narrow: existing helper families remain the contract, and the feature only standardizes adoption plus a bounded allowlist guard.
- A few requirement lines necessarily name existing shared contract classes and canonical routes because the subject of the spec is contract enforcement on those existing platform surfaces. The spec avoids prescribing implementation structure beyond reuse of the already-shipped canonical paths.

View File

@ -0,0 +1,380 @@
openapi: 3.1.0
info:
title: Operation Run Link Contract Enforcement
version: 1.0.0
summary: Logical internal contract for Spec 232 canonical admin and system operation-run links plus bounded guard enforcement.
description: |
This contract documents the internal helper-owned URL semantics that Spec 232 enforces.
It is intentionally logical rather than a public HTTP API because the feature reuses
existing Filament pages, helper families, and route ownership instead of introducing
a new controller namespace.
servers:
- url: https://logical.internal
description: Non-routable placeholder used to describe internal repository contracts.
paths:
/internal/operation-run-links/admin/collection:
post:
summary: Build the canonical admin operations collection URL for a covered source surface.
operationId: buildAdminOperationCollectionLink
x-not-public-http: true
requestBody:
required: true
content:
application/vnd.tenantpilot.admin-operation-collection-input+json:
schema:
$ref: '#/components/schemas/AdminOperationCollectionLinkInput'
responses:
'200':
description: Canonical admin collection link emitted through `OperationRunLinks::index(...)`.
content:
application/vnd.tenantpilot.operation-link+json:
schema:
$ref: '#/components/schemas/CanonicalOperationLink'
/internal/operation-run-links/admin/detail:
post:
summary: Build the canonical admin operation detail URL for a covered source surface.
operationId: buildAdminOperationDetailLink
x-not-public-http: true
requestBody:
required: true
content:
application/vnd.tenantpilot.admin-operation-detail-input+json:
schema:
$ref: '#/components/schemas/AdminOperationDetailLinkInput'
responses:
'200':
description: Canonical admin detail link emitted through `OperationRunLinks::view(...)` or `tenantlessView(...)`.
content:
application/vnd.tenantpilot.operation-link+json:
schema:
$ref: '#/components/schemas/CanonicalOperationLink'
/internal/operation-run-links/system/collection:
post:
summary: Build the canonical system operations collection URL for a covered system source surface.
operationId: buildSystemOperationCollectionLink
x-not-public-http: true
requestBody:
required: true
content:
application/vnd.tenantpilot.system-operation-collection-input+json:
schema:
$ref: '#/components/schemas/SystemOperationCollectionLinkInput'
responses:
'200':
description: Canonical system collection link emitted through `SystemOperationRunLinks::index()`.
content:
application/vnd.tenantpilot.operation-link+json:
schema:
$ref: '#/components/schemas/CanonicalOperationLink'
/internal/operation-run-links/system/detail:
post:
summary: Build the canonical system operation detail URL for a covered system source surface.
operationId: buildSystemOperationDetailLink
x-not-public-http: true
requestBody:
required: true
content:
application/vnd.tenantpilot.system-operation-detail-input+json:
schema:
$ref: '#/components/schemas/SystemOperationDetailLinkInput'
responses:
'200':
description: Canonical system detail link emitted through `SystemOperationRunLinks::view(...)`.
content:
application/vnd.tenantpilot.operation-link+json:
schema:
$ref: '#/components/schemas/CanonicalOperationLink'
/internal/guards/operation-run-link-contract/check:
post:
summary: Scan the bounded app-side source surface for raw operation-run link bypasses.
operationId: checkOperationRunLinkContract
x-not-public-http: true
requestBody:
required: true
content:
application/vnd.tenantpilot.operation-run-link-guard-input+json:
schema:
$ref: '#/components/schemas/OperationRunLinkGuardCheck'
responses:
'200':
description: Guard completed and found no violations inside the declared boundary.
content:
application/vnd.tenantpilot.operation-run-link-guard-report+json:
schema:
$ref: '#/components/schemas/OperationRunLinkGuardReport'
'422':
description: Guard found one or more raw bypasses outside the allowlist.
content:
application/vnd.tenantpilot.operation-run-link-guard-report+json:
schema:
$ref: '#/components/schemas/OperationRunLinkGuardReport'
/admin/operations:
get:
summary: Existing canonical admin operations collection route.
operationId: openAdminOperationsCollection
responses:
'200':
description: Admin monitoring collection renders for an entitled workspace operator.
content:
text/html:
schema:
type: string
'403':
description: Actor is in scope but lacks the required capability.
'404':
description: Actor is not entitled to the workspace or tenant-bound records referenced by the current context.
/admin/operations/{run}:
get:
summary: Existing canonical admin operation detail route.
operationId: openAdminOperationDetail
parameters:
- name: run
in: path
required: true
schema:
type: integer
responses:
'200':
description: Canonical admin run detail renders for an entitled operator.
content:
text/html:
schema:
type: string
'403':
description: Actor is a member in scope but lacks current capability.
'404':
description: Actor cannot access the run because of workspace, tenant, or plane isolation.
/system/ops/runs:
get:
summary: Existing canonical system operations collection route.
operationId: openSystemOperationsCollection
responses:
'200':
description: System monitoring collection renders for an entitled platform user.
content:
text/html:
schema:
type: string
'403':
description: Platform user lacks the required system operations capability.
'404':
description: Actor is not entitled to the system plane.
/system/ops/runs/{run}:
get:
summary: Existing canonical system operation detail route.
operationId: openSystemOperationDetail
parameters:
- name: run
in: path
required: true
schema:
type: integer
responses:
'200':
description: Canonical system run detail renders for an entitled platform user.
content:
text/html:
schema:
type: string
'403':
description: Platform user lacks the required system operations capability.
'404':
description: Actor is not entitled to the system plane or the run is not visible there.
components:
schemas:
OperationPlane:
type: string
enum:
- admin
- system
OperationLinkKind:
type: string
enum:
- collection
- detail
CoveredSurfaceKey:
type: string
description: Stable identifier for the source surface that emits the canonical link.
CanonicalNavigationContextInput:
type: object
description: |
Opaque helper-owned navigation context payload passed through the admin helper family
when a source surface needs canonical back-link or query continuity.
additionalProperties: true
AdminOperationCollectionLinkInput:
type: object
required:
- surfaceKey
properties:
surfaceKey:
$ref: '#/components/schemas/CoveredSurfaceKey'
tenantId:
type: integer
tenantExternalId:
type: string
navigationContext:
$ref: '#/components/schemas/CanonicalNavigationContextInput'
activeTab:
type: string
problemClass:
type: string
operationType:
type: string
description: Optional helper-owned operations table type filter, used by inventory coverage history links.
allTenants:
type: boolean
default: false
description: Canonical input for `OperationRunLinks::index(...)`.
AdminOperationDetailLinkInput:
type: object
required:
- surfaceKey
- runId
properties:
surfaceKey:
$ref: '#/components/schemas/CoveredSurfaceKey'
runId:
type: integer
navigationContext:
$ref: '#/components/schemas/CanonicalNavigationContextInput'
description: Canonical input for `OperationRunLinks::view(...)` or `tenantlessView(...)`.
SystemOperationCollectionLinkInput:
type: object
required:
- surfaceKey
properties:
surfaceKey:
$ref: '#/components/schemas/CoveredSurfaceKey'
description: Canonical input for `SystemOperationRunLinks::index()`.
SystemOperationDetailLinkInput:
type: object
required:
- surfaceKey
- runId
properties:
surfaceKey:
$ref: '#/components/schemas/CoveredSurfaceKey'
runId:
type: integer
description: Canonical input for `SystemOperationRunLinks::view(...)`.
CanonicalOperationLink:
type: object
required:
- label
- url
- plane
- kind
- canonicalNoun
properties:
label:
type: string
url:
type: string
plane:
$ref: '#/components/schemas/OperationPlane'
kind:
$ref: '#/components/schemas/OperationLinkKind'
canonicalNoun:
type: string
example: Operation
preservedQueryKeys:
type: array
items:
type: string
description: Helper-owned operator-facing link output for canonical operations destinations.
OperationRunLinkGuardCheck:
type: object
required:
- includePaths
- allowlistedPaths
- forbiddenPatterns
properties:
includePaths:
type: array
items:
type: string
examples:
- - app/Filament/Widgets/Tenant/RecentOperationsSummary.php
- app/Filament/Pages/InventoryCoverage.php
- app/Filament/Resources/InventoryItemResource.php
- app/Filament/Resources/ReviewPackResource.php
- app/Filament/Pages/Operations/TenantlessOperationRunViewer.php
- app/Support/Navigation/RelatedNavigationResolver.php
- app/Filament/System/Pages/Directory/ViewTenant.php
- app/Filament/System/Pages/Directory/ViewWorkspace.php
- app/Filament/System/Pages/Ops/Runs.php
- app/Filament/System/Pages/Ops/ViewRun.php
- app/Providers/Filament/AdminPanelProvider.php
- app/Providers/Filament/TenantPanelProvider.php
- app/Support/Middleware/EnsureFilamentTenantSelected.php
- app/Http/Controllers/ClearTenantContextController.php
- app/Support/OpsUx/OperationRunUrl.php
allowlistedPaths:
type: array
items:
type: string
examples:
- - app/Providers/Filament/AdminPanelProvider.php
- app/Providers/Filament/TenantPanelProvider.php
- app/Support/Middleware/EnsureFilamentTenantSelected.php
- app/Http/Controllers/ClearTenantContextController.php
forbiddenPatterns:
type: array
items:
type: string
examples:
- - "route('admin.operations.index'"
- "route('admin.operations.view'"
- "/system/ops/runs"
- "Runs::getUrl("
- "ViewRun::getUrl("
acceptedDelegates:
type: array
items:
type: string
examples:
- - app/Support/OpsUx/OperationRunUrl.php
description: Declares the bounded source surface and explicit exceptions for the guard.
OperationRunLinkGuardViolation:
type: object
required:
- filePath
- line
- snippet
- reason
properties:
filePath:
type: string
line:
type: integer
snippet:
type: string
expectedHelper:
type: string
reason:
type: string
description: Actionable failure output for one raw bypass.
OperationRunLinkGuardReport:
type: object
required:
- scannedPaths
- allowlistedPaths
- violations
properties:
scannedPaths:
type: array
items:
type: string
allowlistedPaths:
type: array
items:
type: string
acceptedDelegates:
type: array
items:
type: string
violations:
type: array
items:
$ref: '#/components/schemas/OperationRunLinkGuardViolation'
description: Report shape returned by the bounded guard check.

View File

@ -0,0 +1,199 @@
# Data Model: Operation Run Link Contract Enforcement
## Overview
This feature introduces no new persisted business entity. Existing `OperationRun` records, workspace and tenant authorization truth, and canonical operations destination pages remain authoritative. The new work is a derived link-generation and guard contract over those existing records and helper families.
## Existing Persistent Entities
### OperationRun
**Purpose**: Canonical runtime and monitoring truth for operation collection and detail destinations.
**Key fields used by this feature**:
- `id`
- `workspace_id`
- `tenant_id`
- `type`
- `status`
- `outcome`
- `context`
**Rules relevant to this feature**:
- Admin-plane and system-plane detail links resolve to existing canonical monitoring surfaces; the feature does not add a new route family.
- Tenant-bound runs remain subject to destination-side entitlement checks even when the source link carries canonical tenant continuity.
- The feature changes how source surfaces build URLs, not how `OperationRun` lifecycle truth is persisted.
### Tenant
**Purpose**: Existing tenant scope and entitlement anchor for admin-plane collection continuity and tenant-bound run inspection.
**Key fields used by this feature**:
- `id`
- `external_id`
- `workspace_id`
- `name`
**Rules relevant to this feature**:
- Admin-plane collection links may preserve entitled tenant context only through helper-supported parameters.
- Detail links never create a tenant-prefixed duplicate route; tenant relevance is enforced at the destination against the run itself.
### Workspace
**Purpose**: Existing workspace isolation boundary for canonical admin monitoring routes.
**Key fields used by this feature**:
- `id`
- membership and capability truth via existing authorization helpers
**Rules relevant to this feature**:
- Non-members remain `404` on canonical admin monitoring routes.
- The feature does not add any new workspace-scoped persistence or copied navigation records.
## Derived Models
### AdminOperationCollectionLinkInput
**Purpose**: Canonical input model for helper-owned admin collection links.
**Fields**:
- `surfaceKey`
- `tenantId` or `tenantExternalId` when the source surface owns entitled tenant continuity
- `navigationContext`
- `activeTab`
- `problemClass`
- `allTenants`
**Validation rules**:
- Tenant context is included only when the source surface already owns an entitled tenant.
- `activeTab`, `problemClass`, and `allTenants` remain limited to current helper-supported semantics.
- Collection URLs are always emitted by `OperationRunLinks::index(...)`.
### AdminOperationDetailLinkInput
**Purpose**: Canonical input model for helper-owned admin detail links.
**Fields**:
- `surfaceKey`
- `runId`
- `navigationContext`
**Validation rules**:
- Detail links are emitted only through `OperationRunLinks::view(...)` or `OperationRunLinks::tenantlessView(...)`.
- No source surface may mint a tenant-prefixed or surface-local duplicate detail route.
### SystemOperationCollectionLinkInput
**Purpose**: Canonical input model for helper-owned system collection links.
**Fields**:
- `surfaceKey`
**Validation rules**:
- Collection links are emitted only through `SystemOperationRunLinks::index()`.
- System-plane collection links never fall back to admin-plane monitoring.
### SystemOperationDetailLinkInput
**Purpose**: Canonical input model for helper-owned system detail links.
**Fields**:
- `surfaceKey`
- `runId`
**Validation rules**:
- Detail links are emitted only through `SystemOperationRunLinks::view(...)`.
- System-plane detail links never fall back to admin-plane monitoring.
### CoveredLinkProducer
**Purpose**: Planning and guard model for every app-side source that emits an `OperationRun` collection or detail link.
**Fields**:
- `surfaceKey`
- `filePath`
- `plane` (`admin`, `system`)
- `linkKind` (`collection`, `detail`, `both`)
- `contractState` (`migrated`, `verified_helper_backed`, `allowlisted_exception`)
- `justification`
**State transitions**:
- `raw_bypass` -> `migrated`
- `raw_bypass` -> `allowlisted_exception`
- `existing_helper_path` -> `verified_helper_backed`
- `thin_delegate` -> `verified_helper_backed`
**Rules**:
- Every first-slice producer must end in either `migrated`, `verified_helper_backed`, or `allowlisted_exception`.
- `allowlisted_exception` is valid only for infrastructure or redirect code that should not absorb UI-context dependencies.
- `verified_helper_backed` is valid for already-converged system producers and thin delegates that forward directly to the canonical helper family.
### OperationRunLinkGuardReport
**Purpose**: Derived failure output for the bounded regression guard.
**Fields**:
- `scannedPaths[]`
- `allowlistedPaths[]`
- `acceptedDelegates[]`
- `violations[]`
### GuardViolation
**Purpose**: Actionable output for a newly detected raw bypass.
**Fields**:
- `filePath`
- `line`
- `snippet`
- `expectedHelper` (optional)
- `reason`
**Rules**:
- When present, `expectedHelper` points to a concrete replacement path such as `OperationRunLinks::index(...)`, `OperationRunLinks::view(...)`, or `SystemOperationRunLinks::view(...)`.
- Violations are limited to the declared guard boundary and must not report tests or helper implementations themselves.
## Consumer Matrix
| Producer | Plane | Link kinds | Target state |
|----------|-------|------------|--------------|
| `RecentOperationsSummary` | admin | collection | migrated |
| `InventoryCoverage` | admin | collection + detail | migrated |
| `InventoryItemResource` | admin | detail | migrated |
| `ReviewPackResource` | admin | detail | migrated |
| `TenantlessOperationRunViewer` | admin | collection fallbacks | migrated |
| `RelatedNavigationResolver` | admin | detail | migrated |
| `AdminPanelProvider` | admin | collection nav shortcut | allowlisted exception |
| `TenantPanelProvider` | admin | collection nav shortcut | allowlisted exception |
| `EnsureFilamentTenantSelected` | admin | collection redirect shortcut | allowlisted exception |
| `ClearTenantContextController` | admin | collection redirect fallback | allowlisted exception |
| `ViewTenant` | system | collection + detail | verified helper-backed |
| `ViewWorkspace` | system | collection + detail | verified helper-backed |
| `Runs` | system | collection + detail | verified helper-backed |
| `ViewRun` | system | collection + detail | verified helper-backed |
## Persistence Boundaries
- No new table, enum-backed state, cache record, or presentation-only persistence is introduced.
- The producer inventory and allowlist are repository-level planning and guard artifacts, not product-domain records.
- Canonical navigation context remains derived request state owned by existing helper and navigation abstractions.

View File

@ -0,0 +1,230 @@
# Implementation Plan: Operation Run Link Contract Enforcement
**Branch**: `232-operation-run-link-contract` | **Date**: 2026-04-23 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/232-operation-run-link-contract/spec.md`
## Summary
Enforce one canonical contract for platform-owned `OperationRun` collection and detail links by migrating confirmed raw admin-plane producers to `OperationRunLinks`, preserving and regression-protecting the existing system-plane helper path through `SystemOperationRunLinks`, recording only the narrow infrastructure exceptions that cannot safely depend on the helper family, and adding one bounded regression guard that blocks new raw bypasses inside the declared UI and shared-navigation boundary.
## Technical Context
**Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4
**Primary Dependencies**: Filament Resources/Pages/Widgets, Pest v4, `App\Support\OperationRunLinks`, `App\Support\System\SystemOperationRunLinks`, `App\Support\Navigation\CanonicalNavigationContext`, `App\Support\Navigation\RelatedNavigationResolver`, existing workspace and tenant authorization helpers
**Storage**: PostgreSQL-backed existing `operation_runs`, `tenants`, and `workspaces` records plus current session-backed canonical navigation state; no new persistence
**Testing**: Focused Pest feature tests for canonical link behavior, representative admin drill-throughs, admin and system authorization semantics, and one bounded guard test
**Validation Lanes**: `fast-feedback`, `confidence`
**Target Platform**: Laravel admin web application running in Sail Linux containers, with admin plane at `/admin` and platform plane at `/system`
**Project Type**: Monorepo with one Laravel runtime in `apps/platform` and spec artifacts at repository root
**Performance Goals**: Link generation remains helper-owned string construction with no new remote work, no new persisted navigation state, and no broadened destination queries beyond existing canonical helper semantics
**Constraints**: No new operations route family, no second link presenter stack, no compatibility shim layer, no weakening of `404` versus `403` semantics, and no repo-wide guard broadening beyond platform-owned UI and shared navigation code in this slice
**Scale/Scope**: Migrate confirmed raw admin-plane producers in widgets, pages, resources, and shared navigation; validate already-helper-backed system-plane producers; keep bootstrapping and redirect exceptions explicit and narrow
## Filament v5 Implementation Contract
- **Livewire v4.0+ compliance**: Preserved. The feature stays inside existing Filament v5 and Livewire v4 primitives and does not introduce legacy Livewire v3 patterns.
- **Provider registration location**: Unchanged. Panel providers remain registered in `bootstrap/providers.php`, not `bootstrap/app.php`.
- **Global search coverage**:
- `InventoryItemResource` remains compatible with global search expectations because it already exposes a `view` page.
- `ReviewPackResource` keeps global search disabled via `$isGloballySearchable = false` while still exposing a `view` page for direct navigation.
- The affected pages, widgets, and shared navigation helpers do not introduce or change any additional global-search surface.
- **Destructive actions**: No new destructive actions are introduced. Existing destructive or destructive-like actions on operations surfaces remain governed by their current `Action::make(...)->action(...)` definitions, and existing system run-detail actions already retain `->requiresConfirmation()`.
- **Asset strategy**: No new panel-only or shared assets are planned. Deployment expectations remain unchanged, including `cd apps/platform && php artisan filament:assets` only when registered Filament assets change.
- **Testing plan**: Prove the feature with focused feature coverage on canonical admin links, representative dashboard and shared-resolver drill-throughs, explicit admin `404`/`403` authorization preservation, the new bounded guard, and system-plane continuity plus authorization semantics. No browser or heavy-governance family is needed for this plan.
## UI / Surface Guardrail Plan
- **Guardrail scope**: Changed admin monitoring collection/detail link producers, shared related-navigation builders, helper-owned URL-query continuity, and system-plane regression protection for canonical operations links
- **Native vs custom classification summary**: Mixed shared-family change using native Filament resources/pages/widgets plus existing shared link helpers
- **Shared-family relevance**: Navigation, action links, related links, deep links, and canonical monitoring entry points
- **State layers in scope**: `page`, `detail`, and helper-owned `URL-query` continuity
- **Handling modes by drift class or surface**: Hard-stop for new raw bypasses inside the declared guard boundary; review-mandatory for explicit infrastructure exceptions
- **Repository-signal treatment**: Review-mandatory because the feature adds a bounded repo guard and a named exception list
- **Special surface test profiles**: `standard-native-filament`, `monitoring-state-page`
- **Required tests or manual smoke**: `functional-core`, `state-contract`, `manual-smoke`
- **Exception path and spread control**: One named exception boundary for bootstrapping, middleware, and redirect surfaces that cannot safely depend on runtime navigation context; each retained path must be file-specific and justified
- **Active feature PR close-out entry**: `Guardrail`
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes
- **Systems touched**: `OperationRunLinks`, `SystemOperationRunLinks`, `CanonicalNavigationContext`, `RecentOperationsSummary`, `InventoryCoverage`, `InventoryItemResource`, `ReviewPackResource`, `TenantlessOperationRunViewer`, `RelatedNavigationResolver`, panel navigation providers, tenant-selection middleware, and clear-tenant-context redirect behavior
- **Shared abstractions reused**: `App\Support\OperationRunLinks`, `App\Support\System\SystemOperationRunLinks`, `App\Support\Navigation\CanonicalNavigationContext`; the existing `App\Support\OpsUx\OperationRunUrl` wrapper remains acceptable where it simply delegates to `OperationRunLinks`
- **New abstraction introduced? why?**: none; the only new structure is a test-local allowlist boundary for legitimate raw producers
- **Why the existing abstraction was sufficient or insufficient**: The helper families already encode canonical labels, admin/system plane selection, entitled tenant continuity, and canonical query semantics. The only current-release gap is adoption and enforcement on specific producers that still assemble routes locally.
- **Bounded deviation / spread control**: Infrastructure-only exceptions are explicit, file-scoped, and non-precedential. UI surfaces, shared navigation, and related-link builders stay on the helper path.
## Constitution Check
*GATE: Passed before Phase 0 research. Re-checked after Phase 1 design: still passed with one bounded guard and no new persisted truth.*
| Gate | Status | Plan Notes |
|------|--------|------------|
| Inventory-first / read-write separation | PASS | The feature changes link generation only. It introduces no new writes, no new restore or remediation flow, and no new persisted artifact. |
| RBAC, workspace isolation, tenant isolation | PASS | Admin-plane destinations keep workspace and tenant entitlement checks; system-plane destinations remain platform-only; cross-plane access stays `404`. |
| Run observability / Ops-UX | PASS | Existing operations pages remain the canonical monitoring destination, but the feature does not start, mutate, or reclassify `OperationRun` lifecycle behavior. |
| Shared pattern first | PASS | The plan converges on `OperationRunLinks` and `SystemOperationRunLinks` instead of introducing a second link presenter or navigation framework. |
| Proportionality / no premature abstraction | PASS | The change is confined to migrating confirmed producers plus one bounded guard allowlist. No new registry, resolver family, or persisted truth is introduced. |
| UI semantics / Filament-native discipline | PASS | Existing Filament pages, resources, widgets, and navigation items remain intact; only helper-owned URLs change. No ad-hoc UI replacement or new action chrome is introduced. |
| Test governance | PASS | Proof stays in focused feature lanes with a narrow guard boundary. No browser lane or heavy-governance promotion is required for this slice. |
## Test Governance Check
- **Test purpose / classification by changed surface**: `Feature` for link continuity, representative admin drill-throughs, authorization-path preservation, and the bounded guard
- **Affected validation lanes**: `fast-feedback`, `confidence`
- **Why this lane mix is the narrowest sufficient proof**: The business truth is helper-owned URL generation, correct plane and scope continuity, and bounded prevention of new raw bypasses. Those behaviors are fully provable with targeted feature tests and one repo-guard test; browser coverage would add cost without validating additional business logic.
- **Narrowest proving command(s)**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/CanonicalViewRunLinksTest.php tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php tests/Feature/Filament/InventoryCoverageRunContinuityTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php tests/Feature/078/RelatedLinksOnDetailTest.php tests/Feature/RunAuthorizationTenantIsolationTest.php tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php tests/Feature/System/Spec113/AuthorizationSemanticsTest.php tests/Feature/Guards/OperationRunLinkContractGuardTest.php`
- **Fixture / helper / factory / seed / context cost risks**: Minimal. Existing `OperationRun` factories, workspace membership helpers, tenant fixtures, and platform-user fixtures are sufficient.
- **Expensive defaults or shared helper growth introduced?**: No. The guard allowlist remains opt-in and local to this feature; no new global test helper is required.
- **Heavy-family additions, promotions, or visibility changes**: none
- **Surface-class relief / special coverage rule**: Standard native-Filament relief plus the existing `monitoring-state-page` profile for canonical operations destinations
- **Closing validation and reviewer handoff**: Re-run `pint`, then the focused test command above. Reviewers should verify that each migrated source now uses the correct helper family, that remaining raw producers sit only in the named exception boundary, and that both admin and system authorization semantics are unchanged.
- **Budget / baseline / trend follow-up**: none
- **Review-stop questions**: Did the guard accidentally absorb tests or non-operator infrastructure? Did any admin-plane producer still hand-assemble `admin.operations` URLs? Did any system-plane producer start bypassing `SystemOperationRunLinks`? Did any exception remain convenience-based instead of infrastructure-based?
- **Escalation path**: `document-in-feature`
- **Active feature PR close-out entry**: `Guardrail`
- **Why no dedicated follow-up spec is needed**: This is current-release contract enforcement around an existing shared interaction family. Only a later desire for repo-wide routing policy would justify a separate governance spec.
## Project Structure
### Documentation (this feature)
```text
specs/232-operation-run-link-contract/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── operation-run-link-contract.logical.openapi.yaml
└── tasks.md
```
### Source Code (repository root)
```text
apps/platform/
├── app/
│ ├── Filament/
│ │ ├── Pages/
│ │ │ ├── InventoryCoverage.php
│ │ │ └── Operations/TenantlessOperationRunViewer.php
│ │ ├── Resources/
│ │ │ ├── InventoryItemResource.php
│ │ │ └── ReviewPackResource.php
│ │ └── Widgets/Tenant/RecentOperationsSummary.php
│ ├── Http/Controllers/ClearTenantContextController.php
│ ├── Providers/Filament/
│ │ ├── AdminPanelProvider.php
│ │ └── TenantPanelProvider.php
│ └── Support/
│ ├── Middleware/EnsureFilamentTenantSelected.php
│ ├── Navigation/
│ │ ├── CanonicalNavigationContext.php
│ │ └── RelatedNavigationResolver.php
│ ├── OperationRunLinks.php
│ ├── OpsUx/OperationRunUrl.php
│ └── System/SystemOperationRunLinks.php
└── tests/
└── Feature/
├── 078/RelatedLinksOnDetailTest.php
├── 144/CanonicalOperationViewerDeepLinkTrustTest.php
├── Filament/
│ ├── InventoryCoverageRunContinuityTest.php
│ └── RecentOperationsSummaryWidgetTest.php
├── Guards/OperationRunLinkContractGuardTest.php
├── Monitoring/OperationsDashboardDrillthroughTest.php
├── OpsUx/CanonicalViewRunLinksTest.php
├── ReviewPack/ReviewPackResourceTest.php
├── RunAuthorizationTenantIsolationTest.php
└── System/
├── Spec113/AuthorizationSemanticsTest.php
└── Spec195/SystemDirectoryResidualSurfaceTest.php
```
**Structure Decision**: Single Laravel application inside the monorepo. Runtime changes stay inside `apps/platform`, while planning artifacts remain under `specs/232-operation-run-link-contract`.
## Complexity Tracking
No constitutional violation is planned. One bounded review artifact is tracked explicitly because the feature adds an allowlisted guard boundary.
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| BLOAT-001 bounded exception inventory | The guard needs a small explicit exception list so bootstrapping and redirect code does not have to fake helper usage or fabricate runtime context it does not own. | A raw repo-wide ban without named exceptions would create false positives, blur the operator-facing boundary, and push the feature into heavy-governance scope. |
## Proportionality Review
- **Current operator problem**: Platform-owned admin and system surfaces still have parallel ways to open the same canonical operations destinations, which creates plane and scope drift and keeps the next contributor free to reintroduce raw route assembly.
- **Existing structure is insufficient because**: The helper families already exist, but they are optional in practice. Without a bounded enforcement slice, the repository keeps two competing link-generation paths.
- **Narrowest correct implementation**: Migrate the confirmed raw admin producers, preserve the existing system helper path, record the few legitimate infrastructure exceptions, and add one route-bounded guard that fails on new bypasses.
- **Ownership cost created**: Small ongoing maintenance of the exception list and targeted regression coverage for a shared interaction family.
- **Alternative intentionally rejected**: A repo-wide route-string ban or a new navigation presenter stack. Both would be broader than the current operator problem and would add governance or architecture cost the release does not need.
- **Release truth**: Current-release contract enforcement and cleanup.
## Phase 0 Research Summary
- Reuse `OperationRunLinks` for every admin-plane producer that opens `/admin/operations` or `/admin/operations/{run}`; do not create a second admin helper or presenter.
- Treat the currently confirmed raw admin-plane producers as first-slice migration targets: `RecentOperationsSummary`, `InventoryCoverage`, `InventoryItemResource`, `ReviewPackResource`, `TenantlessOperationRunViewer`, and `RelatedNavigationResolver`.
- Keep the initial explicit exception boundary narrow and infrastructure-only: panel providers, tenant-selection middleware, and clear-tenant-context redirects may stay raw if helper adoption would fabricate the wrong runtime context.
- Preserve `SystemOperationRunLinks` as the canonical system-plane path. Current system widgets and pages are already largely converged, so the first slice focuses on regression prevention rather than inventing synthetic migration work.
- Treat `OperationRunUrl` as an acceptable thin delegating seam because it forwards directly to `OperationRunLinks` and does not create parallel routing truth.
- Keep the guard route-bounded to platform-owned UI and shared navigation code under `apps/platform/app`; do not scan tests, helpers themselves, or unrelated infrastructure.
## Phase 1 Design Summary
- `data-model.md` documents the feature as a derived contract over existing `OperationRun`, tenant, workspace, and canonical navigation truth plus a bounded producer inventory and explicit exception model.
- `contracts/operation-run-link-contract.logical.openapi.yaml` defines the internal logical contract for canonical admin/system collection and detail links plus the bounded guard request and result shape.
- `quickstart.md` provides the focused validation path for representative admin drill-throughs, system-plane continuity, allowlisted exceptions, and authorization semantics.
## Implementation Close-Out Inventory
- **Migrated admin producers**: `RecentOperationsSummary` collection links now use `OperationRunLinks::index(...)`; `InventoryCoverage` basis/detail and inventory-sync history links now use `OperationRunLinks::view(...)` and helper-owned type filtering; `InventoryItemResource` and `ReviewPackResource` detail links now use `OperationRunLinks::view(...)` or `tenantlessView(...)`; `TenantlessOperationRunViewer` default collection fallbacks now use `OperationRunLinks::index()`; `RelatedNavigationResolver` operation-run audit target links now use `OperationRunLinks::tenantlessView(...)`.
- **Verified system producers**: `ViewTenant`, `ViewWorkspace`, `Runs`, and `ViewRun` remain on `SystemOperationRunLinks::index()` and `SystemOperationRunLinks::view(...)` with no admin-plane fallback.
- **Accepted delegate**: `App\Support\OpsUx\OperationRunUrl` remains a thin delegate to `OperationRunLinks` and is explicitly covered by helper-contract tests.
- **Allowlisted infrastructure exceptions**: `AdminPanelProvider`, `TenantPanelProvider`, `EnsureFilamentTenantSelected`, and `ClearTenantContextController` retain raw `admin.operations.index` routes because they own panel bootstrapping or redirect behavior rather than source-surface drill-through context.
- **Guard boundary**: `OperationRunLinkContractGuardTest` scans the migrated admin producers, verified system producers, accepted delegate, and four explicit infrastructure exceptions. It blocks raw `admin.operations.index`, raw `admin.operations.view`, direct `/system/ops/runs` paths, and direct `Runs::getUrl(...)` / `ViewRun::getUrl(...)` use outside the allowlist.
- **Test-governance disposition**: `document-in-feature`. The cost is contained to a focused feature guard and representative feature coverage; no follow-up spec or heavy-governance lane is needed.
## Phase 1 Agent Context Update
- Run `.specify/scripts/bash/update-agent-context.sh copilot` after the plan, research, data model, quickstart, and contract artifacts are written.
- The update must preserve manual additions between generated markers and add only the new technology and change notes relevant to Spec 232.
- The generated agent-context file is supporting output for the planning workflow, not a reason to widen the feature scope.
## Implementation Strategy
1. **Inventory and classify producers**
- Freeze the first-slice inventory of raw admin-plane link producers and distinguish between migration targets, verified helper-backed system producers, and explicit infrastructure exceptions.
2. **Migrate direct admin collection and detail links**
- Replace raw `route('admin.operations.index')` and `route('admin.operations.view')` usage in widgets, pages, and resources with `OperationRunLinks` methods.
- Preserve canonical tenant continuity, problem-class filters, active-tab semantics, and current operator-facing labels through the helper contract only.
3. **Normalize shared related-navigation and back-link paths**
- Refactor `RelatedNavigationResolver` and `TenantlessOperationRunViewer` to consume canonical helper methods rather than local route assembly.
- Keep back-link context helper-owned when `CanonicalNavigationContext` is present and degrade cleanly to the canonical admin collection when it is absent.
4. **Retain only justified infrastructure exceptions**
- Keep panel navigation providers, tenant-selection middleware, and clear-tenant-context redirects as the narrow allowlisted exception set for this slice.
- Record each allowlisted exception with a reason tied to bootstrapping or redirect ownership, not convenience.
5. **Protect the already-converged system plane**
- Audit verified helper-backed system pages and widgets and keep them on `SystemOperationRunLinks`, applying only minimal cleanup if a direct page URL slips through.
- Use the guard and authorization tests to prove that no platform-facing system producer regresses to admin-plane or direct page URL assembly.
6. **Add bounded regression coverage**
- Add one guard test that scans only the declared app-side boundary and fails with file-plus-snippet output on representative bypasses.
- Extend or update focused feature tests so admin-plane continuity, explicit admin `404`/`403` preservation, system-plane continuity, and negative authorization behavior remain explicit.
## Risks and Mitigations
- **False-positive guard scope**: A broad scan would catch tests or infrastructure code. Mitigation: keep the boundary on platform-owned UI and shared navigation files only and maintain a file-scoped allowlist for legitimate exceptions.
- **Tenant continuity drift**: Replacing raw URLs could accidentally drop canonical filters or navigation context. Mitigation: route collection and detail links through existing helper parameters and keep representative continuity tests in scope.
- **Back-link regression on run detail**: `TenantlessOperationRunViewer` currently mixes raw fallbacks with helper-owned detail refresh behavior. Mitigation: migrate both the back-to-operations and show-all-operations fallbacks in the same slice so behavior stays coherent.
- **Over-scoping into routing cleanup**: It is easy to turn this into a general route-string purge. Mitigation: keep the feature limited to `OperationRun` collection and detail link producers plus their bounded exception list.
## Post-Design Re-check
Phase 0 and Phase 1 outputs resolve the planning questions without introducing a new routing framework, new persisted navigation truth, or a repo-wide governance lane change. The plan remains constitution-compliant, helper-first, and ready for `/speckit.tasks`.

View File

@ -0,0 +1,97 @@
# Quickstart: Operation Run Link Contract Enforcement
## Prerequisites
1. Start the local platform stack.
```bash
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH"
cd apps/platform && ./vendor/bin/sail up -d
```
2. Work with:
- one workspace operator who can access canonical admin monitoring,
- one entitled tenant with recent `OperationRun` records,
- one second tenant that the operator must not be able to inspect, and
- one platform user who can access `/system/ops/runs`.
3. Remember that this feature changes link generation only. No frontend asset build should be required unless unrelated platform assets changed.
## Automated Validation
Run formatting and the narrowest proving suites for this feature:
```bash
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH"
cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH"
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/CanonicalViewRunLinksTest.php tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php tests/Feature/Filament/InventoryCoverageRunContinuityTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php tests/Feature/078/RelatedLinksOnDetailTest.php tests/Feature/RunAuthorizationTenantIsolationTest.php tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php tests/Feature/System/Spec113/AuthorizationSemanticsTest.php tests/Feature/Guards/OperationRunLinkContractGuardTest.php
```
## Final Guard Boundary
The implemented guard is bounded to the first-slice source surfaces and explicit infrastructure exceptions:
- **Migrated admin producers**: `RecentOperationsSummary`, `InventoryCoverage`, `InventoryItemResource`, `ReviewPackResource`, `TenantlessOperationRunViewer`, and `RelatedNavigationResolver`.
- **Verified system producers**: `ViewTenant`, `ViewWorkspace`, `Runs`, and `ViewRun`, all continuing through `SystemOperationRunLinks`.
- **Accepted thin delegate**: `App\Support\OpsUx\OperationRunUrl`, which forwards to `OperationRunLinks`.
- **Allowlisted infrastructure exceptions**: `AdminPanelProvider`, `TenantPanelProvider`, `EnsureFilamentTenantSelected`, and `ClearTenantContextController`.
- **Forbidden bypasses inside the boundary**: raw `route('admin.operations.index')`, raw `route('admin.operations.view')`, direct `/system/ops/runs` strings, and direct `Runs::getUrl(...)` or `ViewRun::getUrl(...)` outside `SystemOperationRunLinks`.
## Manual Validation Flow
### 1. Validate tenant-aware admin collection continuity
1. Open a tenant-facing surface that exposes the recent-operations summary or an inventory coverage follow-up link.
2. Follow the `Open operations` or equivalent history link.
3. Confirm the destination stays on `/admin/operations` and preserves only helper-supported tenant or filter continuity.
4. Confirm the page does not invent a tenant-prefixed duplicate operations route.
### 2. Validate canonical admin detail links from representative resource surfaces
1. Open one inventory item with a `last_seen_operation_run_id`.
2. Follow the `Last inventory sync` link.
3. Open one review pack with an associated `operation_run_id`.
4. Confirm both links open canonical admin run detail, not a surface-local route or raw fallback URL.
### 3. Validate shared related-navigation and back-link behavior
1. Open a surface that renders an `operation_run` related link through `RelatedNavigationResolver`.
2. Confirm the helper-generated label and URL match canonical admin run detail behavior.
3. Open `TenantlessOperationRunViewer` through a source without an explicit back-link context.
4. Confirm `Back to Operations` and `Show all operations` land on the canonical admin collection helper path.
### 4. Validate system-plane continuity
1. Open a system-plane widget or directory page with run drill-through.
2. Follow collection and detail links into monitoring.
3. Confirm the destination stays on `/system/ops/runs` or `/system/ops/runs/{run}` and does not fall back to `/admin/operations`.
### 5. Validate authorization semantics stayed unchanged
1. As a workspace member who is not entitled to a foreign tenant, request a canonical admin detail URL for that tenants run.
2. Confirm the response remains `404`.
3. As a non-platform user, request a system-plane operations URL.
4. Confirm the response remains `404`.
5. As an entitled actor missing the relevant capability, confirm current destination behavior still yields `403` where the route already distinguishes membership from capability denial.
### 6. Validate the explicit exception boundary
1. Confirm that navigation boot, middleware, and clear-tenant redirect behavior still function after the cleanup.
2. Review the named allowlist entries and verify each remaining raw producer is infrastructure-owned rather than convenience-owned.
3. Confirm no new operator-facing page, widget, or related-navigation builder remains on raw `admin.operations.*` assembly outside the allowlist.
### 7. Validate the guardrail
1. Use a temporary local probe or test fixture to simulate one representative raw `route('admin.operations.view', ...)` bypass inside the declared guard boundary without committing it.
2. Run the guard test.
3. Confirm it fails with actionable file and snippet output.
4. Replace the bypass with the canonical helper or move it into an explicitly justified exception and confirm the guard passes again.
## Reviewer Notes
- The feature stays Livewire v4.0+ compatible and does not change provider registration in `bootstrap/providers.php`.
- No new global-search surface is introduced; `InventoryItemResource` already has a view page and `ReviewPackResource` remains non-searchable.
- No destructive action or new asset behavior is introduced.
- The contract boundary is intentionally narrow: platform-owned UI and shared navigation code only.

View File

@ -0,0 +1,67 @@
# Research: Operation Run Link Contract Enforcement
## Decision 1: Reuse the existing helper families instead of introducing a new link presenter
**Decision**: Treat `App\Support\OperationRunLinks` as the single admin-plane contract and `App\Support\System\SystemOperationRunLinks` as the single system-plane contract for all in-scope `OperationRun` collection and detail links.
**Rationale**: The existing helper families already own canonical nouns, labels, plane separation, and admin query semantics. The current defect is incomplete adoption, not missing capability. Reusing those helpers satisfies XCUT-001 with the smallest possible change.
**Alternatives considered**:
- Create a new cross-plane navigation presenter or registry. Rejected because the repository already has concrete helper ownership for the exact routes this feature targets.
- Fix each raw producer locally without naming a shared contract. Rejected because it would not stop the next contributor from reintroducing raw route assembly.
## Decision 2: Scope the first migration wave to the confirmed raw admin-plane producers
**Decision**: The first implementation slice migrates the currently confirmed raw admin-plane producers in `RecentOperationsSummary`, `InventoryCoverage`, `InventoryItemResource`, `ReviewPackResource`, `TenantlessOperationRunViewer`, and `RelatedNavigationResolver`.
**Rationale**: A targeted scan of `apps/platform/app` shows these files still assemble `admin.operations.index` or `admin.operations.view` links directly inside platform-owned UI or shared navigation code. They represent the smallest set of real producers needed to satisfy FR-232-003, FR-232-014, and FR-232-015.
**Alternatives considered**:
- Full opportunistic cleanup of every `admin.operations.*` string in the repository. Rejected because tests and legitimate infrastructure redirects would expand the slice into heavy-governance work.
- Migrate only one or two surfaces. Rejected because the drift is already spread across widget, page, resource, and shared-resolver layers.
## Decision 3: Keep the initial allowlist narrow and infrastructure-only
**Decision**: Seed the explicit exception boundary with bootstrapping or redirect-owned raw producers such as `AdminPanelProvider`, `TenantPanelProvider`, `EnsureFilamentTenantSelected`, and `ClearTenantContextController`, and require file-specific justification for each retained exception.
**Rationale**: These producers sit in navigation boot, middleware, or redirect code where runtime canonical navigation context is not the owning abstraction. Forcing helper usage there can fabricate the wrong dependency shape or hide a deliberate redirect contract.
**Alternatives considered**:
- Ban all raw route usage with no exceptions. Rejected because it would create false positives in infrastructure code and push the feature into a broader governance lane.
- Allow convenience-based exceptions. Rejected because convenience is exactly what created the current drift.
## Decision 4: Treat the system plane as contract lock-in, not broad migration work
**Decision**: Preserve `SystemOperationRunLinks` as the canonical system-plane path and focus the system portion of the feature on regression protection and authorization proof rather than manufactured cleanup work.
**Rationale**: The app-side scan shows current system widgets and pages already use `SystemOperationRunLinks` consistently. The feature should document and protect that convergence instead of inflating the slice with unnecessary system refactors.
**Alternatives considered**:
- Expand the scope into general system routing cleanup. Rejected because the current-release problem is not missing on the system plane in app code.
- Ignore the system plane entirely. Rejected because FR-232-002, FR-232-008, and FR-232-016 still require explicit system-plane continuity protection.
## Decision 5: The guard stays route-bounded and app-side only
**Decision**: The new guard scans only the declared platform-owned UI and shared-navigation boundary in `apps/platform/app` and fails on raw `admin.operations.*` route assembly or direct system operations page URLs outside the explicit allowlist.
**Rationale**: The proving purpose is preventing new operator-facing bypasses, not banning route literals everywhere in the repository. A route-bounded guard keeps the feature in `fast-feedback + confidence` instead of escalating it into a structural repo-governance family.
**Alternatives considered**:
- Whole-repo scanning including tests and unrelated infrastructure. Rejected because it would create noisy failures and require a much broader allowlist.
- No guard at all. Rejected because the cleanup would then be a one-time repair with no contract enforcement.
## Decision 6: Keep thin delegating wrappers out of the violation set
**Decision**: Treat `App\Support\OpsUx\OperationRunUrl` as an acceptable thin seam because it delegates directly to `OperationRunLinks` and does not introduce parallel route truth.
**Rationale**: The spec targets raw bypass of the canonical helper families, not every intermediate call site that already converges on them. Counting a delegating wrapper as a violation would add churn without improving contract safety.
**Alternatives considered**:
- Force all wrapper users to switch to direct helper calls in the same slice. Rejected because the wrapper is not the source of drift and does not block enforcement of the raw-route contract.
- Allow arbitrary wrappers. Rejected because only pure delegating seams remain acceptable; a wrapper that adds its own routing logic would reintroduce the same problem.

View File

@ -0,0 +1,302 @@
# Feature Specification: Operation Run Link Contract Enforcement
**Feature Branch**: `232-operation-run-link-contract`
**Created**: 2026-04-23
**Status**: Draft
**Input**: User description: "schau in spec-candidate und roadmap rein und wähle das nächste sinnvolle spec"
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
- **Problem**: Platform-owned admin and system surfaces still emit `OperationRun` collection and detail links through raw route assembly instead of the shared helper families, so the same canonical operations destinations have two parallel generation paths.
- **Today's failure**: Operators can reach the right pages through URLs that ignore tenant-prefilter or plane semantics, while contributors can keep adding raw operation routes in shared UI code without tripping a contract failure.
- **User-visible improvement**: Operation links behave consistently across dashboards, resources, monitoring pages, and shared related-navigation so operators land in the canonical destination with the expected plane and scope continuity.
- **Smallest enterprise-capable version**: Inventory all platform-owned operation collection/detail link producers, migrate raw admin-plane producers to `OperationRunLinks`, verify already-helper-backed system producers stay on `SystemOperationRunLinks`, record the narrow allowlisted exceptions, and add one automated guard that blocks new raw bypasses.
- **Explicit non-goals**: No operations IA redesign, no page-content rewrite, no status-semantics change, no new tenant-specific operations route family, and no platform-wide routing cleanup beyond `OperationRun` collection/detail links.
- **Permanent complexity imported**: One explicit allowlist for legitimate infrastructure-only exceptions, narrow regression coverage for admin-plane and system-plane link producers, and small helper-contract extensions only if current canonical context parameters are incomplete.
- **Why now**: Governance & Architecture Hardening is the active roadmap block, and the 2026-04-22 drift audit identified this as the first high-leverage contract-enforcement slice before more operations surfaces or navigation retrofits land.
- **Why not local**: The drift exists across shared navigation and multiple platform-owned surfaces. A one-off fix on one widget or resource would leave the same contract bypass available everywhere else.
- **Approval class**: Cleanup
- **Red flags triggered**: Cross-cutting interaction-class scope and repository guardrail addition. Defense: the helper families already exist, the feature only makes them mandatory on platform-owned paths, and the guard stays bounded to explicit allowlisted exceptions instead of introducing a new framework.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 2 | Produktnähe: 1 | Wiederverwendung: 2 | **Gesamt: 11/12**
- **Decision**: approve
## Spec Scope Fields *(mandatory)*
- **Scope**: workspace + canonical-view + system
- **Primary Routes**:
- `/admin/operations`
- `/admin/operations/{run}`
- `/system/ops/runs`
- `/system/ops/runs/{run}`
- Existing platform-owned source surfaces that drill into those canonical destinations, including tenant recency widgets, inventory coverage follow-up links, review-pack operation links, panel navigation shortcuts, and shared related-navigation builders
- **Data Ownership**:
- No new persisted entity or ownership model is introduced.
- `operation_runs` remain the existing operational source of truth, tenant-owned in the admin plane with workspace-scoped canonical viewing rules and platform-plane visibility through existing system monitoring surfaces.
- Workspace- or system-page filter state remains derived navigation state only; this feature does not persist copied link state or introduce a second run-navigation record.
- **RBAC**:
- Admin-plane monitoring links continue to require workspace membership; tenant-linked destinations continue to enforce tenant entitlement and existing operations-view capabilities server-side.
- System-plane monitoring links continue to require authenticated platform users with existing system operations access.
- Cross-plane access remains deny-as-not-found; this feature does not introduce new capabilities or raw capability strings.
For canonical-view specs, the spec MUST define:
- **Default filter behavior when tenant-context is active**: Admin-plane collection links may preserve the active entitled tenant through the canonical `OperationRunLinks` collection contract. Admin-plane run detail remains record-authoritative and may carry canonical navigation context, but must not invent a tenant-specific duplicate detail route. System-plane links never inherit tenant-context filters from `/admin`.
- **Explicit entitlement checks preventing cross-tenant leakage**: Helper-generated links may carry only entitled tenant/query context; destination pages re-check workspace membership, tenant entitlement, and plane-specific access before rendering. A link from a covered surface must never make a foreign-tenant run or system route newly observable.
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
- **Cross-cutting feature?**: yes
- **Interaction class(es)**: navigation, action links, related links, deep links
- **Systems touched**: admin monitoring surfaces, system operations surfaces, shared related-navigation builders, panel shortcuts, and tenant/workspace drill-through links from recency and evidence-oriented surfaces
- **Existing pattern(s) to extend**: canonical operation link helpers, canonical navigation context propagation, and existing operations row-click/detail conventions
- **Shared contract / presenter / builder / renderer to reuse**: `App\Support\OperationRunLinks`, `App\Support\System\SystemOperationRunLinks`, and `App\Support\Navigation\CanonicalNavigationContext`
- **Why the existing shared path is sufficient or insufficient**: The shared path is already sufficient for collection/detail labels, tenant-prefilter continuity, active-tab/problem-class query semantics, and plane separation. The current gap is not missing capability but incomplete adoption where raw `route('admin.operations...')` or direct system page URLs still bypass the contract.
- **Allowed deviation and why**: Bounded infrastructure-only exceptions may remain when the producer cannot depend on the helper family without introducing the wrong runtime dependency or hiding a deliberate redirect contract. Every retained exception must be explicit and allowlisted.
- **Consistency impact**: `Operations` / `Operation` nouns, `Open operations` / `Open operation` labels, admin versus system plane routing, tenant-prefilter continuity, canonical back-link behavior, and shared related-link structure must stay aligned across all covered producers.
- **Review focus**: Reviewers must verify that new or changed platform-owned operation links use the correct helper family for their plane and that no new raw collection/detail route assembly appears outside the explicit allowlist.
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
Use this section to classify UI and surface risk once. If the feature does
not change an operator-facing surface, write `N/A - no operator-facing surface
change` here and do not invent duplicate prose in the downstream surface tables.
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Admin monitoring collection/detail link contract enforcement | yes | Native Filament + shared helpers | navigation / related links | table, detail header, linked widgets | no | Destination pages stay the same; only link generation is standardized |
| System operations collection/detail link contract enforcement | yes | Native Filament + shared helpers | navigation / related links | table, detail header | no | Destination pages stay the same; plane separation becomes explicit |
| Shared operation drill-through links from tenant/workspace surfaces | yes | Native Filament widgets/resources + shared helpers | navigation | widget, table cell, infolist/detail supporting links | no | Low-impact UI change; same destinations, stricter continuity semantics |
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
If this feature adds or materially changes an operator-facing surface,
fill out one row per affected surface. This role is orthogonal to the
Action Surface Class / Surface Type below. Reuse the exact surface names
and classifications from the UI / Surface Guardrail Impact section above.
| 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 |
|---|---|---|---|---|---|---|---|
| Admin monitoring collection and run detail | Secondary Context Surface | Open canonical monitoring after a tenant/workspace surface signals follow-up | Correct destination family, preserved tenant scope when valid, stable canonical operation identity | Full run diagnostics, raw context, related artifacts | Secondary because the decision to inspect happens on the source surface; monitoring remains the canonical context surface for follow-up and diagnosis | Follows existing dashboard/resource-to-monitoring workflow instead of creating duplicate run pages | Removes operator guesswork about which operations page and which scope a link should open |
| System operations collection and run detail | Secondary Context Surface | Open platform-plane run monitoring from system runbooks or system registries | Correct system destination, preserved platform plane, stable run identity | Full system run diagnostics and runbook context | Secondary because it supports platform triage after a runbook or system list already framed the operator task | Follows existing `/system` triage workflow and keeps system links out of `/admin` | Removes plane confusion and manual route reconstruction for platform operators |
## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*
If this feature adds or materially changes an operator-facing list, detail, queue, audit, config, or report surface,
fill out one row per affected surface. Declare the broad Action Surface
Class first, then the detailed Surface Type. Keep this table in sync
with the Decision-First Surface Role section above and avoid renaming the
same surface a second time.
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Admin monitoring collection | Read-only Registry / Report | Canonical monitoring registry | Open operation detail or inspect filtered run history | Full-row click or canonical helper-generated open link | required | Header/context filters and source-surface link affordances only | No new destructive placement; existing detail-only interventions remain unchanged | `/admin/operations` | `/admin/operations/{run}` | Workspace scope, optional entitled tenant filter, canonical navigation context | Operations / Operation | The operator can tell they are entering canonical admin monitoring and whether tenant context was preserved | none |
| Admin operation run detail | Canonical detail | Diagnostic run detail | Inspect one run without losing canonical plane semantics | Direct route-resolved detail page | forbidden | Related links and back-navigation remain secondary in the header/body | No new destructive actions in this feature | `/admin/operations` | `/admin/operations/{run}` | Workspace scope, entitled tenant context if present, run identity | Operations / Operation | The canonical run identity and plane are obvious without interpreting source-surface hacks | none |
| System operations collection | Read-only Registry / Report | Platform monitoring registry | Open system run detail | Full-row click or canonical helper-generated open link | required | Header filters or runbook follow-up only | Existing system interventions remain separately governed | `/system/ops/runs` | `/system/ops/runs/{run}` | Platform scope only | Operations / Operation | The operator can tell they are staying in the system plane | none |
| System run detail | Detail / Decision | Platform run triage detail | Inspect one platform run in the correct plane | Direct route-resolved detail page | forbidden | Header/context links only | Existing system-detail interventions remain separately governed | `/system/ops/runs` | `/system/ops/runs/{run}` | Platform scope, run identity, system context | Operations / Operation | The canonical system-run destination is explicit and not silently downgraded to admin monitoring | none |
## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*
If this feature adds a new operator-facing page or materially refactors
one, fill out one row per affected page/surface. The contract MUST show
how one governance case or operator task becomes decidable without
unnecessary cross-page reconstruction.
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Admin monitoring collection and run detail | Workspace operator | Follow a source-surface signal into canonical operation history or one canonical run detail | Registry + detail | Did I land on the right canonical operations surface with the right scope, and can I inspect the run from here? | Canonical collection/detail destination, run identity, workspace scope, entitled tenant continuity where applicable | Raw context payloads, error traces, and extended related links | lifecycle, outcome, problem class, scope continuity | No new mutation in this slice | Open operation, Open operations | None added by this feature |
| System operations collection and run detail | Platform operator | Follow runbook or system registry context into platform-plane run history or one system run detail | Registry + detail | Am I staying in the system plane, and am I opening the correct run history/detail surface? | Canonical system destination, run identity, platform scope | Raw run context, failure payloads, runbook lineage | lifecycle, outcome, platform scope | No new mutation in this slice | Open operation, Open operations | None added by this feature |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no
- **New persisted entity/table/artifact?**: no
- **New abstraction?**: no
- **New enum/state/reason family?**: no
- **New cross-domain UI framework/taxonomy?**: no
- **Current operator problem**: Covered surfaces can bypass the existing canonical operation-link contract, which makes plane and scope continuity drift across the same operations destinations.
- **Existing structure is insufficient because**: The helpers exist but are not enforced. Without one explicit enforcement slice and guardrail, every new platform-owned surface can keep choosing local route assembly.
- **Narrowest correct implementation**: Reuse the existing helper families, migrate all in-scope producers, and add one bounded allowlist guard instead of inventing a broader routing framework or new navigation model.
- **Ownership cost**: Small ongoing allowlist review, targeted regression maintenance, and occasional helper extensions when new canonical query semantics are introduced.
- **Alternative intentionally rejected**: A one-off cleanup of currently known raw routes without a guardrail was rejected because it would not stop the same drift from reappearing on the next surface.
- **Release truth**: current-release contract enforcement and cleanup, not future-platform preparation
### Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.
Canonical replacement is preferred over preservation.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
For docs-only or template-only changes, state concise `N/A` or `none`. For runtime- or test-affecting work, classification MUST follow the proving purpose of the change rather than the file path or folder name.
- **Test purpose / classification**: Feature
- **Validation lane(s)**: fast-feedback + confidence
- **Why this classification and these lanes are sufficient**: This change is proved by end-to-end URL generation and plane/scope outcomes on real admin/system surfaces, not by isolated string helpers alone. Fast-feedback covers representative admin/system surfaces and the guard. Confidence reruns the broader operations link-contract coverage already present in the monitoring family.
- **New or expanded test families**: Extend existing canonical run-link coverage for admin and system surfaces; add one focused guard test that blocks new raw platform-owned operation routes outside the allowlist.
- **Fixture / helper cost impact**: Minimal. Existing `OperationRun` factories, workspace/tenant membership helpers, and platform-user fixtures are sufficient.
- **Heavy-family visibility / justification**: none
- **Special surface test profile**: monitoring-state-page
- **Standard-native relief or required special coverage**: Requires shared-link contract coverage on representative widgets/resources plus one repository guard check; no browser or heavy-governance suite is needed.
- **Reviewer handoff**: Reviewers must confirm that each migrated producer emits helper-generated URLs for the correct plane, that allowlisted exceptions are explicitly justified, and that the guard pattern cannot be trivially bypassed.
- **Budget / baseline / trend impact**: none
- **Escalation needed**: none
- **Active feature PR close-out entry**: Guardrail
- **Planned validation commands**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/CanonicalViewRunLinksTest.php tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php tests/Feature/Filament/InventoryCoverageRunContinuityTest.php tests/Feature/ReviewPack/ReviewPackResourceTest.php tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php tests/Feature/078/RelatedLinksOnDetailTest.php tests/Feature/RunAuthorizationTenantIsolationTest.php tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php tests/Feature/System/Spec113/AuthorizationSemanticsTest.php tests/Feature/Guards/OperationRunLinkContractGuardTest.php`
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Follow Admin Operations Links Consistently (Priority: P1)
As a workspace operator, I want platform-owned admin surfaces to open canonical operations collection/detail links through one shared contract so that tenant-prefilter and run-detail continuity are consistent.
**Why this priority**: This is the direct operator-facing path. If admin-plane drill-through links remain inconsistent, the canonical monitoring experience stays fragile even when the destination pages themselves are correct.
**Independent Test**: Can be fully tested by opening operations links from covered tenant/workspace surfaces such as recency widgets, inventory coverage, and review-pack detail and confirming that the links land on the canonical admin collection/detail destinations with the expected scope continuity.
**Acceptance Scenarios**:
1. **Given** an active entitled tenant context, **When** an operator opens operations from a covered admin-plane surface, **Then** the link resolves to `/admin/operations` or `/admin/operations/{run}` with the expected tenant/context continuity and without inventing a duplicate route family.
2. **Given** no tenant context is active, **When** a covered admin-plane surface links to operations, **Then** it opens workspace-wide admin monitoring rather than inventing or leaking tenant scope.
3. **Given** a run belongs to a tenant the actor is not entitled to inspect, **When** the actor requests the destination, **Then** the destination still resolves as deny-as-not-found.
---
### User Story 2 - Keep System-Plane Run Links in the System Plane (Priority: P1)
As a platform operator, I want system run lists and runbooks to open canonical system operations URLs so that platform monitoring never silently routes me through the admin plane.
**Why this priority**: Plane correctness is a core trust requirement. A system operator following a run link to the wrong plane is both confusing and authorization-sensitive.
**Independent Test**: Can be fully tested by opening run history and run detail from covered `/system` surfaces and confirming that the destination remains `/system/ops/runs` or `/system/ops/runs/{run}` for platform users while tenant/admin users still cannot access those routes.
**Acceptance Scenarios**:
1. **Given** a system-plane list or follow-up link, **When** the platform operator opens a run, **Then** the URL is `/system/ops/runs/{run}` rather than an admin-plane monitoring route.
2. **Given** a system-plane collection link, **When** the platform operator opens history, **Then** the URL is `/system/ops/runs`.
3. **Given** a tenant/admin user session, **When** that user requests a system-plane destination, **Then** the response remains deny-as-not-found.
---
### User Story 3 - Prevent New Raw Operation-Link Bypasses (Priority: P2)
As a maintainer, I want a guardrail that fails when platform-owned UI introduces new raw operation routes outside allowlisted exceptions so that the contract stays enforced after cleanup.
**Why this priority**: The cleanup only holds if the next contributor cannot quietly reintroduce the same drift on a new surface.
**Independent Test**: Can be fully tested by introducing a representative raw route bypass in a covered area, observing the guard fail, then moving the same producer to the helper family or explicit allowlist and observing the guard pass.
**Acceptance Scenarios**:
1. **Given** a new platform-owned UI producer assembles `route('admin.operations...')` or direct system page URLs outside the allowlist, **When** the guard runs, **Then** the build fails with an actionable message.
2. **Given** an infrastructure-only exception is explicitly allowlisted, **When** the guard runs, **Then** it passes without forcing fake helper usage.
3. **Given** a new covered surface needs an additional canonical query semantic, **When** the contributor extends the helper family, **Then** the surface adopts the helper rather than adding a second local route pattern.
### Edge Cases
- A source surface has stale or absent tenant context; the admin collection link must degrade to workspace-wide canonical monitoring rather than carrying invalid tenant state.
- A source surface wants to prefilter by problem class or active tab; query semantics must be emitted through the helper contract only, not handwritten per surface.
- A run detail link is built from a tenant surface but the run belongs to a tenant the current actor can no longer access; opening the destination must still fail as `404`.
- A boot-time panel item or controller redirect cannot safely depend on view-context objects; if it remains raw, it must be explicitly allowlisted and justified.
- System-plane and admin-plane links for the same run ID must never resolve through the wrong plane by helper accident or local override.
## Requirements *(mandatory)*
**Constitution alignment (required):** This feature introduces no Microsoft Graph calls, no new mutation workflow, and no new `OperationRun` type. It standardizes link generation to already-shipped canonical operations destinations and must therefore make tenant isolation, plane separation, shared-link reuse, and regression coverage explicit.
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** This feature does not introduce new persistence, a new abstraction family, or new states. It deliberately reuses the existing helper families and adds only the narrowest enforcement mechanism needed to stop repeated drift on a current-release operator path.
**Constitution alignment (XCUT-001):** This is a cross-cutting navigation and action-link feature. The shared path is `OperationRunLinks` for the admin plane and `SystemOperationRunLinks` for the system plane, with `CanonicalNavigationContext` carrying canonical admin query state where needed. Any retained raw-route exception must be explicit, justified, and reviewable.
**Constitution alignment (TEST-GOV-001):** The proving purpose is runtime link behavior on real admin/system surfaces plus one repository guard. Feature tests and a focused guard are the narrowest sufficient proof. Existing factories and membership fixtures remain enough, and no heavy-governance or browser family is justified.
**Constitution alignment (OPS-UX):** Not applicable. This feature does not create, start, or mutate `OperationRun` records. Existing operations destinations remain the canonical Ops-UX surfaces and keep their current service-owned lifecycle semantics.
**Constitution alignment (RBAC-UX):** The feature spans the admin `/admin` plane and the platform `/system` plane. Cross-plane access remains `404`. Admin-plane destinations continue to enforce workspace membership first and tenant entitlement when the run is tenant-bound. System-plane destinations continue to enforce platform-user access only. The feature must add at least one positive and one negative authorization-path regression covering the affected link destinations.
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable. No authentication handshake behavior changes.
**Constitution alignment (BADGE-001):** Not applicable. No badge semantics are introduced or changed.
**Constitution alignment (UI-FIL-001):** The affected surfaces remain native Filament pages, widgets, and resources. No local replacement markup is needed. Semantic emphasis stays in existing tables, related-link areas, and header affordances while the destination URLs are delegated to the shared helper families.
**Constitution alignment (UI-NAMING-001):** The target object is the canonical operation destination. Operator verbs remain `Open operations`, `Open operation`, and `View in Operations` where those labels are already defined by the helper family. Source/domain disambiguation is only plane-based: admin-plane links open admin monitoring, system-plane links open system monitoring.
**Constitution alignment (DECIDE-001):** The affected surfaces are Secondary Context Surfaces. Their responsibility is not to create a new decision queue but to preserve a calm, predictable path from source surfaces into canonical monitoring without forcing operators to reconstruct plane or scope by hand.
**Constitution alignment (UI-CONST-001 / UI-SURF-001 / ACTSURF-001 / UI-HARD-001 / UI-EX-001 / UI-REVIEW-001 / HDR-001):** This feature does not add new visible row or header actions. It preserves one primary inspect/open model per affected surface, keeps pure navigation in row click or supporting related links, leaves destructive actions where existing destinations already govern them, and does not introduce a second operations route family.
**Constitution alignment (ACTSURF-001 - action hierarchy):** No new action hierarchy is introduced. Navigation remains separate from mutation, existing detail-only interventions remain where they are, and the feature does not turn route cleanup into a new action chrome pattern.
**Constitution alignment (OPSURF-001):** The default-visible operator experience remains operator-first because the change is to destination continuity, not content density. Raw route tokens and local path assembly must not leak into surface-specific logic. Diagnostics remain on the destination detail pages, not in the link producers.
**Constitution alignment (UI-SEM-001 / LAYER-001 / TEST-TRUTH-001):** No new UI-semantic or presenter layer is introduced. Direct helper reuse is preferred over another interpretation layer, and tests focus on business consequences: correct plane, correct destination, correct scope continuity, and blocked drift.
**Constitution alignment (Filament Action Surfaces):** The Action Surface Contract remains satisfied. Each affected surface keeps exactly one primary inspect/open model, redundant `View` actions are not introduced by this feature, empty placeholder groups remain forbidden, and destructive actions remain governed by the existing destination pages. `UI-FIL-001` is satisfied with no exception.
**Constitution alignment (UX-001 — Layout & Information Architecture):** Existing operations pages, widgets, and resources keep their current layouts, infolists, table search/sort/filter behavior, and empty-state structure. No layout exemption is needed because this slice changes destination generation only.
### Functional Requirements
- **FR-232-001**: The system MUST treat `App\Support\OperationRunLinks` as the canonical admin-plane generator for platform-owned links to `/admin/operations` and `/admin/operations/{run}`.
- **FR-232-002**: The system MUST treat `App\Support\System\SystemOperationRunLinks` as the canonical system-plane generator for platform-owned links to `/system/ops/runs` and `/system/ops/runs/{run}`.
- **FR-232-003**: The first implementation slice MUST inventory all platform-owned collection/detail link producers for `OperationRun` destinations and classify each producer as migrated, verified helper-backed, or explicit exception.
- **FR-232-004**: Covered admin-plane producers MUST stop assembling raw `route('admin.operations.index')` or `route('admin.operations.view')` URLs locally.
- **FR-232-005**: Covered system-plane producers MUST remain on `SystemOperationRunLinks`, and any direct system operations page URL assembly outside explicit allowlisted infrastructure-only cases MUST be removed.
- **FR-232-006**: Admin-plane collection links originating from tenant-aware surfaces MUST preserve only valid canonical context supported by `OperationRunLinks`, including entitled tenant prefilter, active tab, problem class, and canonical navigation context when applicable.
- **FR-232-007**: Admin-plane run-detail links MUST use the canonical admin detail helper and MUST NOT invent tenant-prefixed or surface-specific duplicate detail routes.
- **FR-232-008**: System-plane links MUST never route platform operators to admin-plane monitoring pages as the default destination for system operations history or detail.
- **FR-232-009**: The helper contract MUST remain the only source for the canonical operator-facing nouns and open labels used by covered admin-plane operation links.
- **FR-232-010**: Existing admin-plane and system-plane destination pages MUST keep their current authorization and plane semantics; this feature MUST NOT widen access or weaken `404` versus `403` behavior.
- **FR-232-011**: Explicit exceptions to helper-family reuse MUST be documented, narrowly scoped, and justified by infrastructure or bootstrapping constraints rather than convenience.
- **FR-232-012**: The repository MUST provide one automated guard that fails when new platform-owned operation collection/detail links bypass the canonical helper families outside the explicit allowlist.
- **FR-232-013**: The guard MUST target platform-owned UI and shared navigation layers only and MUST NOT force helper use inside the helper classes themselves, unrelated tests, or non-operator infrastructure code outside the declared boundary.
- **FR-232-014**: Shared navigation builders that produce `operation_run` links, including audit- or related-navigation paths in scope, MUST use the canonical helper family for the destination plane instead of local route assembly.
- **FR-232-015**: Representative tenant/workspace source surfaces in scope, including at least one tenant recency surface, one evidence- or review-oriented surface, and one shared resolver path, MUST be migrated in the first rollout.
- **FR-232-016**: Regression coverage MUST prove correct admin-plane destination continuity, correct system-plane destination continuity, and guard failure on a representative bypass.
- **FR-232-017**: The feature MUST NOT introduce a new operations collection/detail route family, a second link presenter stack, or a separate tenant-local operations page.
## UI Action Matrix *(mandatory when Filament is changed)*
If this feature adds/modifies any Filament Resource / RelationManager / Page, fill out the matrix below.
For each surface, list the exact action labels, whether they are destructive (confirmation? typed confirmation?),
RBAC gating (capability + enforcement helper), whether the mutation writes an audit log, and any exemption or exception used.
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Admin Operations index | `apps/platform/app/Filament/Pages/Monitoring/Operations.php` | Existing filter/context actions only | Full-row click and helper-generated incoming links to the canonical list | none added by this feature | none added by this feature | Existing clear-filter or empty-state CTA unchanged | n/a | n/a | no new audit behavior | Link contract only; no redundant `View` action added |
| Admin operation run detail | `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` | Existing back/refresh/context actions only | Direct route-resolved detail plus helper-generated incoming links | none added by this feature | none | n/a | Existing header navigation and related links only | n/a | no new audit behavior | Incoming links and related links must use the canonical admin helper |
| System operations runs | `apps/platform/app/Filament/System/Pages/Ops/Runs.php` | Existing filters or context actions only | Full-row click and helper-generated incoming links | none added by this feature | none added by this feature | Existing clear-filter CTA unchanged | n/a | n/a | no new audit behavior | Collection destination must stay in system plane |
| System operation run detail | `apps/platform/app/Filament/System/Pages/Ops/ViewRun.php` | Existing refresh/contextual actions only | Direct route-resolved detail plus helper-generated incoming links | none added by this feature | none | n/a | Existing detail actions unchanged | n/a | no new audit behavior | Incoming links must use the canonical system helper |
### Key Entities *(include if feature involves data)*
- **Admin Operation Link Contract**: The canonical helper-generated admin monitoring destination, including collection/detail route choice and any allowed tenant- or navigation-context query semantics.
- **System Operation Link Contract**: The canonical helper-generated system monitoring destination for platform-plane run history and run detail.
- **Allowlisted Link Producer Exception**: A narrowly justified infrastructure-only producer that is explicitly permitted to bypass the helper family without becoming a general precedent for platform-owned UI code.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-232-001**: In automated regression coverage, 100% of covered admin-plane operation links open the canonical admin collection or detail destination with the expected tenant/workspace continuity.
- **SC-232-002**: In automated regression coverage, 100% of covered system-plane operation links open `/system/ops/runs` or `/system/ops/runs/{run}`, and 0 covered system links fall back to admin-plane monitoring.
- **SC-232-003**: The guardrail fails on a representative raw-route bypass and passes only for explicitly allowlisted exceptions in automated review coverage.
- **SC-232-004**: Negative authorization coverage confirms that covered links do not convert foreign-tenant or wrong-plane destinations into successful opens for unauthorized actors.
## Assumptions
- `OperationRunLinks` and `SystemOperationRunLinks` remain the chosen canonical helper families for this release rather than being replaced by a broader navigation framework.
- Existing admin-plane and system-plane operations destination pages already enforce the correct authorization and scope semantics; this feature fixes link production, not destination legitimacy.
- The known drift is concentrated in platform-owned UI and shared navigation layers rather than external integrations or public API contracts.
## Non-Goals
- Redesigning operations list/detail layout, action hierarchy, or lifecycle semantics
- Reworking failures or stuck route families into a broader system monitoring link framework beyond the canonical runs collection/detail seam
- Cleaning every historical test literal or non-operator infrastructure route string unrelated to platform-owned UI or shared navigation layers

View File

@ -0,0 +1,228 @@
# Tasks: Operation Run Link Contract Enforcement
**Input**: Design documents from `/specs/232-operation-run-link-contract/`
**Prerequisites**: `plan.md` (required), `spec.md` (required for user stories), `research.md`, `data-model.md`, `contracts/operation-run-link-contract.logical.openapi.yaml`, `quickstart.md`
**Tests**: Required. This feature changes runtime behavior in operator-facing monitoring drill-through and shared link-contract enforcement, so Pest coverage must be added or updated in `apps/platform/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php`, `apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`, `apps/platform/tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php`, `apps/platform/tests/Feature/Filament/InventoryCoverageRunContinuityTest.php`, `apps/platform/tests/Feature/ReviewPack/ReviewPackResourceTest.php`, `apps/platform/tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php`, `apps/platform/tests/Feature/078/RelatedLinksOnDetailTest.php`, `apps/platform/tests/Feature/RunAuthorizationTenantIsolationTest.php`, `apps/platform/tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php`, `apps/platform/tests/Feature/System/Spec113/AuthorizationSemanticsTest.php`, and `apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php`.
**Operations**: No new `OperationRun` is introduced. Existing admin and system monitoring pages remain the canonical destination surfaces, and this feature must not change run lifecycle, notification, or audit semantics.
**RBAC**: The feature spans the admin `/admin` plane and the platform `/system` plane. It must preserve non-member or wrong-plane `404`, in-scope missing-capability `403`, tenant-safe canonical admin continuity, and current platform-only system access semantics.
**UI / Surface Guardrails**: The changed surfaces are native Filament widgets, pages, resources, and shared navigation builders. The admin monitoring entry points keep the `monitoring-state-page` profile, the remaining surfaces take `standard-native-filament` relief, and the repository signal is `review-mandatory` because the feature adds a bounded guard plus explicit exceptions.
**Filament UI Action Surfaces**: `RecentOperationsSummary`, `InventoryCoverage`, `InventoryItemResource`, `ReviewPackResource`, `TenantlessOperationRunViewer`, and the residual system directory pages keep their existing inspect/open model. No new header, row, bulk, or destructive actions are introduced.
**Badges**: Existing status and outcome badge semantics remain authoritative. This feature must not add ad-hoc badge mappings or a new status taxonomy.
**Organization**: Tasks are grouped by user story so each slice remains independently testable after the shared helper and guard boundary are stabilized. Recommended delivery order is `US1 -> US2 -> US3` because the admin-plane cleanup is the primary migration slice and the guard should close only after the final migrated surface set and exceptions are settled.
## 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 any heavy-governance or browser addition is explicit.
- [X] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is isolated or documented.
- [X] Planned validation commands cover the change without pulling in unrelated lane cost.
- [X] The declared surface test profile or `standard-native-filament` relief is explicit.
- [X] Any material budget, baseline, trend, or escalation note is recorded in the active spec or PR.
## Phase 1: Setup (Shared Link Contract Scaffolding)
**Purpose**: Prepare the focused regression surfaces that will prove canonical admin and system link behavior before runtime files are edited.
- [X] T001 [P] Extend baseline helper contract coverage for canonical admin and system collection/detail URLs in `apps/platform/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php`
- [X] T002 [P] Extend tenant-summary and dashboard drill-through coverage for canonical admin collection links in `apps/platform/tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php` and `apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`
- [X] T003 [P] Extend resource-level admin detail continuity coverage in `apps/platform/tests/Feature/Filament/InventoryCoverageRunContinuityTest.php` and `apps/platform/tests/Feature/ReviewPack/ReviewPackResourceTest.php`
- [X] T004 [P] Extend shared resolver, canonical viewer, and system-plane continuity scaffolding in `apps/platform/tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php`, `apps/platform/tests/Feature/078/RelatedLinksOnDetailTest.php`, `apps/platform/tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php`, and `apps/platform/tests/Feature/System/Spec113/AuthorizationSemanticsTest.php`
**Checkpoint**: The focused test surfaces are ready to prove canonical helper adoption, system-plane continuity, and bounded guard behavior.
---
## Phase 2: Foundational (Blocking Helper And Guard Boundary)
**Purpose**: Stabilize the canonical helper contract and the route-bounded guard before any user story migration begins.
**Critical**: No user story work should begin until this phase is complete.
- [X] T005 Freeze canonical helper inputs, labels, and accepted delegation boundaries in `apps/platform/app/Support/OperationRunLinks.php`, `apps/platform/app/Support/System/SystemOperationRunLinks.php`, and `apps/platform/app/Support/OpsUx/OperationRunUrl.php`
- [X] T006 [P] Create the bounded raw-bypass guard with scoped include paths, explicit exception candidates, forbidden patterns, and actionable file-plus-snippet output in `apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php`
**Checkpoint**: Canonical helper semantics and the initial guard boundary are stable enough for surface-by-surface migration work.
---
## Phase 3: User Story 1 - Follow Admin Operations Links Consistently (Priority: P1)
**Goal**: Platform-owned admin surfaces open canonical operations collection and detail URLs through `OperationRunLinks` with the correct tenant and navigation continuity.
**Independent Test**: Open operations links from the tenant summary widget, dashboard drill-throughs, inventory coverage, review packs, and related-link surfaces, then confirm they land on `/admin/operations` or `/admin/operations/{run}` with only helper-supported continuity semantics.
### Tests for User Story 1
- [X] T007 [P] [US1] Add tenant-aware admin collection link assertions for summary and dashboard sources in `apps/platform/tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php` and `apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`
- [X] T008 [P] [US1] Add canonical admin detail link assertions for coverage and review-pack sources in `apps/platform/tests/Feature/Filament/InventoryCoverageRunContinuityTest.php` and `apps/platform/tests/Feature/ReviewPack/ReviewPackResourceTest.php`
- [X] T009 [P] [US1] Add canonical related-link, viewer fallback, and explicit admin `404`/`403` authorization assertions, including the in-scope capability-denial proof, in `apps/platform/tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php`, `apps/platform/tests/Feature/078/RelatedLinksOnDetailTest.php`, and `apps/platform/tests/Feature/RunAuthorizationTenantIsolationTest.php`
### Implementation for User Story 1
- [X] T010 [P] [US1] Migrate admin collection links in `apps/platform/app/Filament/Widgets/Tenant/RecentOperationsSummary.php` and the collection/detail continuity paths in `apps/platform/app/Filament/Pages/InventoryCoverage.php` to `OperationRunLinks`
- [X] T011 [P] [US1] Migrate admin detail links in `apps/platform/app/Filament/Resources/InventoryItemResource.php` and `apps/platform/app/Filament/Resources/ReviewPackResource.php` to `OperationRunLinks::view(...)`
- [X] T012 [US1] Migrate shared related-navigation and canonical viewer fallback paths in `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php` and `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php` to helper-owned admin links
- [X] T013 [US1] Run the US1 admin continuity verification flow documented in `specs/232-operation-run-link-contract/quickstart.md`
**Checkpoint**: User Story 1 is independently functional and platform-owned admin drill-throughs consistently use the canonical admin helper family.
---
## Phase 4: User Story 2 - Keep System-Plane Run Links In The System Plane (Priority: P1)
**Goal**: Platform operators keep landing on canonical `/system/ops/runs` surfaces, and system follow-up links do not regress to admin-plane monitoring.
**Independent Test**: Open system directory or operations follow-up links as a platform user and confirm collection/detail URLs remain helper-owned system-plane destinations while wrong-plane access still resolves as deny-as-not-found.
### Tests for User Story 2
- [X] T014 [P] [US2] Add system-plane continuity and platform-authorization assertions in `apps/platform/tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php` and `apps/platform/tests/Feature/System/Spec113/AuthorizationSemanticsTest.php`
### Implementation for User Story 2
- [X] T015 [P] [US2] Audit system directory follow-up links in `apps/platform/app/Filament/System/Pages/Directory/ViewTenant.php` and `apps/platform/app/Filament/System/Pages/Directory/ViewWorkspace.php` and keep collection/detail navigation on `SystemOperationRunLinks` without admin fallbacks
- [X] T016 [US2] Audit canonical system entry points in `apps/platform/app/Filament/System/Pages/Ops/ViewRun.php`, `apps/platform/app/Filament/System/Pages/Ops/Runs.php`, and `apps/platform/app/Support/System/SystemOperationRunLinks.php` and apply only minimal cleanup needed to keep verified helper-backed system navigation admin-plane free
- [X] T017 [US2] Run the US2 system-plane verification flow documented in `specs/232-operation-run-link-contract/quickstart.md`
**Checkpoint**: User Story 2 is independently functional and system-plane run navigation remains helper-owned and plane-correct.
---
## Phase 5: User Story 3 - Prevent New Raw Operation-Link Bypasses (Priority: P2)
**Goal**: A bounded repository guard blocks new raw operation-route assembly in platform-owned UI and shared navigation code while preserving explicitly justified infrastructure exceptions.
**Independent Test**: Introduce a representative raw `route('admin.operations.view', ...)` or direct system operations URL inside the declared app-side boundary, confirm the guard fails with actionable output, then replace it with the canonical helper or an explicitly justified exception and confirm the guard passes.
### Tests for User Story 3
- [X] T018 [P] [US3] Add bounded app-side scan coverage, exception handling, and actionable failure assertions in `apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php`
- [X] T019 [P] [US3] Add guard-adjacent regression coverage for accepted delegates and canonical helper outputs in `apps/platform/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php` and `apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`
### Implementation for User Story 3
- [X] T020 [US3] Finalize the guard include paths, forbidden patterns, and allowlisted exception entries for `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`, and `apps/platform/app/Http/Controllers/ClearTenantContextController.php` in `apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php`
- [X] T021 [US3] Retain the finalized allowlisted exceptions in `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`, and `apps/platform/app/Http/Controllers/ClearTenantContextController.php`
- [X] T022 [US3] Run the US3 guardrail verification flow documented in `specs/232-operation-run-link-contract/quickstart.md`
**Checkpoint**: User Story 3 is independently functional and future platform-owned raw bypasses are blocked by a bounded, reviewable guard.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Finalize the contract artifacts, formatting, and focused validation workflow for the full feature.
- [X] T023 [P] Refresh `specs/232-operation-run-link-contract/contracts/operation-run-link-contract.logical.openapi.yaml` and `specs/232-operation-run-link-contract/quickstart.md` with the final guard boundary, exception inventory, and focused validation steps
- [X] T024 Run formatting on touched app and test files with `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- [X] T025 Run the focused Pest suite from `specs/232-operation-run-link-contract/quickstart.md` against `apps/platform/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php`, `apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php`, `apps/platform/tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php`, `apps/platform/tests/Feature/Filament/InventoryCoverageRunContinuityTest.php`, `apps/platform/tests/Feature/ReviewPack/ReviewPackResourceTest.php`, `apps/platform/tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php`, `apps/platform/tests/Feature/078/RelatedLinksOnDetailTest.php`, `apps/platform/tests/Feature/RunAuthorizationTenantIsolationTest.php`, `apps/platform/tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php`, `apps/platform/tests/Feature/System/Spec113/AuthorizationSemanticsTest.php`, and `apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php`
- [X] T026 Record the finalized producer inventory, allowlisted exception set, guard boundary, and `document-in-feature` test-governance disposition in `specs/232-operation-run-link-contract/plan.md`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies; can start immediately.
- **Foundational (Phase 2)**: Depends on Setup completion and blocks all user story work.
- **User Story 1 (Phase 3)**: Depends on Foundational completion and is the recommended first implementation increment.
- **User Story 2 (Phase 4)**: Depends on Foundational completion and can proceed once helper semantics are stable.
- **User Story 3 (Phase 5)**: Depends on User Stories 1 and 2 because the final guard boundary must reflect the settled migrated surfaces and explicit exception set.
- **Polish (Phase 6)**: Depends on all desired user stories being complete.
### User Story Dependencies
- **US1 (P1)**: Starts immediately after Foundational and delivers the primary admin-plane cleanup.
- **US2 (P1)**: Can begin after Foundational, but is easiest to validate once US1 has settled the shared helper vocabulary.
- **US3 (P2)**: Starts after US1 and US2 stabilize because the allowlist and forbidden-pattern boundary should be closed against the final adopted surface set.
### Within Each User Story
- Story tests should be written and fail before the corresponding implementation tasks are considered complete.
- Helper-semantics work in Phase 2 should land before any surface migration adopts the final contract.
- Shared files such as `apps/platform/app/Support/OperationRunLinks.php`, `apps/platform/app/Support/System/SystemOperationRunLinks.php`, and `apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php` should be edited sequentially even when surrounding tasks are otherwise parallelizable.
- Each storys verification task should complete before moving to the next priority slice when working sequentially.
### Parallel Opportunities
- **Setup**: `T001`, `T002`, `T003`, and `T004` can run in parallel.
- **Foundational**: `T006` can run in parallel with the tail end of `T005` once helper inputs and delegate boundaries are settled.
- **US1 tests**: `T007`, `T008`, and `T009` can run in parallel.
- **US1 implementation**: `T010` and `T011` can run in parallel; `T012` should follow once the surrounding helper semantics are stable.
- **US2**: `T014` can run in parallel with early system normalization in `T015`; `T016` should follow once any needed directory-page normalization is clear.
- **US3 tests**: `T018` and `T019` can run in parallel.
- **Polish**: `T023` can run in parallel with `T024` once implementation is stable.
---
## Parallel Example: User Story 1
```bash
# Run US1 coverage in parallel:
T007 apps/platform/tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php and apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php
T008 apps/platform/tests/Feature/Filament/InventoryCoverageRunContinuityTest.php and apps/platform/tests/Feature/ReviewPack/ReviewPackResourceTest.php
T009 apps/platform/tests/Feature/144/CanonicalOperationViewerDeepLinkTrustTest.php and apps/platform/tests/Feature/078/RelatedLinksOnDetailTest.php
# Then split the non-overlapping admin migrations:
T010 apps/platform/app/Filament/Widgets/Tenant/RecentOperationsSummary.php and apps/platform/app/Filament/Pages/InventoryCoverage.php
T011 apps/platform/app/Filament/Resources/InventoryItemResource.php and apps/platform/app/Filament/Resources/ReviewPackResource.php
```
---
## Parallel Example: User Story 2
```bash
# Run US2 system assertions while normalizing residual system directory links:
T014 apps/platform/tests/Feature/System/Spec195/SystemDirectoryResidualSurfaceTest.php and apps/platform/tests/Feature/System/Spec113/AuthorizationSemanticsTest.php
T015 apps/platform/app/Filament/System/Pages/Directory/ViewTenant.php and apps/platform/app/Filament/System/Pages/Directory/ViewWorkspace.php
```
---
## Parallel Example: User Story 3
```bash
# Run guard coverage in parallel with adjacent helper-output regressions:
T018 apps/platform/tests/Feature/Guards/OperationRunLinkContractGuardTest.php
T019 apps/platform/tests/Feature/OpsUx/CanonicalViewRunLinksTest.php and apps/platform/tests/Feature/Monitoring/OperationsDashboardDrillthroughTest.php
```
---
## Implementation Strategy
### First Implementation Increment (User Story 1 Only)
1. Complete Phase 1: Setup.
2. Complete Phase 2: Foundational.
3. Complete Phase 3: User Story 1.
4. Validate the feature with `T013` before widening the slice.
### Incremental Delivery
1. Stabilize the helper contract and guard boundary in Setup and Foundational work.
2. Ship US1 to migrate the actual admin-plane drift surface set.
3. Add US2 to lock the system plane and preserve platform-only destination truth.
4. Add US3 to prevent future raw bypasses and make exceptions explicit.
5. Finish with contract refresh, formatting, focused tests, and close-out notes.
### Parallel Team Strategy
With multiple developers:
1. One contributor can extend helper and guard tests while another prepares the admin widget/resource drill-through assertions.
2. After Phase 2, one contributor can migrate admin collection sources, another can migrate admin detail sources, and a third can normalize shared resolver or viewer fallbacks.
3. Keep `OperationRunLinks.php`, `SystemOperationRunLinks.php`, and `OperationRunLinkContractGuardTest.php` serialized because they define the shared contract boundary.
---
## Notes
- `[P]` marks tasks that can run in parallel once their prerequisites are satisfied and the touched files do not overlap.
- `[US1]`, `[US2]`, and `[US3]` map directly to the feature specification user stories.
- The first working increment is Phase 1 through Phase 3, but the approved feature-complete minimum remains Phase 1 through Phase 5 because system-plane continuity and the guardrail are part of the accepted scope.
- All tasks above follow the required checklist format with task ID, optional parallel marker, story label where applicable, and exact file paths.