diff --git a/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php b/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php
index b658781a..17b3bb59 100644
--- a/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php
+++ b/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php
@@ -15,6 +15,7 @@
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\CanonicalNavigationContext;
+use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\OperateHub\OperateHubShell;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
@@ -182,7 +183,7 @@ public function availableFilters(): array
'options' => [],
],
[
- 'key' => 'tenant',
+ 'key' => 'environment_id',
'label' => 'ManagedEnvironment',
'fixed' => false,
'options' => collect($this->visibleTenants())
@@ -402,14 +403,22 @@ private function tenantFilterOptions(): array
private function applyRequestedTenantPrefilter(): void
{
- $requestedTenant = request()->query('tenant');
+ $workspace = $this->workspace();
- if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
+ if (! $workspace instanceof Workspace) {
return;
}
+ $filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
+
+ if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
+ return;
+ }
+
+ $environmentId = $filter->environmentId();
+
foreach ($this->visibleTenants() as $tenant) {
- if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
+ if ((int) $tenant->getKey() !== $environmentId) {
continue;
}
@@ -418,6 +427,8 @@ private function applyRequestedTenantPrefilter(): void
return;
}
+
+ throw new NotFoundHttpException;
}
private function normalizeTenantFilterState(): void
@@ -583,9 +594,9 @@ private function navigationContext(): CanonicalNavigationContext
private function reportUrl(array $overrides = []): string
{
- $resolvedTenant = array_key_exists('tenant', $overrides)
- ? $overrides['tenant']
- : $this->filteredTenant()?->external_id;
+ $resolvedEnvironment = array_key_exists('environment_id', $overrides)
+ ? $overrides['environment_id']
+ : $this->filteredTenant()?->getKey();
$resolvedReason = array_key_exists('reason', $overrides)
? $overrides['reason']
: $this->currentReasonFilter();
@@ -593,7 +604,7 @@ private function reportUrl(array $overrides = []): string
return static::getUrl(
panel: 'admin',
parameters: array_filter([
- 'tenant' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null,
+ 'environment_id' => is_numeric($resolvedEnvironment) ? (int) $resolvedEnvironment : null,
'reason' => is_string($resolvedReason) && $resolvedReason !== FindingAssignmentHygieneService::FILTER_ALL
? $resolvedReason
: null,
diff --git a/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php b/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php
index f70cf920..29689202 100644
--- a/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php
+++ b/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php
@@ -19,6 +19,7 @@
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\CanonicalNavigationContext;
+use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\OperateHub\OperateHubShell;
use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips;
@@ -79,7 +80,7 @@ public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
->withListRowPrimaryActionLimit(1)
- ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the shared-unassigned scope fixed and expose only a tenant-prefilter clear action when needed.')
+ ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the shared-unassigned scope fixed and expose only an environment-prefilter clear action when needed.')
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The intake queue keeps Claim finding inline and does not render a secondary More menu on rows.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The intake queue does not expose bulk actions in v1.')
@@ -521,14 +522,22 @@ private function tenantFilterOptions(): array
private function applyRequestedTenantPrefilter(): void
{
- $requestedTenant = request()->query('tenant');
+ $workspace = $this->workspace();
- if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
+ if (! $workspace instanceof Workspace) {
return;
}
+ $filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
+
+ if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
+ return;
+ }
+
+ $environmentId = $filter->environmentId();
+
foreach ($this->visibleTenants() as $tenant) {
- if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
+ if ((int) $tenant->getKey() !== $environmentId) {
continue;
}
@@ -537,6 +546,8 @@ private function applyRequestedTenantPrefilter(): void
return;
}
+
+ throw new NotFoundHttpException;
}
private function normalizeTenantFilterState(): void
@@ -720,9 +731,9 @@ private function incomingGovernanceContext(): ?CanonicalNavigationContext
private function queueUrl(array $overrides = []): string
{
- $resolvedTenant = array_key_exists('tenant', $overrides)
- ? $overrides['tenant']
- : $this->filteredTenant()?->external_id;
+ $resolvedEnvironment = array_key_exists('environment_id', $overrides)
+ ? $overrides['environment_id']
+ : $this->filteredTenant()?->getKey();
$resolvedView = array_key_exists('view', $overrides)
? $overrides['view']
: $this->currentQueueView();
@@ -730,7 +741,7 @@ private function queueUrl(array $overrides = []): string
return static::getUrl(
panel: 'admin',
parameters: array_filter([
- 'tenant' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null,
+ 'environment_id' => is_numeric($resolvedEnvironment) ? (int) $resolvedEnvironment : null,
'view' => $resolvedView === 'needs_triage' ? 'needs_triage' : null,
], static fn (mixed $value): bool => $value !== null && $value !== ''),
);
diff --git a/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php b/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php
index 962b97be..be4ba8b3 100644
--- a/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php
+++ b/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php
@@ -18,6 +18,7 @@
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\CanonicalNavigationContext;
+use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\OperateHub\OperateHubShell;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
@@ -206,7 +207,7 @@ public function availableFilters(): array
'options' => [],
],
[
- 'key' => 'tenant',
+ 'key' => 'environment_id',
'label' => 'Managed environment',
'fixed' => false,
'options' => collect($this->visibleTenants())
@@ -461,14 +462,22 @@ private function tenantFilterOptions(): array
private function applyRequestedTenantPrefilter(): void
{
- $requestedTenant = request()->query('tenant');
+ $workspace = $this->workspace();
- if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
+ if (! $workspace instanceof Workspace) {
return;
}
+ $filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
+
+ if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
+ return;
+ }
+
+ $environmentId = $filter->environmentId();
+
foreach ($this->visibleTenants() as $tenant) {
- if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
+ if ((int) $tenant->getKey() !== $environmentId) {
continue;
}
@@ -477,6 +486,8 @@ private function applyRequestedTenantPrefilter(): void
return;
}
+
+ throw new NotFoundHttpException;
}
private function normalizeTenantFilterState(): void
@@ -667,8 +678,8 @@ private function queueUrl(): string
return static::getUrl(
panel: 'admin',
parameters: array_filter([
- 'tenant' => $tenant?->external_id,
- ], static fn (mixed $value): bool => $value !== null && $value !== ''),
+ 'environment_id' => $tenant?->getKey(),
+ ], static fn (mixed $value): bool => is_numeric($value)),
);
}
diff --git a/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php b/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php
index f413c818..7e056e64 100644
--- a/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php
+++ b/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php
@@ -307,8 +307,8 @@ private function assignedFindingsSection(
MyFindingsInbox::getUrl(
panel: 'admin',
parameters: array_filter([
- 'tenant' => $selectedTenant?->external_id,
- ], static fn (mixed $value): bool => is_string($value) && $value !== ''),
+ 'environment_id' => $selectedTenant?->getKey(),
+ ], static fn (mixed $value): bool => is_numeric($value)),
),
$navigationContext?->toQuery() ?? [],
),
@@ -349,7 +349,7 @@ private function intakeFindingsSection(
FindingsIntakeQueue::getUrl(
panel: 'admin',
parameters: array_filter([
- 'tenant' => $selectedTenant?->external_id,
+ 'environment_id' => $selectedTenant?->getKey(),
'view' => $needsTriageCount > 0 ? 'needs_triage' : null,
], static fn (mixed $value): bool => $value !== null && $value !== ''),
),
diff --git a/apps/platform/app/Support/Navigation/AdminSurfaceScope.php b/apps/platform/app/Support/Navigation/AdminSurfaceScope.php
index cdb599a8..5dcf0b88 100644
--- a/apps/platform/app/Support/Navigation/AdminSurfaceScope.php
+++ b/apps/platform/app/Support/Navigation/AdminSurfaceScope.php
@@ -10,6 +10,7 @@
enum AdminSurfaceScope: string
{
case WorkspaceWideSurface = 'workspace_wide_surface';
+ case WorkspaceOwnedAnalysisSurface = 'workspace_owned_analysis_surface';
case WorkspaceScoped = 'workspace_scoped';
case WorkspaceChooserException = 'workspace_chooser_exception';
case EnvironmentBound = 'environment_bound';
@@ -42,6 +43,10 @@ public static function fromPath(string $path): self
return self::WorkspaceWideSurface;
}
+ if (self::isWorkspaceOwnedAnalysisSurfacePath($normalizedPath)) {
+ return self::WorkspaceOwnedAnalysisSurface;
+ }
+
if (
str_starts_with($normalizedPath, '/admin/evidence/')
&& ! str_starts_with($normalizedPath, '/admin/evidence/overview')
@@ -80,6 +85,7 @@ public function allowsEnvironmentlessState(): bool
{
return match ($this) {
self::WorkspaceWideSurface,
+ self::WorkspaceOwnedAnalysisSurface,
self::WorkspaceScoped,
self::WorkspaceChooserException,
self::OnboardingWorkflow,
@@ -92,6 +98,7 @@ public function forcesEnvironmentlessShellContext(): bool
{
return match ($this) {
self::WorkspaceWideSurface,
+ self::WorkspaceOwnedAnalysisSurface,
self::WorkspaceChooserException,
self::CanonicalWorkspaceRecordViewer => true,
default => false,
@@ -116,6 +123,13 @@ private static function isWorkspaceWideSurfacePath(string $normalizedPath): bool
return WorkspaceHubRegistry::isWorkspaceHubPath($normalizedPath);
}
+ private static function isWorkspaceOwnedAnalysisSurfacePath(string $normalizedPath): bool
+ {
+ return preg_match('#^/admin/(baseline-profiles|baseline-snapshots)(?:/.*)?$#', $normalizedPath) === 1
+ || preg_match('#^/admin/findings/(?:my-work|intake|hygiene)/?$#', $normalizedPath) === 1
+ || preg_match('#^/admin/cross-environment-compare/?$#', $normalizedPath) === 1;
+ }
+
private static function effectivePath(Request $request): string
{
$path = '/'.ltrim((string) $request->path(), '/');
diff --git a/apps/platform/app/Support/Tenants/TenantInteractionLane.php b/apps/platform/app/Support/Tenants/TenantInteractionLane.php
index 7103d79a..689ed9e5 100644
--- a/apps/platform/app/Support/Tenants/TenantInteractionLane.php
+++ b/apps/platform/app/Support/Tenants/TenantInteractionLane.php
@@ -21,6 +21,7 @@ public static function fromSurfaceScope(AdminSurfaceScope $pageCategory): self
AdminSurfaceScope::EnvironmentScopedEvidence => self::AdministrativeManagement,
AdminSurfaceScope::CanonicalWorkspaceRecordViewer => self::CanonicalWorkspaceRecord,
AdminSurfaceScope::WorkspaceWideSurface,
+ AdminSurfaceScope::WorkspaceOwnedAnalysisSurface,
AdminSurfaceScope::WorkspaceScoped,
AdminSurfaceScope::WorkspaceChooserException => self::StandardActiveOperating,
};
diff --git a/apps/platform/resources/views/filament/pages/findings/findings-hygiene-report.blade.php b/apps/platform/resources/views/filament/pages/findings/findings-hygiene-report.blade.php
index 51857d66..88c2d3d4 100644
--- a/apps/platform/resources/views/filament/pages/findings/findings-hygiene-report.blade.php
+++ b/apps/platform/resources/views/filament/pages/findings/findings-hygiene-report.blade.php
@@ -18,7 +18,7 @@
- Review visible broken assignments and stale in-progress work across entitled tenants in one read-first repair queue. Existing finding detail stays the only place where reassignment or lifecycle repair happens.
+ Review visible broken assignments and stale in-progress work across entitled environments in one read-first repair queue. Existing finding detail stays the only place where reassignment or lifecycle repair happens.
@@ -68,14 +68,11 @@
{{ $scope['reason_filter_label'] }}
- @if (($scope['tenant_prefilter_source'] ?? 'none') === 'active_tenant_context')
- ManagedEnvironment prefilter from active context:
- {{ $scope['tenant_label'] }}
- @elseif (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
- ManagedEnvironment filter applied:
+ @if (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
+ Environment filter applied:
{{ $scope['tenant_label'] }}
@else
- All visible tenants are currently included.
+ All visible environments are currently included.
@endif
diff --git a/apps/platform/resources/views/filament/pages/findings/findings-intake-queue.blade.php b/apps/platform/resources/views/filament/pages/findings/findings-intake-queue.blade.php
index 623703bc..f535c195 100644
--- a/apps/platform/resources/views/filament/pages/findings/findings-intake-queue.blade.php
+++ b/apps/platform/resources/views/filament/pages/findings/findings-intake-queue.blade.php
@@ -18,7 +18,7 @@
- Review visible unassigned open findings across entitled tenants in one queue. ManagedEnvironment context can narrow the view, but the intake scope stays fixed.
+ Review visible unassigned open findings across entitled environments in one queue. An explicit environment filter can narrow the view, but the intake scope stays fixed.
@@ -68,14 +68,11 @@
{{ $scope['queue_view_label'] }}
- @if (($scope['tenant_prefilter_source'] ?? 'none') === 'active_tenant_context')
- ManagedEnvironment prefilter from active context:
- {{ $scope['tenant_label'] }}
- @elseif (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
- ManagedEnvironment filter applied:
+ @if (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
+ Environment filter applied:
{{ $scope['tenant_label'] }}
@else
- All visible tenants are currently included.
+ All visible environments are currently included.
@endif
diff --git a/apps/platform/resources/views/filament/pages/findings/my-findings-inbox.blade.php b/apps/platform/resources/views/filament/pages/findings/my-findings-inbox.blade.php
index d4a4e4b3..290f135d 100644
--- a/apps/platform/resources/views/filament/pages/findings/my-findings-inbox.blade.php
+++ b/apps/platform/resources/views/filament/pages/findings/my-findings-inbox.blade.php
@@ -18,7 +18,7 @@
- Review open assigned findings across visible tenants in one queue. ManagedEnvironment context can narrow the view, but the personal assignment scope stays fixed.
+ Review open assigned findings across visible environments in one queue. An explicit environment filter can narrow the view, but the personal assignment scope stays fixed.
@@ -56,14 +56,11 @@
Assigned to me only
- @if (($scope['tenant_prefilter_source'] ?? 'none') === 'active_tenant_context')
- ManagedEnvironment prefilter from active context:
- {{ $scope['tenant_label'] }}
- @elseif (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
- ManagedEnvironment filter applied:
+ @if (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
+ Environment filter applied:
{{ $scope['tenant_label'] }}
@else
- All visible tenants are currently included.
+ All visible environments are currently included.
@endif
diff --git a/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php b/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php
index 3fd8fa24..260df0e8 100644
--- a/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php
+++ b/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php
@@ -43,11 +43,13 @@ function findingsHygienePage(?User $user = null, array $query = [])
setAdminPanelContext();
- $factory = $query === []
- ? Livewire::actingAs(auth()->user())
- : Livewire::withQueryParams($query)->actingAs(auth()->user());
+ $factory = Livewire::withHeaders(['referer' => FindingsHygieneReport::getUrl(panel: 'admin')]);
- return $factory->test(FindingsHygieneReport::class);
+ if ($query !== []) {
+ $factory = $factory->withQueryParams($query);
+ }
+
+ return $factory->actingAs(auth()->user())->test(FindingsHygieneReport::class);
}
function makeFindingsHygieneFinding(ManagedEnvironment $tenant, array $attributes = []): Finding
@@ -256,7 +258,7 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu
'options' => [],
],
[
- 'key' => 'tenant',
+ 'key' => 'environment_id',
'label' => 'ManagedEnvironment',
'fixed' => false,
'options' => [
@@ -351,7 +353,68 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu
]);
});
-it('explains when the active tenant prefilter hides otherwise visible hygiene issues and clears it in place', function (): void {
+it('ignores remembered environments and retired tenant query aliases on the workspace-owned hygiene surface', function (): void {
+ [$user, $tenantA] = findingsHygieneActingUser();
+
+ $tenantB = ManagedEnvironment::factory()->create([
+ 'status' => 'active',
+ 'workspace_id' => (int) $tenantA->workspace_id,
+ 'name' => 'Beta ManagedEnvironment',
+ ]);
+ createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');
+
+ $lostMemberA = User::factory()->create(['name' => 'Lost Member A']);
+ createUserWithTenant($tenantA, $lostMemberA, role: 'readonly', workspaceRole: 'readonly');
+ ManagedEnvironmentMembership::query()
+ ->where('managed_environment_id', (int) $tenantA->getKey())
+ ->where('user_id', (int) $lostMemberA->getKey())
+ ->delete();
+
+ $lostMemberB = User::factory()->create(['name' => 'Lost Member B']);
+ createUserWithTenant($tenantB, $lostMemberB, role: 'readonly', workspaceRole: 'readonly');
+ ManagedEnvironmentMembership::query()
+ ->where('managed_environment_id', (int) $tenantB->getKey())
+ ->where('user_id', (int) $lostMemberB->getKey())
+ ->delete();
+
+ $tenantAIssue = makeFindingsHygieneFinding($tenantA, [
+ 'owner_user_id' => (int) $user->getKey(),
+ 'assignee_user_id' => (int) $lostMemberA->getKey(),
+ 'subject_display_name' => 'ManagedEnvironment A Issue',
+ ]);
+ $tenantBIssue = makeFindingsHygieneFinding($tenantB, [
+ 'owner_user_id' => (int) $user->getKey(),
+ 'assignee_user_id' => (int) $lostMemberB->getKey(),
+ 'subject_display_name' => 'ManagedEnvironment B Issue',
+ ]);
+
+ session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
+ (string) $tenantA->workspace_id => (int) $tenantB->getKey(),
+ ]);
+
+ $component = findingsHygienePage($user, [
+ 'tenant' => (string) $tenantB->external_id,
+ 'tenant_id' => (int) $tenantB->getKey(),
+ 'managed_environment_id' => (int) $tenantB->getKey(),
+ 'tenant_scope' => 'environment',
+ 'tableFilters' => [
+ 'managed_environment_id' => ['value' => (string) $tenantB->getKey()],
+ ],
+ ])
+ ->assertSet('tableFilters.managed_environment_id.value', null)
+ ->assertCanSeeTableRecords([$tenantAIssue, $tenantBIssue]);
+
+ expect($component->instance()->appliedScope())->toBe([
+ 'workspace_scoped' => true,
+ 'fixed_scope' => 'visible_findings_hygiene_only',
+ 'reason_filter' => 'all',
+ 'reason_filter_label' => 'All issues',
+ 'tenant_prefilter_source' => 'none',
+ 'tenant_label' => null,
+ ]);
+});
+
+it('explains when the explicit environment_id prefilter hides otherwise visible hygiene issues and clears it in place', function (): void {
[$user, $tenantA] = findingsHygieneActingUser();
$tenantB = ManagedEnvironment::factory()->create([
@@ -374,11 +437,7 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu
'subject_display_name' => 'ManagedEnvironment A Issue',
]);
- session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
- (string) $tenantA->workspace_id => (int) $tenantB->getKey(),
- ]);
-
- $component = findingsHygienePage($user)
+ $component = findingsHygienePage($user, ['environment_id' => (int) $tenantB->getKey()])
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
->assertCanNotSeeTableRecords([$tenantAIssue])
->assertSee('No hygiene issues match this environment scope')
diff --git a/apps/platform/tests/Feature/Findings/FindingsIntakeQueueNavigationContextTest.php b/apps/platform/tests/Feature/Findings/FindingsIntakeQueueNavigationContextTest.php
index f3f2bcb8..4b1c1069 100644
--- a/apps/platform/tests/Feature/Findings/FindingsIntakeQueueNavigationContextTest.php
+++ b/apps/platform/tests/Feature/Findings/FindingsIntakeQueueNavigationContextTest.php
@@ -39,15 +39,16 @@
tenantId: (int) $tenant->getKey(),
familyKey: 'intake_findings',
backLinkUrl: GovernanceInbox::getUrl(panel: 'admin', parameters: [
- 'managed_environment_id' => (string) $tenant->getKey(),
+ 'environment_id' => (int) $tenant->getKey(),
'family' => 'intake_findings',
]),
);
- Livewire::withQueryParams(array_replace($context->toQuery(), [
- 'tenant' => (string) $tenant->external_id,
- 'view' => 'needs_triage',
- ]))
+ Livewire::withHeaders(['referer' => FindingsIntakeQueue::getUrl(panel: 'admin')])
+ ->withQueryParams(array_replace($context->toQuery(), [
+ 'environment_id' => (int) $tenant->getKey(),
+ 'view' => 'needs_triage',
+ ]))
->actingAs($user)
->test(FindingsIntakeQueue::class)
->assertSet('tableFilters.managed_environment_id.value', (string) $tenant->getKey())
diff --git a/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php b/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php
index 9d58a6fb..1d8fb9ba 100644
--- a/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php
+++ b/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php
@@ -31,11 +31,13 @@ function findingsIntakePage(?User $user = null, array $query = [])
setAdminPanelContext();
- $factory = $query === []
- ? Livewire::actingAs(auth()->user())
- : Livewire::withQueryParams($query)->actingAs(auth()->user());
+ $factory = Livewire::withHeaders(['referer' => FindingsIntakeQueue::getUrl(panel: 'admin')]);
- return $factory->test(FindingsIntakeQueue::class);
+ if ($query !== []) {
+ $factory = $factory->withQueryParams($query);
+ }
+
+ return $factory->actingAs(auth()->user())->test(FindingsIntakeQueue::class);
}
function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []): Finding
@@ -140,7 +142,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
->and($queueViews['needs_triage']['active'])->toBeFalse();
});
-it('defaults to the active tenant prefilter and lets the operator clear it without dropping intake scope', function (): void {
+it('applies the explicit environment_id prefilter and lets the operator clear it without dropping intake scope', function (): void {
[$user, $tenantA] = findingsIntakeActingUser();
$tenantB = ManagedEnvironment::factory()->create([
@@ -159,11 +161,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
'status' => Finding::STATUS_TRIAGED,
]);
- session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
- (string) $tenantA->workspace_id => (int) $tenantB->getKey(),
- ]);
-
- $component = findingsIntakePage($user)
+ $component = findingsIntakePage($user, ['environment_id' => (int) $tenantB->getKey()])
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
->assertCanSeeTableRecords([$findingB])
->assertCanNotSeeTableRecords([$findingA])
@@ -174,7 +172,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
'fixed_scope' => 'visible_unassigned_open_findings_only',
'queue_view' => 'unassigned',
'queue_view_label' => 'Unassigned',
- 'tenant_prefilter_source' => 'active_tenant_context',
+ 'tenant_prefilter_source' => 'explicit_filter',
'tenant_label' => $tenantB->name,
]);
@@ -191,7 +189,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
]);
});
-it('keeps the needs triage view active when clearing the tenant prefilter', function (): void {
+it('keeps the needs triage view active when clearing the environment_id prefilter', function (): void {
[$user, $tenantA] = findingsIntakeActingUser();
$tenantB = ManagedEnvironment::factory()->create([
@@ -215,11 +213,10 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
'status' => Finding::STATUS_TRIAGED,
]);
- session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
- (string) $tenantA->workspace_id => (int) $tenantB->getKey(),
- ]);
-
- $component = findingsIntakePage($user, ['view' => 'needs_triage'])
+ $component = findingsIntakePage($user, [
+ 'environment_id' => (int) $tenantB->getKey(),
+ 'view' => 'needs_triage',
+ ])
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
->assertCanSeeTableRecords([$tenantBTriage])
->assertCanNotSeeTableRecords([$tenantATriage, $tenantBBacklog]);
@@ -229,7 +226,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
'fixed_scope' => 'visible_unassigned_open_findings_only',
'queue_view' => 'needs_triage',
'queue_view_label' => 'Needs triage',
- 'tenant_prefilter_source' => 'active_tenant_context',
+ 'tenant_prefilter_source' => 'explicit_filter',
'tenant_label' => $tenantB->name,
]);
@@ -252,6 +249,51 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
->and($queueViews['needs_triage']['active'])->toBeTrue();
});
+it('ignores remembered environments and retired tenant query aliases on the workspace-owned intake surface', function (): void {
+ [$user, $tenantA] = findingsIntakeActingUser();
+
+ $tenantB = ManagedEnvironment::factory()->create([
+ 'status' => 'active',
+ 'workspace_id' => (int) $tenantA->workspace_id,
+ 'name' => 'Beta ManagedEnvironment',
+ ]);
+ createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');
+
+ $findingA = makeIntakeFinding($tenantA, [
+ 'subject_external_id' => 'tenant-a',
+ 'status' => Finding::STATUS_NEW,
+ ]);
+ $findingB = makeIntakeFinding($tenantB, [
+ 'subject_external_id' => 'tenant-b',
+ 'status' => Finding::STATUS_TRIAGED,
+ ]);
+
+ session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
+ (string) $tenantA->workspace_id => (int) $tenantB->getKey(),
+ ]);
+
+ $component = findingsIntakePage($user, [
+ 'tenant' => (string) $tenantB->external_id,
+ 'tenant_id' => (int) $tenantB->getKey(),
+ 'managed_environment_id' => (int) $tenantB->getKey(),
+ 'tenant_scope' => 'environment',
+ 'tableFilters' => [
+ 'managed_environment_id' => ['value' => (string) $tenantB->getKey()],
+ ],
+ ])
+ ->assertSet('tableFilters.managed_environment_id.value', null)
+ ->assertCanSeeTableRecords([$findingA, $findingB]);
+
+ expect($component->instance()->appliedScope())->toBe([
+ 'workspace_scoped' => true,
+ 'fixed_scope' => 'visible_unassigned_open_findings_only',
+ 'queue_view' => 'unassigned',
+ 'queue_view_label' => 'Unassigned',
+ 'tenant_prefilter_source' => 'none',
+ 'tenant_label' => null,
+ ]);
+});
+
it('separates needs triage from the remaining backlog and keeps deterministic urgency ordering', function (): void {
[$user, $tenant] = findingsIntakeActingUser();
@@ -303,7 +345,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
]);
$component = findingsIntakePage($user, [
- 'tenant' => (string) $tenant->external_id,
+ 'environment_id' => (int) $tenant->getKey(),
'view' => 'needs_triage',
]);
@@ -333,9 +375,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
'subject_external_id' => 'available-elsewhere',
]);
- findingsIntakePage($user, [
- 'tenant' => (string) $tenantA->external_id,
- ])
+ findingsIntakePage($user, ['environment_id' => (int) $tenantA->getKey()])
->assertSee('No intake findings match this environment scope')
->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']);
diff --git a/apps/platform/tests/Feature/Findings/MyFindingsInboxNavigationContextTest.php b/apps/platform/tests/Feature/Findings/MyFindingsInboxNavigationContextTest.php
index 5bdd826b..c39a91a1 100644
--- a/apps/platform/tests/Feature/Findings/MyFindingsInboxNavigationContextTest.php
+++ b/apps/platform/tests/Feature/Findings/MyFindingsInboxNavigationContextTest.php
@@ -38,14 +38,15 @@
tenantId: (int) $tenant->getKey(),
familyKey: 'assigned_findings',
backLinkUrl: GovernanceInbox::getUrl(panel: 'admin', parameters: [
- 'managed_environment_id' => (string) $tenant->getKey(),
+ 'environment_id' => (int) $tenant->getKey(),
'family' => 'assigned_findings',
]),
);
- Livewire::withQueryParams(array_replace($context->toQuery(), [
- 'tenant' => (string) $tenant->external_id,
- ]))
+ Livewire::withHeaders(['referer' => MyFindingsInbox::getUrl(panel: 'admin')])
+ ->withQueryParams(array_replace($context->toQuery(), [
+ 'environment_id' => (int) $tenant->getKey(),
+ ]))
->actingAs($user)
->test(MyFindingsInbox::class)
->assertSet('tableFilters.managed_environment_id.value', (string) $tenant->getKey())
diff --git a/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php b/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php
index 4f5b497e..2534a8e4 100644
--- a/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php
+++ b/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php
@@ -31,9 +31,13 @@ function myWorkInboxPage(?User $user = null, array $query = [])
setAdminPanelContext();
- $factory = $query === [] ? Livewire::actingAs(auth()->user()) : Livewire::withQueryParams($query)->actingAs(auth()->user());
+ $factory = Livewire::withHeaders(['referer' => MyFindingsInbox::getUrl(panel: 'admin')]);
- return $factory->test(MyFindingsInbox::class);
+ if ($query !== []) {
+ $factory = $factory->withQueryParams($query);
+ }
+
+ return $factory->actingAs(auth()->user())->test(MyFindingsInbox::class);
}
function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, array $attributes = []): Finding
@@ -121,7 +125,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
'options' => [],
],
[
- 'key' => 'tenant',
+ 'key' => 'environment_id',
'label' => 'Managed environment',
'fixed' => false,
'options' => [
@@ -150,7 +154,50 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
]);
});
-it('defaults to the active environment prefilter and lets the operator clear it without dropping personal scope', function (): void {
+it('applies the explicit environment_id prefilter and lets the operator clear it without dropping personal scope', function (): void {
+ [$user, $tenantA] = myWorkInboxActingUser();
+
+ $tenantB = ManagedEnvironment::factory()->create([
+ 'status' => 'active',
+ 'workspace_id' => (int) $tenantA->workspace_id,
+ 'name' => 'Beta ManagedEnvironment',
+ ]);
+ createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');
+
+ $findingA = makeAssignedFindingForInbox($tenantA, $user, [
+ 'subject_external_id' => 'tenant-a',
+ 'status' => Finding::STATUS_NEW,
+ ]);
+ $findingB = makeAssignedFindingForInbox($tenantB, $user, [
+ 'subject_external_id' => 'tenant-b',
+ 'status' => Finding::STATUS_TRIAGED,
+ ]);
+
+ $component = myWorkInboxPage($user, ['environment_id' => (int) $tenantB->getKey()])
+ ->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
+ ->assertCanSeeTableRecords([$findingB])
+ ->assertCanNotSeeTableRecords([$findingA])
+ ->assertActionVisible('clear_tenant_filter');
+
+ expect($component->instance()->appliedScope())->toBe([
+ 'workspace_scoped' => true,
+ 'assignee_scope' => 'current_user_only',
+ 'tenant_prefilter_source' => 'explicit_filter',
+ 'tenant_label' => $tenantB->name,
+ ]);
+
+ $component->callAction('clear_tenant_filter')
+ ->assertCanSeeTableRecords([$findingA, $findingB]);
+
+ expect($component->instance()->appliedScope())->toBe([
+ 'workspace_scoped' => true,
+ 'assignee_scope' => 'current_user_only',
+ 'tenant_prefilter_source' => 'none',
+ 'tenant_label' => null,
+ ]);
+});
+
+it('ignores remembered environments and retired tenant query aliases on the workspace-owned analysis surface', function (): void {
[$user, $tenantA] = myWorkInboxActingUser();
$tenantB = ManagedEnvironment::factory()->create([
@@ -173,20 +220,16 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
]);
- $component = myWorkInboxPage($user)
- ->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
- ->assertCanSeeTableRecords([$findingB])
- ->assertCanNotSeeTableRecords([$findingA])
- ->assertActionVisible('clear_tenant_filter');
-
- expect($component->instance()->appliedScope())->toBe([
- 'workspace_scoped' => true,
- 'assignee_scope' => 'current_user_only',
- 'tenant_prefilter_source' => 'active_tenant_context',
- 'tenant_label' => $tenantB->name,
- ]);
-
- $component->callAction('clear_tenant_filter')
+ $component = myWorkInboxPage($user, [
+ 'tenant' => (string) $tenantB->external_id,
+ 'tenant_id' => (int) $tenantB->getKey(),
+ 'managed_environment_id' => (int) $tenantB->getKey(),
+ 'tenant_scope' => 'environment',
+ 'tableFilters' => [
+ 'managed_environment_id' => ['value' => (string) $tenantB->getKey()],
+ ],
+ ])
+ ->assertSet('tableFilters.managed_environment_id.value', null)
->assertCanSeeTableRecords([$findingA, $findingB]);
expect($component->instance()->appliedScope())->toBe([
@@ -282,9 +325,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
'subject_external_id' => 'available-elsewhere',
]);
- $component = myWorkInboxPage($user, [
- 'tenant' => (string) $tenantA->external_id,
- ])
+ $component = myWorkInboxPage($user, ['environment_id' => (int) $tenantA->getKey()])
->assertCanNotSeeTableRecords([])
->assertSee('No assigned findings match this environment scope')
->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']);
@@ -306,7 +347,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
->assertTableEmptyStateActionsExistInOrder(['choose_environment_empty']);
});
-it('uses the active visible environment for the calm empty-state drillback when environment context exists', function (): void {
+it('keeps the calm empty-state drillback workspace-owned when remembered environment context exists', function (): void {
[$user, $tenant] = myWorkInboxActingUser();
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
@@ -315,13 +356,13 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
$component = myWorkInboxPage($user)
->assertSee('No visible assigned findings right now')
- ->assertTableEmptyStateActionsExistInOrder(['open_tenant_findings_empty']);
+ ->assertTableEmptyStateActionsExistInOrder(['choose_environment_empty']);
expect($component->instance()->emptyState())->toMatchArray([
- 'action_name' => 'open_tenant_findings_empty',
- 'action_label' => 'Open environment findings',
+ 'action_name' => 'choose_environment_empty',
+ 'action_label' => 'Choose an environment',
'action_kind' => 'url',
- 'action_url' => FindingResource::getUrl('index', panel: 'admin', tenant: $tenant),
+ 'action_url' => route('filament.admin.pages.choose-environment'),
]);
});
diff --git a/apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php b/apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php
index c335d1cb..4ab22f28 100644
--- a/apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php
+++ b/apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php
@@ -44,6 +44,13 @@
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/workspaces/1/environments/2/baseline-compare'))->toBeFalse()
->and(WorkspaceHubRegistry::isExplicitlyExcludedPath('/admin/workspaces/1/environments/2/baseline-compare'))->toBeTrue()
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-compare-landing'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-profiles'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-profiles/42/compare-matrix'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-snapshots'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/findings/my-work'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/findings/intake'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/findings/hygiene'))->toBeFalse()
+ ->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/cross-environment-compare'))->toBeFalse()
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/workspaces/1/environments/2/stored-reports'))->toBeFalse()
->and(WorkspaceHubRegistry::isExplicitlyExcludedPath('/admin/workspaces/1/environments/2/stored-reports'))->toBeTrue();
});
diff --git a/apps/platform/tests/Unit/Support/OperateHub/OperateHubShellResolutionTest.php b/apps/platform/tests/Unit/Support/OperateHub/OperateHubShellResolutionTest.php
index 58112f10..7f107a18 100644
--- a/apps/platform/tests/Unit/Support/OperateHub/OperateHubShellResolutionTest.php
+++ b/apps/platform/tests/Unit/Support/OperateHub/OperateHubShellResolutionTest.php
@@ -83,6 +83,81 @@
->and($resolved->recoveryReason)->toBeNull();
});
+it('keeps workspace owned analysis surfaces tenantless when a remembered environment exists', function (string $path): void {
+ $rememberedEnvironment = ManagedEnvironment::factory()->active()->create(['name' => 'Remembered ManagedEnvironment']);
+ [$user, $rememberedEnvironment] = createUserWithTenant(tenant: $rememberedEnvironment, role: 'owner');
+
+ $this->actingAs($user);
+ Filament::setTenant(null, true);
+
+ $workspaceId = (int) $rememberedEnvironment->workspace_id;
+
+ session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
+ session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
+ (string) $workspaceId => (int) $rememberedEnvironment->getKey(),
+ ]);
+
+ $request = Request::create($path);
+ $request->setLaravelSession(app('session.store'));
+ $request->setUserResolver(static fn () => $user);
+
+ $resolved = app(OperateHubShell::class)->resolvedContext($request);
+
+ expect($resolved->workspace?->getKey())->toBe($workspaceId)
+ ->and($resolved->tenant)->toBeNull()
+ ->and($resolved->tenantSource)->toBe('none')
+ ->and($resolved->state)->toBe('tenantless_workspace');
+})->with([
+ 'baseline profiles list' => ['/admin/baseline-profiles'],
+ 'baseline profiles detail' => ['/admin/baseline-profiles/42'],
+ 'baseline profiles edit' => ['/admin/baseline-profiles/42/edit'],
+ 'baseline profiles compare matrix' => ['/admin/baseline-profiles/42/compare-matrix'],
+ 'baseline snapshots list' => ['/admin/baseline-snapshots'],
+ 'baseline snapshots detail' => ['/admin/baseline-snapshots/42'],
+ 'my findings' => ['/admin/findings/my-work'],
+ 'findings intake' => ['/admin/findings/intake'],
+ 'findings hygiene' => ['/admin/findings/hygiene'],
+ 'cross-environment compare' => ['/admin/cross-environment-compare'],
+]);
+
+it('does not resolve explicit environment_id query hints as shell tenant context on workspace owned analysis surfaces', function (string $path): void {
+ $workspaceTenant = ManagedEnvironment::factory()->active()->create(['name' => 'Workspace ManagedEnvironment']);
+ [$user, $workspaceTenant] = createUserWithTenant(tenant: $workspaceTenant, role: 'owner');
+
+ $hintedTenant = ManagedEnvironment::factory()->active()->create([
+ 'workspace_id' => (int) $workspaceTenant->workspace_id,
+ 'name' => 'Hinted ManagedEnvironment',
+ ]);
+ createUserWithTenant(tenant: $hintedTenant, user: $user, role: 'owner');
+
+ $this->actingAs($user);
+ Filament::setTenant(null, true);
+
+ $workspaceId = (int) $workspaceTenant->workspace_id;
+
+ session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
+
+ $request = Request::create($path, parameters: [
+ 'environment_id' => (int) $hintedTenant->getKey(),
+ ]);
+ $request->setLaravelSession(app('session.store'));
+ $request->setUserResolver(static fn () => $user);
+
+ $resolved = app(OperateHubShell::class)->resolvedContext($request);
+
+ expect($resolved->workspace?->getKey())->toBe($workspaceId)
+ ->and($resolved->tenant)->toBeNull()
+ ->and($resolved->tenantSource)->toBe('none')
+ ->and($resolved->state)->toBe('tenantless_workspace');
+})->with([
+ 'baseline profiles' => ['/admin/baseline-profiles'],
+ 'baseline snapshots' => ['/admin/baseline-snapshots'],
+ 'my findings' => ['/admin/findings/my-work'],
+ 'findings intake' => ['/admin/findings/intake'],
+ 'findings hygiene' => ['/admin/findings/hygiene'],
+ 'cross-environment compare' => ['/admin/cross-environment-compare'],
+]);
+
it('uses the routed tenant workspace when the tenant panel is entered without a selected workspace session', function (): void {
$tenant = ManagedEnvironment::factory()->active()->create(['name' => 'ManagedEnvironment Panel Scope']);
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
diff --git a/apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php b/apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php
index 4d6bb610..a536abb5 100644
--- a/apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php
+++ b/apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php
@@ -3,6 +3,7 @@
declare(strict_types=1);
use App\Support\Navigation\AdminSurfaceScope;
+use App\Support\Tenants\TenantInteractionLane;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
@@ -17,6 +18,16 @@
'retired tenant panel route' => ['/admin/t/tenant-123', AdminSurfaceScope::WorkspaceScoped],
'workspace environment detail' => ['/admin/workspaces/acme/environments/tenant-123', AdminSurfaceScope::EnvironmentBound],
'baseline compare environment route' => ['/admin/workspaces/acme/environments/tenant-123/baseline-compare', AdminSurfaceScope::EnvironmentBound],
+ 'baseline profiles list' => ['/admin/baseline-profiles', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'baseline profiles detail' => ['/admin/baseline-profiles/42', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'baseline profiles edit' => ['/admin/baseline-profiles/42/edit', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'baseline profiles compare matrix' => ['/admin/baseline-profiles/42/compare-matrix', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'baseline snapshots list' => ['/admin/baseline-snapshots', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'baseline snapshots detail' => ['/admin/baseline-snapshots/42', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'my findings inbox' => ['/admin/findings/my-work', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'findings intake' => ['/admin/findings/intake', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'findings hygiene' => ['/admin/findings/hygiene', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
+ 'cross-environment compare' => ['/admin/cross-environment-compare', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
'tenant scoped evidence detail' => ['/admin/evidence/123', AdminSurfaceScope::EnvironmentScopedEvidence],
'evidence overview' => ['/admin/evidence/overview', AdminSurfaceScope::WorkspaceWideSurface],
'customer review workspace' => ['/admin/reviews/workspace', AdminSurfaceScope::WorkspaceWideSurface],
@@ -31,3 +42,14 @@
'retired operation run detail' => ['/admin/operations/44', AdminSurfaceScope::WorkspaceScoped],
'operation run detail' => ['/admin/workspaces/acme/operations/44', AdminSurfaceScope::CanonicalWorkspaceRecordViewer],
]);
+
+it('keeps workspace owned analysis surfaces tenantless without query hint or remembered environment restore', function (): void {
+ $surface = AdminSurfaceScope::WorkspaceOwnedAnalysisSurface;
+
+ expect($surface->allowsQueryEnvironmentHints())->toBeFalse()
+ ->and($surface->allowsRememberedEnvironmentRestore())->toBeFalse()
+ ->and($surface->allowsEnvironmentlessState())->toBeTrue()
+ ->and($surface->forcesEnvironmentlessShellContext())->toBeTrue()
+ ->and($surface->requiresExplicitEnvironment())->toBeFalse()
+ ->and($surface->lane())->toBe(TenantInteractionLane::StandardActiveOperating);
+});