diff --git a/apps/platform/app/Filament/Pages/BaselineCompareLanding.php b/apps/platform/app/Filament/Pages/BaselineCompareLanding.php index 29411f13..75c80904 100644 --- a/apps/platform/app/Filament/Pages/BaselineCompareLanding.php +++ b/apps/platform/app/Filament/Pages/BaselineCompareLanding.php @@ -18,6 +18,7 @@ use App\Support\Baselines\BaselineCompareEvidenceGapDetails; use App\Support\Baselines\BaselineCompareStats; use App\Support\Navigation\CanonicalNavigationContext; +use App\Support\Navigation\NavigationScope; use App\Support\Baselines\TenantGovernanceAggregate; use App\Support\Baselines\TenantGovernanceAggregateResolver; use App\Support\OperationRunLinks; @@ -105,6 +106,12 @@ class BaselineCompareLanding extends Page protected string $view = 'filament.pages.baseline-compare-landing'; + public static function shouldRegisterNavigation(): bool + { + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); + } + public ?string $state = null; public ?string $message = null; diff --git a/apps/platform/app/Filament/Pages/BaselineCompareMatrix.php b/apps/platform/app/Filament/Pages/BaselineCompareMatrix.php index d3de9a6a..c1576197 100644 --- a/apps/platform/app/Filament/Pages/BaselineCompareMatrix.php +++ b/apps/platform/app/Filament/Pages/BaselineCompareMatrix.php @@ -200,7 +200,7 @@ class BaselineCompareMatrix extends Page implements HasForms public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly) - ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep bounded navigation plus confirmation-gated compare fan-out for visible assigned tenants.') + ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep bounded navigation plus confirmation-gated compare fan-out for visible assigned environments.') ->exempt(ActionSurfaceSlot::InspectAffordance, 'The matrix intentionally forbids row click; only explicit tenant, subject, cell, and run drilldowns are rendered.') ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The matrix does not use a row-level secondary-actions menu.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The matrix has no bulk actions.') @@ -341,11 +341,11 @@ protected function getHeaderActions(): array $profile = $this->getRecord(); $compareAssignedTenantsAction = Action::make('compareAssignedTenants') - ->label('Compare assigned tenants') + ->label('Compare assigned environments') ->icon('heroicon-o-play') ->requiresConfirmation() - ->modalHeading('Compare assigned tenants') - ->modalDescription('Simulation only. This starts the normal tenant-owned baseline compare path for the visible assigned set. No workspace umbrella run is created.') + ->modalHeading('Compare assigned environments') + ->modalDescription('Simulation only. This starts the normal environment-owned baseline compare path for the visible assigned set. No workspace umbrella run is created.') ->disabled(fn (): bool => $this->compareAssignedTenantsDisabledReason() !== null) ->tooltip(fn (): ?string => $this->compareAssignedTenantsDisabledReason()) ->action(fn (): mixed => $this->compareAssignedTenants()); @@ -734,11 +734,11 @@ private function compareAssignedTenantsDisabledReason(): ?string $reference = is_array($this->matrix['reference'] ?? null) ? $this->matrix['reference'] : []; if (($reference['referenceState'] ?? null) !== 'ready') { - return 'Capture a complete baseline snapshot before comparing assigned tenants.'; + return 'Capture a complete baseline snapshot before comparing assigned environments.'; } if ((int) ($reference['visibleTenantCount'] ?? 0) === 0) { - return 'No visible assigned tenants are available for compare.'; + return 'No visible assigned environments are available for compare.'; } return $this->compareStartReasonMessage($this->compareAssignedTenantsReasonCode()); @@ -768,10 +768,10 @@ private function compareAssignedTenantsReasonCode(): ?string private function compareStartReasonMessage(?string $reasonCode): ?string { return match ($reasonCode) { - BaselineReasonCodes::COMPARE_INVALID_SCOPE => 'The assigned baseline scope is invalid and must be reviewed before comparing assigned tenants.', + BaselineReasonCodes::COMPARE_INVALID_SCOPE => 'The assigned baseline scope is invalid and must be reviewed before comparing assigned environments.', BaselineReasonCodes::COMPARE_UNSUPPORTED_SCOPE => 'The selected governed subjects are not supported by any compare strategy yet.', - BaselineReasonCodes::COMPARE_MIXED_SCOPE => 'The selected governed subjects span multiple compare strategy families and must be narrowed before comparing assigned tenants.', - 'tenant_sync_required' => 'You need tenant sync access for each visible tenant before compare can start.', + BaselineReasonCodes::COMPARE_MIXED_SCOPE => 'The selected governed subjects span multiple compare strategy families and must be narrowed before comparing assigned environments.', + 'tenant_sync_required' => 'You need environment sync access for each visible environment before compare can start.', default => null, }; } diff --git a/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php b/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php index aa903fdb..5394de20 100644 --- a/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php +++ b/apps/platform/app/Filament/Pages/Findings/FindingsHygieneReport.php @@ -65,12 +65,12 @@ class FindingsHygieneReport extends Page implements HasTable public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport) - ->satisfy(ActionSurfaceSlot::ListHeader, 'Header controls keep the hygiene scope fixed and expose only fixed reason views plus tenant-prefilter recovery when needed.') + ->satisfy(ActionSurfaceSlot::ListHeader, 'Header controls keep the hygiene scope fixed and expose only fixed reason views plus environment-prefilter recovery when needed.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The hygiene report stays read-only and exposes row click as the only inspect path.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The hygiene report does not expose bulk actions.') - ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state stays calm and only offers a tenant-prefilter reset when the active tenant filter hides otherwise visible issues.') - ->exempt(ActionSurfaceSlot::DetailHeader, 'Repair remains on the existing tenant finding detail surface.'); + ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state stays calm and only offers an environment-prefilter reset when the active environment filter hides otherwise visible issues.') + ->exempt(ActionSurfaceSlot::DetailHeader, 'Repair remains on the existing environment finding detail surface.'); } public function mount(): void @@ -93,7 +93,7 @@ protected function getHeaderActions(): array { return [ Action::make('clear_tenant_filter') - ->label('Clear tenant filter') + ->label('Clear environment filter') ->icon('heroicon-o-x-mark') ->color('gray') ->visible(fn (): bool => $this->currentTenantFilterId() !== null) @@ -259,11 +259,11 @@ public function emptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ - 'title' => 'No hygiene issues match this tenant scope', - 'body' => 'Your current tenant filter is hiding hygiene issues that are still visible elsewhere in this workspace.', + 'title' => 'No hygiene issues match this environment scope', + 'body' => 'Your current environment filter is hiding hygiene issues that are still visible elsewhere in this workspace.', 'icon' => 'heroicon-o-funnel', 'action_name' => 'clear_tenant_filter_empty', - 'action_label' => 'Clear tenant filter', + 'action_label' => 'Clear environment filter', 'action_kind' => 'clear_tenant_filter', ]; } @@ -278,7 +278,7 @@ public function emptyState(): array return [ 'title' => 'No visible hygiene issues right now', - 'body' => 'Visible broken assignments and stale in-progress work are currently calm across the entitled tenant scope.', + 'body' => 'Visible broken assignments and stale in-progress work are currently calm across the entitled environment scope.', 'icon' => 'heroicon-o-wrench-screwdriver', ]; } diff --git a/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php b/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php index a1e72706..e73d310e 100644 --- a/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php +++ b/apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php @@ -118,7 +118,7 @@ protected function getHeaderActions(): array } $actions[] = Action::make('clear_tenant_filter') - ->label('Clear tenant filter') + ->label('Clear environment filter') ->icon('heroicon-o-x-mark') ->color('gray') ->visible(fn (): bool => $this->currentTenantFilterId() !== null) @@ -251,18 +251,18 @@ public function emptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ - 'title' => 'No intake findings match this tenant scope', - 'body' => 'Your current tenant filter is hiding shared intake work that is still visible elsewhere in this workspace.', + 'title' => 'No intake findings match this environment scope', + 'body' => 'Your current environment filter is hiding shared intake work that is still visible elsewhere in this workspace.', 'icon' => 'heroicon-o-funnel', 'action_name' => 'clear_tenant_filter_empty', - 'action_label' => 'Clear tenant filter', + 'action_label' => 'Clear environment filter', 'action_kind' => 'clear_tenant_filter', ]; } return [ 'title' => 'Shared intake is clear', - 'body' => 'No visible unassigned findings currently need first routing across your entitled tenants. Open your personal queue if you want to continue with claimed work.', + 'body' => 'No visible unassigned findings currently need first routing across your entitled environments. Open your personal queue if you want to continue with claimed work.', 'icon' => 'heroicon-o-inbox-stack', 'action_name' => 'open_my_findings_empty', 'action_label' => 'Open my findings', diff --git a/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php b/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php index 7953281b..fac0bd2b 100644 --- a/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php +++ b/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php @@ -72,7 +72,7 @@ class MyFindingsInbox extends Page implements HasTable public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport) - ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the assigned-to-me scope fixed and expose only a tenant-prefilter clear action when needed.') + ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the assigned-to-me scope fixed and expose only an environment-prefilter clear action when needed.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The personal findings inbox exposes row click as the only inspect path and does not render a secondary More menu.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The personal findings inbox does not expose bulk actions.') @@ -110,7 +110,7 @@ protected function getHeaderActions(): array } $actions[] = Action::make('clear_tenant_filter') - ->label('Clear tenant filter') + ->label('Clear environment filter') ->icon('heroicon-o-x-mark') ->color('gray') ->visible(fn (): bool => $this->currentTenantFilterId() !== null) @@ -127,7 +127,7 @@ public function table(Table $table): Table ->persistFiltersInSession() ->columns([ TextColumn::make('tenant.name') - ->label('ManagedEnvironment'), + ->label('Managed environment'), TextColumn::make('subject_display_name') ->label('Finding') ->state(fn (Finding $record): string => $record->resolvedSubjectDisplayName() ?? 'Finding #'.$record->getKey()) @@ -154,7 +154,7 @@ public function table(Table $table): Table ]) ->filters([ SelectFilter::make('managed_environment_id') - ->label('ManagedEnvironment') + ->label('Managed environment') ->options(fn (): array => $this->tenantFilterOptions()) ->searchable(), Filter::make('overdue') @@ -207,7 +207,7 @@ public function availableFilters(): array ], [ 'key' => 'tenant', - 'label' => 'ManagedEnvironment', + 'label' => 'Managed environment', 'fixed' => false, 'options' => collect($this->visibleTenants()) ->map(fn (ManagedEnvironment $tenant): array => [ @@ -261,11 +261,11 @@ public function emptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ - 'title' => 'No assigned findings match this tenant scope', - 'body' => 'Your current tenant filter is hiding assigned work that is still visible elsewhere in this workspace.', + 'title' => 'No assigned findings match this environment scope', + 'body' => 'Your current environment filter is hiding assigned work that is still visible elsewhere in this workspace.', 'icon' => 'heroicon-o-funnel', 'action_name' => 'clear_tenant_filter_empty', - 'action_label' => 'Clear tenant filter', + 'action_label' => 'Clear environment filter', 'action_kind' => 'clear_tenant_filter', ]; } @@ -275,10 +275,10 @@ public function emptyState(): array if ($activeTenant instanceof ManagedEnvironment) { return [ 'title' => 'No visible assigned findings right now', - 'body' => 'Nothing currently assigned to you needs attention in the visible tenant scope. You can still open tenant findings for broader context.', + 'body' => 'Nothing currently assigned to you needs attention in the visible environment scope. You can still open environment findings for broader context.', 'icon' => 'heroicon-o-clipboard-document-check', 'action_name' => 'open_tenant_findings_empty', - 'action_label' => 'Open tenant findings', + 'action_label' => 'Open environment findings', 'action_kind' => 'url', 'action_url' => FindingResource::getUrl('index', tenant: $activeTenant), ]; @@ -286,10 +286,10 @@ public function emptyState(): array return [ 'title' => 'No visible assigned findings right now', - 'body' => 'Nothing currently assigned to you needs attention across the visible tenant scope. Choose a tenant to continue working elsewhere in the workspace.', + 'body' => 'Nothing currently assigned to you needs attention across the visible environment scope. Choose an environment to continue working elsewhere in the workspace.', 'icon' => 'heroicon-o-clipboard-document-check', 'action_name' => 'choose_tenant_empty', - 'action_label' => 'Choose a tenant', + 'action_label' => 'Choose an environment', 'action_kind' => 'url', 'action_url' => route('filament.admin.pages.choose-tenant'), ]; diff --git a/apps/platform/app/Filament/Pages/Governance/DecisionRegister.php b/apps/platform/app/Filament/Pages/Governance/DecisionRegister.php index 073c3bea..1c4d0f99 100644 --- a/apps/platform/app/Filament/Pages/Governance/DecisionRegister.php +++ b/apps/platform/app/Filament/Pages/Governance/DecisionRegister.php @@ -219,7 +219,7 @@ public function isActiveRegisterState(string $registerState): bool public function emptyStateHeading(): string { if ($this->tenantFilterAloneExcludesRows()) { - return 'This tenant filter is hiding other visible decision follow-through'; + return 'This environment filter is hiding other visible decision follow-through'; } if ($this->registerState === 'recently_closed') { @@ -232,20 +232,20 @@ public function emptyStateHeading(): string public function emptyStateDescription(): string { if ($this->tenantFilterAloneExcludesRows()) { - return 'The current tenant scope is calm, but other visible tenants in this workspace still have open governance decisions.'; + return 'The current environment scope is calm, but other visible environments in this workspace still have open governance decisions.'; } if ($this->registerState === 'recently_closed') { - return 'Switch back to open decisions to continue the current follow-through lane, or widen the tenant scope if you were filtering the register.'; + return 'Switch back to open decisions to continue the current follow-through lane, or widen the environment scope if you were filtering the register.'; } - return 'Try widening the tenant scope or switch to recently closed decisions if you are checking what was just finished.'; + return 'Try widening the environment scope or switch to recently closed decisions if you are checking what was just finished.'; } public function emptyStateActionLabel(): ?string { if ($this->tenantFilterAloneExcludesRows()) { - return 'Clear tenant filter'; + return 'Clear environment filter'; } if ($this->registerState === 'recently_closed') { diff --git a/apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php b/apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php index 2561c052..13bef1f2 100644 --- a/apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php +++ b/apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php @@ -145,9 +145,9 @@ public function calmEmptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ - 'title' => 'This tenant filter is hiding other visible attention', - 'body' => 'The current tenant scope is calm, but other visible tenants in this workspace still have governance attention.', - 'action_label' => 'Clear tenant filter', + 'title' => 'This environment filter is hiding other visible attention', + 'body' => 'The current environment scope is calm, but other visible environments in this workspace still have governance attention.', + 'action_label' => 'Clear environment filter', 'action_url' => $this->pageUrl(['tenant' => null]), ]; } diff --git a/apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php b/apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php index 1adc6a47..21834e61 100644 --- a/apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php +++ b/apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php @@ -147,7 +147,7 @@ public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep workspace approval scope visible and expose selected exception review actions.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ViewAction->value) ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Exception decisions are reviewed one record at a time in v1 and do not expose bulk actions.') - ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state explains when the approval queue is empty and keeps navigation back to tenant findings available.') + ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state explains when the approval queue is empty and keeps navigation back to environment findings available.') ->satisfy(ActionSurfaceSlot::DetailHeader, 'Selected exception detail exposes approve, reject, and related-record navigation actions in the page header.'); } @@ -232,7 +232,7 @@ protected function getHeaderActions(): array }); $actions[] = Action::make('view_tenant_register') - ->label('View tenant register') + ->label('View environment findings') ->icon('heroicon-o-arrow-top-right-on-square') ->color('gray') ->visible(fn (): bool => $this->filteredTenant() instanceof ManagedEnvironment) @@ -254,7 +254,7 @@ protected function getHeaderActions(): array ->url(fn (): string => $this->queueUrl(['exception' => null])), Action::make('open_selected_exception') - ->label('Open tenant detail') + ->label('Open environment detail') ->icon('heroicon-o-arrow-top-right-on-square') ->color('gray') ->visible(fn (): bool => $this->selectedFindingException() instanceof FindingException) diff --git a/apps/platform/app/Filament/Resources/BackupScheduleResource.php b/apps/platform/app/Filament/Resources/BackupScheduleResource.php index 1daefb0e..e19c39d1 100644 --- a/apps/platform/app/Filament/Resources/BackupScheduleResource.php +++ b/apps/platform/app/Filament/Resources/BackupScheduleResource.php @@ -24,6 +24,7 @@ use App\Support\Badges\TagBadgeDomain; use App\Support\Badges\TagBadgeRenderer; use App\Support\Filament\TablePaginationProfiles; +use App\Support\Navigation\NavigationScope; use App\Support\OperationRunLinks; use App\Support\OperationRunOutcome; use App\Support\OperationRunType; @@ -81,7 +82,8 @@ class BackupScheduleResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function canViewAny(): bool @@ -440,7 +442,7 @@ public static function table(Table $table): Table if (! $tenant instanceof ManagedEnvironment) { Notification::make() - ->title('No tenant selected') + ->title('No environment selected') ->danger() ->send(); @@ -511,7 +513,7 @@ public static function table(Table $table): Table if (! $tenant instanceof ManagedEnvironment) { Notification::make() - ->title('No tenant selected') + ->title('No environment selected') ->danger() ->send(); @@ -728,7 +730,7 @@ public static function table(Table $table): Table if (! $tenant instanceof ManagedEnvironment) { Notification::make() - ->title('No tenant selected') + ->title('No environment selected') ->danger() ->send(); @@ -825,7 +827,7 @@ public static function table(Table $table): Table if (! $tenant instanceof ManagedEnvironment) { Notification::make() - ->title('No tenant selected') + ->title('No environment selected') ->danger() ->send(); diff --git a/apps/platform/app/Filament/Resources/BackupSetResource.php b/apps/platform/app/Filament/Resources/BackupSetResource.php index 9681ee61..eb2d52d5 100644 --- a/apps/platform/app/Filament/Resources/BackupSetResource.php +++ b/apps/platform/app/Filament/Resources/BackupSetResource.php @@ -26,6 +26,7 @@ use App\Support\Badges\BadgeRenderer; use App\Support\Filament\TablePaginationProfiles; use App\Support\Navigation\CrossResourceNavigationMatrix; +use App\Support\Navigation\NavigationScope; use App\Support\Navigation\RelatedContextEntry; use App\Support\Navigation\RelatedNavigationResolver; use App\Support\OperationRunLinks; @@ -75,7 +76,8 @@ class BackupSetResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration diff --git a/apps/platform/app/Filament/Resources/BaselineProfileResource.php b/apps/platform/app/Filament/Resources/BaselineProfileResource.php index 9a0f1293..f47c29c8 100644 --- a/apps/platform/app/Filament/Resources/BaselineProfileResource.php +++ b/apps/platform/app/Filament/Resources/BaselineProfileResource.php @@ -30,6 +30,7 @@ use App\Support\Filament\FilterOptionCatalog; use App\Support\Inventory\InventoryPolicyTypeMeta; use App\Support\Navigation\CrossResourceNavigationMatrix; +use App\Support\Navigation\NavigationScope; use App\Support\Navigation\RelatedContextEntry; use App\Support\Navigation\RelatedNavigationResolver; use App\Support\OperationCatalog; @@ -96,7 +97,8 @@ public static function shouldRegisterNavigation(): bool return false; } - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function canViewAny(): bool @@ -465,7 +467,7 @@ public static function table(Table $table): Table ->label('Version') ->placeholder('—'), TextColumn::make('tenant_assignments_count') - ->label('Assigned tenants') + ->label('Assigned environments') ->counts('tenantAssignments'), TextColumn::make('current_snapshot_truth') ->label('Current snapshot') diff --git a/apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php b/apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php index a9b1a6e9..a2ae0d97 100644 --- a/apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php +++ b/apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php @@ -311,11 +311,11 @@ private function compareNowAction(): Action private function compareAssignedTenantsAction(): Action { $action = Action::make('compareAssignedTenants') - ->label('Compare assigned tenants') + ->label('Compare assigned environments') ->icon('heroicon-o-play') ->requiresConfirmation() - ->modalHeading('Compare assigned tenants') - ->modalDescription('Simulation only. This starts the normal tenant-owned baseline compare path for the visible assigned set. No workspace umbrella run is created.') + ->modalHeading('Compare assigned environments') + ->modalDescription('Simulation only. This starts the normal environment-owned baseline compare path for the visible assigned set. No workspace umbrella run is created.') ->disabled(fn (): bool => $this->compareAssignedTenantsDisabledReason() !== null) ->tooltip(fn (): ?string => $this->compareAssignedTenantsDisabledReason()) ->action(function (): void { @@ -489,11 +489,11 @@ private function compareAssignedTenantsDisabledReason(): ?string $profile = $this->getRecord(); if (! $this->profileHasConsumableSnapshot()) { - return 'Capture a complete baseline snapshot before comparing assigned tenants.'; + return 'Capture a complete baseline snapshot before comparing assigned environments.'; } if ($this->visibleAssignedTenantCount($profile) === 0) { - return 'No visible assigned tenants are available for compare.'; + return 'No visible assigned environments are available for compare.'; } return null; diff --git a/apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php b/apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php index 2e724340..f320c8c8 100644 --- a/apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php +++ b/apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php @@ -27,7 +27,7 @@ class BaselineTenantAssignmentsRelationManager extends RelationManager { protected static string $relationship = 'tenantAssignments'; - protected static ?string $title = 'ManagedEnvironment assignments'; + protected static ?string $title = 'Environment assignments'; /** * @var array|null @@ -37,11 +37,11 @@ class BaselineTenantAssignmentsRelationManager extends RelationManager public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forRelationManager(ActionSurfaceProfile::RelationManager) - ->satisfy(ActionSurfaceSlot::ListHeader, 'Header action: Assign tenant (manage-gated).') + ->satisfy(ActionSurfaceSlot::ListHeader, 'Header action: Assign environment (manage-gated).') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'v1 assignments have no row-level actions beyond delete.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'No bulk mutations for assignments in v1.') - ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state encourages assigning a tenant.'); + ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty state encourages assigning an environment.'); } public function table(Table $table): Table @@ -51,7 +51,7 @@ public function table(Table $table): Table ->paginated(\App\Support\Filament\TablePaginationProfiles::relationManager()) ->columns([ Tables\Columns\TextColumn::make('tenant.name') - ->label('ManagedEnvironment') + ->label('Managed environment') ->searchable(), Tables\Columns\TextColumn::make('assignedByUser.name') ->label('Assigned by') @@ -68,8 +68,8 @@ public function table(Table $table): Table ->actions([ $this->removeAssignmentAction(), ]) - ->emptyStateHeading('No tenants assigned') - ->emptyStateDescription('Assign a tenant to compare its state against this baseline profile.') + ->emptyStateHeading('No environments assigned') + ->emptyStateDescription('Assign an environment to compare its state against this baseline profile.') ->emptyStateActions([ $this->assignTenantAction()->name('assignEmpty'), ]); @@ -78,12 +78,12 @@ public function table(Table $table): Table private function assignTenantAction(): Action { return Action::make('assign') - ->label('Assign ManagedEnvironment') + ->label('Assign environment') ->icon('heroicon-o-plus') ->visible(fn (): bool => $this->hasManageCapability()) ->form([ Select::make('managed_environment_id') - ->label('ManagedEnvironment') + ->label('Managed environment') ->options(fn (): array => $this->getTenantOptions()) ->disableOptionWhen(fn (string $value): bool => array_key_exists((int) $value, $this->getTenantAssignmentSummaries())) ->required() @@ -114,10 +114,10 @@ private function assignTenantAction(): Action $assignedBaselineName = $this->getAssignedBaselineNameForTenant($tenantId); Notification::make() - ->title('ManagedEnvironment already assigned') + ->title('Environment already assigned') ->body($assignedBaselineName === null - ? 'This tenant already has a baseline assignment in this workspace.' - : "This tenant is already assigned to baseline: {$assignedBaselineName}.") + ? 'This environment already has a baseline assignment in this workspace.' + : "This environment is already assigned to baseline: {$assignedBaselineName}.") ->warning() ->send(); @@ -134,7 +134,7 @@ private function assignTenantAction(): Action $this->auditAssignment($profile, $assignment, $user, 'created'); Notification::make() - ->title('ManagedEnvironment assigned') + ->title('Environment assigned') ->success() ->send(); @@ -150,8 +150,8 @@ private function removeAssignmentAction(): Action ->color('danger') ->visible(fn (): bool => $this->hasManageCapability()) ->requiresConfirmation() - ->modalHeading('Remove tenant assignment') - ->modalDescription('Are you sure you want to remove this tenant assignment? This will not delete any existing findings.') + ->modalHeading('Remove environment assignment') + ->modalDescription('Are you sure you want to remove this environment assignment? This will not delete any existing findings.') ->action(function (BaselineTenantAssignment $record): void { $user = auth()->user(); diff --git a/apps/platform/app/Filament/Resources/BaselineSnapshotResource.php b/apps/platform/app/Filament/Resources/BaselineSnapshotResource.php index cf2b69f4..59a387e2 100644 --- a/apps/platform/app/Filament/Resources/BaselineSnapshotResource.php +++ b/apps/platform/app/Filament/Resources/BaselineSnapshotResource.php @@ -17,6 +17,7 @@ use App\Support\Baselines\BaselineSnapshotLifecycleState; use App\Support\Filament\FilterPresets; use App\Support\Navigation\CrossResourceNavigationMatrix; +use App\Support\Navigation\NavigationScope; use App\Support\Navigation\RelatedContextEntry; use App\Support\Navigation\RelatedNavigationResolver; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; @@ -67,7 +68,8 @@ public static function shouldRegisterNavigation(): bool return false; } - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function canViewAny(): bool diff --git a/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php b/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php index 575e4f56..c3f57edd 100644 --- a/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php +++ b/apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php @@ -22,6 +22,7 @@ use App\Support\Evidence\EvidenceCompletenessState; use App\Support\Evidence\EvidenceSnapshotStatus; use App\Support\Navigation\RelatedContextEntry; +use App\Support\Navigation\NavigationScope; use App\Support\OperationCatalog; use App\Support\OperationRunLinks; use App\Support\OperationRunOutcome; @@ -83,6 +84,12 @@ class EvidenceSnapshotResource extends Resource protected static ?int $navigationSort = 55; + public static function shouldRegisterNavigation(): bool + { + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); + } + public static function canViewAny(): bool { $tenant = static::resolveTenantContextForCurrentPanel(); diff --git a/apps/platform/app/Filament/Resources/FindingExceptionResource.php b/apps/platform/app/Filament/Resources/FindingExceptionResource.php index 7ffb0e83..37bc51f8 100644 --- a/apps/platform/app/Filament/Resources/FindingExceptionResource.php +++ b/apps/platform/app/Filament/Resources/FindingExceptionResource.php @@ -23,6 +23,7 @@ use App\Support\Badges\BadgeRenderer; use App\Support\Filament\FilterOptionCatalog; use App\Support\Filament\TablePaginationProfiles; +use App\Support\Navigation\NavigationScope; use App\Support\Navigation\RelatedContextEntry; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; @@ -73,7 +74,8 @@ class FindingExceptionResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function canViewAny(): bool diff --git a/apps/platform/app/Filament/Resources/FindingResource.php b/apps/platform/app/Filament/Resources/FindingResource.php index fdd577c9..8d28ba27 100644 --- a/apps/platform/app/Filament/Resources/FindingResource.php +++ b/apps/platform/app/Filament/Resources/FindingResource.php @@ -26,6 +26,7 @@ use App\Support\Filament\TablePaginationProfiles; use App\Support\Navigation\CanonicalNavigationContext; use App\Support\Navigation\CrossResourceNavigationMatrix; +use App\Support\Navigation\NavigationScope; use App\Support\Navigation\RelatedContextEntry; use App\Support\Navigation\RelatedNavigationResolver; use App\Support\OperationRunLinks; @@ -80,7 +81,8 @@ class FindingResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function getNavigationLabel(): string diff --git a/apps/platform/app/Filament/Resources/InventoryItemResource.php b/apps/platform/app/Filament/Resources/InventoryItemResource.php index 59253277..94820679 100644 --- a/apps/platform/app/Filament/Resources/InventoryItemResource.php +++ b/apps/platform/app/Filament/Resources/InventoryItemResource.php @@ -18,6 +18,7 @@ use App\Support\Badges\TagBadgeRenderer; use App\Support\Filament\FilterOptionCatalog; use App\Support\Inventory\InventoryPolicyTypeMeta; +use App\Support\Navigation\NavigationScope; use App\Support\OperationRunLinks; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; @@ -60,7 +61,8 @@ class InventoryItemResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function getRouteBaseName(?Panel $panel = null): string diff --git a/apps/platform/app/Filament/Resources/PolicyResource.php b/apps/platform/app/Filament/Resources/PolicyResource.php index e0867130..4ef32adc 100644 --- a/apps/platform/app/Filament/Resources/PolicyResource.php +++ b/apps/platform/app/Filament/Resources/PolicyResource.php @@ -26,6 +26,7 @@ use App\Support\Badges\TagBadgeDomain; use App\Support\Badges\TagBadgeRenderer; use App\Support\Filament\TablePaginationProfiles; +use App\Support\Navigation\NavigationScope; use App\Support\OperationRunLinks; use App\Support\OpsUx\OperationUxPresenter; use App\Support\OpsUx\OpsUxBrowserEvents; @@ -86,7 +87,8 @@ public static function getPluralModelLabel(): string public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function canViewAny(): bool diff --git a/apps/platform/app/Filament/Resources/PolicyVersionResource.php b/apps/platform/app/Filament/Resources/PolicyVersionResource.php index 902d2049..373a7a82 100644 --- a/apps/platform/app/Filament/Resources/PolicyVersionResource.php +++ b/apps/platform/app/Filament/Resources/PolicyVersionResource.php @@ -33,6 +33,7 @@ use App\Support\Filament\FilterOptionCatalog; use App\Support\Filament\FilterPresets; use App\Support\Navigation\CrossResourceNavigationMatrix; +use App\Support\Navigation\NavigationScope; use App\Support\Navigation\RelatedContextEntry; use App\Support\Navigation\RelatedNavigationResolver; use App\Support\OperationRunLinks; @@ -84,7 +85,8 @@ class PolicyVersionResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function canViewAny(): bool diff --git a/apps/platform/app/Filament/Resources/RestoreRunResource.php b/apps/platform/app/Filament/Resources/RestoreRunResource.php index 8447ae33..f657f887 100644 --- a/apps/platform/app/Filament/Resources/RestoreRunResource.php +++ b/apps/platform/app/Filament/Resources/RestoreRunResource.php @@ -39,6 +39,7 @@ use App\Support\Badges\BadgeRenderer; use App\Support\Filament\FilterOptionCatalog; use App\Support\Filament\FilterPresets; +use App\Support\Navigation\NavigationScope; use App\Support\OperationCatalog; use App\Support\OperationRunLinks; use App\Support\OpsUx\OperationUxPresenter; @@ -97,7 +98,8 @@ class RestoreRunResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-arrow-path-rounded-square'; diff --git a/apps/platform/app/Filament/Resources/ReviewPackResource.php b/apps/platform/app/Filament/Resources/ReviewPackResource.php index 5020fae0..f5e9ecee 100644 --- a/apps/platform/app/Filament/Resources/ReviewPackResource.php +++ b/apps/platform/app/Filament/Resources/ReviewPackResource.php @@ -18,6 +18,7 @@ use App\Support\Badges\BadgeCatalog; use App\Support\Badges\BadgeDomain; use App\Support\Badges\BadgeRenderer; +use App\Support\Navigation\NavigationScope; use App\Support\OperationRunLinks; use App\Support\OpsUx\OperationUxPresenter; use App\Support\Rbac\UiEnforcement; @@ -67,6 +68,12 @@ class ReviewPackResource extends Resource protected static ?int $navigationSort = 50; + public static function shouldRegisterNavigation(): bool + { + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); + } + public static function canViewAny(): bool { $tenant = static::resolveTenantContextForCurrentPanel(); diff --git a/apps/platform/app/Filament/Resources/StoredReportResource.php b/apps/platform/app/Filament/Resources/StoredReportResource.php index 15182565..0a12966a 100644 --- a/apps/platform/app/Filament/Resources/StoredReportResource.php +++ b/apps/platform/app/Filament/Resources/StoredReportResource.php @@ -16,6 +16,7 @@ use App\Support\Badges\BadgeRenderer; use App\Support\Filament\TablePaginationProfiles; use App\Support\ManagedEnvironmentLinks; +use App\Support\Navigation\NavigationScope; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; @@ -70,6 +71,12 @@ class StoredReportResource extends Resource protected static ?int $navigationSort = 49; + public static function shouldRegisterNavigation(): bool + { + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); + } + public static function canViewAny(): bool { return static::visibleReportTypesForCurrentUser() !== []; diff --git a/apps/platform/app/Filament/Resources/TenantReviewResource.php b/apps/platform/app/Filament/Resources/TenantReviewResource.php index d8e2799e..699edbc8 100644 --- a/apps/platform/app/Filament/Resources/TenantReviewResource.php +++ b/apps/platform/app/Filament/Resources/TenantReviewResource.php @@ -24,6 +24,7 @@ use App\Support\Badges\BadgeDomain; use App\Support\Badges\BadgeRenderer; use App\Support\Findings\FindingOutcomeSemantics; +use App\Support\Navigation\NavigationScope; use App\Support\OperationRunLinks; use App\Support\OperationRunType; use App\Support\OpsUx\OperationUxPresenter; @@ -87,7 +88,8 @@ class TenantReviewResource extends Resource public static function shouldRegisterNavigation(): bool { - return parent::shouldRegisterNavigation(); + return NavigationScope::shouldRegisterEnvironmentNavigation() + && parent::shouldRegisterNavigation(); } public static function getSlug(?Panel $panel = null): string diff --git a/apps/platform/app/Services/Tenants/TenantActionPolicySurface.php b/apps/platform/app/Services/Tenants/TenantActionPolicySurface.php index 87819234..ee7e1acb 100644 --- a/apps/platform/app/Services/Tenants/TenantActionPolicySurface.php +++ b/apps/platform/app/Services/Tenants/TenantActionPolicySurface.php @@ -225,10 +225,10 @@ private function lifecycleActionForContext( destructive: true, requiresConfirmation: true, auditActionId: AuditActionId::TenantRestored, - successNotificationTitle: 'ManagedEnvironment restored', - successNotificationBody: 'The tenant is available again in normal tenant management flows and can be selected as active context.', - modalHeading: 'Restore tenant', - modalDescription: 'Restore this archived tenant so it can be selected again in normal tenant management flows.', + successNotificationTitle: 'Environment restored', + successNotificationBody: 'The environment is available again in normal environment management flows and can be selected as active context.', + modalHeading: 'Restore environment', + modalDescription: 'Restore this archived environment so it can be selected again in normal environment management flows.', group: $group, ); } @@ -250,10 +250,10 @@ private function lifecycleActionForContext( destructive: true, requiresConfirmation: true, auditActionId: AuditActionId::TenantArchived, - successNotificationTitle: 'ManagedEnvironment archived', - successNotificationBody: 'The tenant remains available for inspection and audit history, but it is no longer selectable as active context.', - modalHeading: 'Archive tenant', - modalDescription: 'Archive this tenant to retain it for inspection and audit history while removing it from active management flows.', + successNotificationTitle: 'Environment archived', + successNotificationBody: 'The environment remains available for inspection and audit history, but it is no longer selectable as active context.', + modalHeading: 'Archive environment', + modalDescription: 'Archive this environment to retain it for inspection and audit history while removing it from active management flows.', group: $group, ); } diff --git a/apps/platform/app/Support/Baselines/BaselineCompareMatrixBuilder.php b/apps/platform/app/Support/Baselines/BaselineCompareMatrixBuilder.php index e4014a1c..150e81da 100644 --- a/apps/platform/app/Support/Baselines/BaselineCompareMatrixBuilder.php +++ b/apps/platform/app/Support/Baselines/BaselineCompareMatrixBuilder.php @@ -973,15 +973,15 @@ private function emptyState( if ((int) ($reference['assignedTenantCount'] ?? 0) === 0) { return [ - 'title' => 'No assigned tenants', - 'body' => 'Assign tenants to this baseline profile to build the visible compare set.', + 'title' => 'No assigned environments', + 'body' => 'Assign environments to this baseline profile to build the visible compare set.', ]; } if ($visibleTenantsCount === 0) { return [ - 'title' => 'No visible assigned tenants', - 'body' => 'This baseline has assigned tenants, but none are visible in your current tenant scope.', + 'title' => 'No visible assigned environments', + 'body' => 'This baseline has assigned environments, but none are visible in your current environment scope.', ]; } diff --git a/apps/platform/app/Support/Baselines/BaselineCompareStats.php b/apps/platform/app/Support/Baselines/BaselineCompareStats.php index c9a931ba..a32eacfa 100644 --- a/apps/platform/app/Support/Baselines/BaselineCompareStats.php +++ b/apps/platform/app/Support/Baselines/BaselineCompareStats.php @@ -92,7 +92,7 @@ private function __construct( public static function forTenant(?ManagedEnvironment $tenant): self { if (! $tenant instanceof ManagedEnvironment) { - return self::empty('no_tenant', 'No tenant selected.'); + return self::empty('no_tenant', 'No environment selected.'); } $findingAttentionCounts = self::findingAttentionCounts($tenant); diff --git a/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php b/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php index fcd6cab4..9df88386 100644 --- a/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php +++ b/apps/platform/app/Support/GovernanceInbox/GovernanceInboxSectionBuilder.php @@ -256,7 +256,7 @@ private function findingExceptionsSection( ), 'entries' => $entries, 'empty_state' => $selectedTenant instanceof ManagedEnvironment - ? 'No finding exceptions match this tenant filter right now.' + ? 'No finding exceptions match this environment filter right now.' : 'No finding exceptions need review right now.', ]; } @@ -315,7 +315,7 @@ private function assignedFindingsSection( ), 'entries' => $entries, 'empty_state' => $selectedTenant instanceof ManagedEnvironment - ? 'No assigned findings match this tenant filter right now.' + ? 'No assigned findings match this environment filter right now.' : 'No assigned findings are visible right now.', ]; } @@ -358,7 +358,7 @@ private function intakeFindingsSection( ), 'entries' => $entries, 'empty_state' => $selectedTenant instanceof ManagedEnvironment - ? 'No intake findings match this tenant filter right now.' + ? 'No intake findings match this environment filter right now.' : 'No intake findings are visible right now.', ]; } @@ -409,7 +409,7 @@ private function operationsSection( ), 'entries' => $entries, 'empty_state' => $selectedTenant instanceof ManagedEnvironment - ? 'No stale or terminal follow-up operations match this tenant filter right now.' + ? 'No stale or terminal follow-up operations match this environment filter right now.' : 'No stale or terminal follow-up operations are visible right now.', ]; } @@ -456,7 +456,7 @@ private function alertsSection( ), 'entries' => $entries, 'empty_state' => $selectedTenant instanceof ManagedEnvironment - ? 'No failed alert deliveries match this tenant filter right now.' + ? 'No failed alert deliveries match this environment filter right now.' : 'No failed alert deliveries are visible right now.', ]; } @@ -554,7 +554,7 @@ private function reviewFollowUpSection( : $this->appendQuery(CustomerReviewWorkspace::getUrl(panel: 'admin'), $navigationContext?->toQuery() ?? []), 'entries' => array_slice($rawEntries, 0, self::PREVIEW_LIMIT), 'empty_state' => $selectedTenant instanceof ManagedEnvironment - ? 'No review follow-up is visible for this tenant filter right now.' + ? 'No review follow-up is visible for this environment filter right now.' : 'No review follow-up is visible right now.', ]; } diff --git a/apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php b/apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php index d5a8e6ff..a05431dc 100644 --- a/apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php +++ b/apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php @@ -3,14 +3,21 @@ namespace App\Support\Middleware; use App\Filament\Pages\WorkspaceOverview; +use App\Filament\Pages\Governance\GovernanceInbox; +use App\Filament\Pages\Reviews\CustomerReviewWorkspace; +use App\Filament\Pages\Settings\WorkspaceSettings; use App\Filament\Resources\AlertDeliveryResource; use App\Filament\Resources\AlertDestinationResource; use App\Filament\Resources\AlertRuleResource; +use App\Filament\Resources\ProviderConnectionResource; use App\Models\ManagedEnvironment; use App\Models\User; +use App\Models\Workspace; use App\Models\WorkspaceMembership; +use App\Services\Auth\WorkspaceCapabilityResolver; use App\Services\Auth\WorkspaceRoleCapabilityMap; use App\Support\Auth\Capabilities; +use App\Support\Navigation\NavigationScope; use App\Support\OperationRunLinks; use App\Support\OperateHub\OperateHubShell; use App\Support\Tenants\TenantPageCategory; @@ -162,7 +169,7 @@ private function configureNavigationForRequest(\Filament\Panel $panel): void return; } - if (filled(Filament::getTenant())) { + if (NavigationScope::isEnvironmentSurface()) { $panel->navigation(true); return; @@ -171,6 +178,36 @@ private function configureNavigationForRequest(\Filament\Panel $panel): void $panel->navigation(function (): NavigationBuilder { return app(NavigationBuilder::class) ->item(WorkspaceOverview::navigationItem()) + ->item( + NavigationItem::make('Governance inbox') + ->url(fn (): string => GovernanceInbox::getUrl(panel: 'admin')) + ->icon('heroicon-o-inbox-stack') + ->group('Governance') + ->sort(5), + ) + ->item( + NavigationItem::make('Customer reviews') + ->url(fn (): string => CustomerReviewWorkspace::getUrl(panel: 'admin')) + ->icon('heroicon-o-document-text') + ->group('Reporting') + ->sort(44), + ) + ->item( + NavigationItem::make('Integrations') + ->url(fn (): string => route('filament.admin.resources.provider-connections.index')) + ->icon('heroicon-o-link') + ->group('Settings') + ->sort(15) + ->visible(fn (): bool => ProviderConnectionResource::canViewAny()), + ) + ->item( + NavigationItem::make('Settings') + ->url(fn (): string => WorkspaceSettings::getUrl(panel: 'admin')) + ->icon('heroicon-o-cog-6-tooth') + ->group('Settings') + ->sort(20) + ->visible(fn (): bool => $this->canViewWorkspaceSettings()), + ) ->item( NavigationItem::make('Manage workspaces') ->url(fn (): string => route('filament.admin.resources.workspaces.index')) @@ -240,6 +277,32 @@ private function configureNavigationForRequest(\Filament\Panel $panel): void }); } + private function canViewWorkspaceSettings(): bool + { + $user = auth()->user(); + + if (! $user instanceof User) { + return false; + } + + $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); + + if (! is_int($workspaceId)) { + return false; + } + + $workspace = Workspace::query()->whereKey($workspaceId)->first(); + + if (! $workspace instanceof Workspace) { + return false; + } + + $resolver = app(WorkspaceCapabilityResolver::class); + + return $resolver->isMember($user, $workspace) + && $resolver->can($user, $workspace, Capabilities::WORKSPACE_SETTINGS_VIEW); + } + private function isWorkspaceScopedPageWithTenant(string $path): bool { return preg_match('#^/admin/workspaces/[^/]+/environments/[^/]+/(required-permissions|diagnostics|access-scopes)$#', $path) === 1; diff --git a/apps/platform/app/Support/Navigation/NavigationScope.php b/apps/platform/app/Support/Navigation/NavigationScope.php new file mode 100644 index 00000000..9bea4cf2 --- /dev/null +++ b/apps/platform/app/Support/Navigation/NavigationScope.php @@ -0,0 +1,55 @@ +path(), '/'); + + if (! self::isLivewireUpdatePath($path)) { + return $path; + } + + $refererPath = parse_url((string) $request->headers->get('referer', ''), PHP_URL_PATH); + + return is_string($refererPath) && $refererPath !== '' + ? '/'.ltrim($refererPath, '/') + : $path; + } + + private static function isLivewireUpdatePath(string $path): bool + { + return preg_match('#^/livewire(?:-[^/]+)?/update$#', $path) === 1; + } +} diff --git a/apps/platform/app/Support/ReasonTranslation/ReasonTranslator.php b/apps/platform/app/Support/ReasonTranslation/ReasonTranslator.php index bdbb8aec..99ab0eac 100644 --- a/apps/platform/app/Support/ReasonTranslation/ReasonTranslator.php +++ b/apps/platform/app/Support/ReasonTranslation/ReasonTranslator.php @@ -252,9 +252,9 @@ private function translateBaselineReason(string $reasonCode, array $context = [] ], BaselineReasonCodes::COMPARE_NO_ELIGIBLE_TARGET => [ 'No eligible compare target', - 'No assigned tenant with compare access is currently available for this baseline profile.', + 'No assigned environment with compare access is currently available for this baseline profile.', 'prerequisite_missing', - 'Assign this baseline to a tenant you can compare, or use an account with access to an assigned tenant.', + 'Assign this baseline to an environment you can compare, or use an account with access to an assigned environment.', ], BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT => [ 'Selected snapshot unavailable', diff --git a/apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php b/apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php index cfb733b8..4098b481 100644 --- a/apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php +++ b/apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php @@ -309,10 +309,10 @@ public static function rules(): array reasonPolicy: GovernanceReasonPolicy::Required, dangerPolicy: 'required', canonicalLabel: 'Archive', - modalHeading: 'Archive tenant', - modalDescription: 'Archive this tenant. TenantPilot keeps it available for inspection and audit history but removes it from active management flows.', - successTitle: 'ManagedEnvironment archived', - auditVerb: 'archive tenant', + modalHeading: 'Archive environment', + modalDescription: 'Archive this environment. TenantPilot keeps it available for inspection and audit history but removes it from active management flows.', + successTitle: 'Environment archived', + auditVerb: 'archive environment', serviceOwner: 'TenantResource', surfaceKeys: ['tenant_index_row', 'view_tenant', 'edit_tenant'], ), @@ -323,10 +323,10 @@ public static function rules(): array reasonPolicy: GovernanceReasonPolicy::None, dangerPolicy: 'none', canonicalLabel: 'Restore', - modalHeading: 'Restore tenant', - modalDescription: 'Restore this tenant so it becomes available again in normal management flows.', - successTitle: 'ManagedEnvironment restored', - auditVerb: 'restore tenant', + modalHeading: 'Restore environment', + modalDescription: 'Restore this environment so it becomes available again in normal management flows.', + successTitle: 'Environment restored', + auditVerb: 'restore environment', serviceOwner: 'TenantResource', surfaceKeys: ['tenant_index_row', 'view_tenant', 'edit_tenant'], ), diff --git a/apps/platform/app/Support/Workspaces/WorkspaceOverviewBuilder.php b/apps/platform/app/Support/Workspaces/WorkspaceOverviewBuilder.php index 18f2dc2c..e19258a7 100644 --- a/apps/platform/app/Support/Workspaces/WorkspaceOverviewBuilder.php +++ b/apps/platform/app/Support/Workspaces/WorkspaceOverviewBuilder.php @@ -1017,7 +1017,7 @@ private function summaryMetrics( category: 'backup_health', description: $this->reviewSummaryMetricDescription( family: PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH, - baseDescription: 'Visible tenants with non-healthy backup posture.', + baseDescription: 'Visible environments with non-healthy backup posture.', triageReviewSummaries: $triageReviewSummaries, ), color: $backupAttentionTenantCount > 0 ? 'danger' : 'gray', @@ -1034,7 +1034,7 @@ private function summaryMetrics( category: 'recovery_evidence', description: $this->reviewSummaryMetricDescription( family: PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE, - baseDescription: 'Visible tenants with weakened or unvalidated recovery evidence.', + baseDescription: 'Visible environments with weakened or unvalidated recovery evidence.', triageReviewSummaries: $triageReviewSummaries, ), color: $recoveryAttentionTenantCount > 0 ? 'warning' : 'gray', @@ -1397,7 +1397,7 @@ private function calmnessState( 'is_calm' => false, 'checked_domains' => $checkedDomains, 'title' => 'Workspace activity still needs review', - 'body' => 'Backup health or recovery evidence still needs follow-up, or activity remains open in the visible workspace, but your current scope does not expose a more specific tenant drill-through here. Review operations first.', + 'body' => 'Backup health or recovery evidence still needs follow-up, or activity remains open in the visible workspace, but your current scope does not expose a more specific environment drill-through here. Review operations first.', 'next_action' => $this->operationsIndexTarget(null, $navigationContext, 'active'), ]; } @@ -1405,7 +1405,7 @@ private function calmnessState( return [ 'is_calm' => false, 'checked_domains' => $checkedDomains, - 'title' => 'Visible tenants still need attention', + 'title' => 'Visible environments still need attention', 'body' => 'Backup health or recovery evidence still needs follow-up, or governance and activity remain open in this workspace.', 'next_action' => $attentionItems[0]['destination'] ?? $this->chooseTenantTarget(), ]; diff --git a/apps/platform/lang/de/localization.php b/apps/platform/lang/de/localization.php index 1854e19b..85060857 100644 --- a/apps/platform/lang/de/localization.php +++ b/apps/platform/lang/de/localization.php @@ -45,23 +45,23 @@ 'switch_environment' => 'Umgebung wechseln', 'clear_environment_scope' => 'Umgebungskontext löschen', 'back_to_environment_registry' => 'Zur Umgebungsregistrierung zurück', - 'tenant_scope' => 'Tenant-Kontext', - 'select_tenant' => 'Tenant auswählen', - 'selected_tenant' => 'Ausgewählter Tenant', - 'no_tenant_selected' => 'Kein Tenant ausgewählt', - 'switch_tenant' => 'Tenant wechseln', - 'clear_tenant_scope' => 'Tenant-Kontext löschen', + 'tenant_scope' => 'Umgebungskontext', + 'select_tenant' => 'Umgebung auswählen', + 'selected_tenant' => 'Ausgewählte Umgebung', + 'no_tenant_selected' => 'Keine Umgebung ausgewählt', + 'switch_tenant' => 'Umgebung wechseln', + 'clear_tenant_scope' => 'Umgebungskontext löschen', 'context_unavailable' => 'Kontext nicht verfügbar', 'context_unavailable_workspace' => 'Der angeforderte Kontext konnte nicht wiederhergestellt werden. Die Shell zeigt stattdessen einen gültigen Workspace-Kontext.', 'context_unavailable_no_workspace' => 'Wählen Sie einen Workspace aus, um mit einem gültigen Admin-Kontext fortzufahren.', 'no_active_environments' => 'Keine aktiven Umgebungen verfügbar', 'no_active_environments_description' => 'In diesem Workspace sind keine auswählbaren aktiven Umgebungen für den normalen Betriebskontext verfügbar. Workspace-weite Seiten funktionieren weiterhin ohne ausgewählte Umgebung, und Sie können Onboarding- oder archivierte Einträge über Managed Environments prüfen.', 'view_managed_environments' => 'Managed Environments anzeigen', - 'no_active_tenants' => 'In diesem Workspace sind keine aktiven Tenants für den Standardbetrieb verfügbar.', - 'view_managed_tenants' => 'Managed Tenants anzeigen', - 'workspace_wide_available' => 'Kein Tenant ausgewählt. Workspace-weite Seiten bleiben verfügbar; ein Tenant setzt nur den normalen aktiven Betriebskontext.', + 'no_active_tenants' => 'In diesem Workspace sind keine aktiven Umgebungen für den Standardbetrieb verfügbar.', + 'view_managed_tenants' => 'Managed Environments anzeigen', + 'workspace_wide_available' => 'Keine Umgebung ausgewählt. Workspace-weite Seiten bleiben verfügbar; eine Umgebung setzt nur den normalen aktiven Betriebskontext.', 'search_environments' => 'Umgebungen suchen...', - 'search_tenants' => 'Tenants suchen...', + 'search_tenants' => 'Umgebungen suchen...', 'choose_workspace_first' => 'Wählen Sie zuerst einen Workspace aus.', ], 'workspace' => [ @@ -81,7 +81,7 @@ 'auth' => [ 'microsoft_not_configured' => 'Microsoft-Anmeldung ist nicht konfiguriert.', 'sign_in_microsoft' => 'Mit Microsoft anmelden', - 'tenant_admin_membership_required' => 'ManagedEnvironment-Admin-Zugriff erfordert eine ManagedEnvironment-Mitgliedschaft.', + 'tenant_admin_membership_required' => 'Admin-Zugriff erfordert eine Umgebungsmitgliedschaft.', ], 'navigation' => [ 'findings' => 'Findings', @@ -96,7 +96,7 @@ 'dashboard' => 'Dashboard', ], 'dashboard' => [ - 'tenant_title' => 'Tenant-Dashboard', + 'tenant_title' => 'Umgebungs-Dashboard', 'environment_title' => 'Umgebungs-Dashboard', 'system_title' => 'System-Dashboard', 'more_actions' => 'Mehr', @@ -139,7 +139,7 @@ 'open_external_ticket' => 'Externes Ticket öffnen', 'open_support_diagnostics' => 'Supportdiagnosen öffnen', 'support_diagnostics' => 'Supportdiagnosen', - 'support_diagnostics_description' => 'Redaktionell bereinigter Tenant-Kontext aus bestehenden Datensätzen.', + 'support_diagnostics_description' => 'Redaktionell bereinigter Umgebungskontext aus bestehenden Datensätzen.', 'close' => 'Schließen', 'time_window' => 'Zeitfenster', 'window' => 'Fenster', @@ -150,7 +150,7 @@ 'overview' => [ 'page_subheading' => 'Umgebungs-Governance-Übersicht', 'context_workspace' => 'Aktueller Workspace', - 'context_no_tenant' => 'Kein Tenant ausgewählt', + 'context_no_tenant' => 'Keine Umgebung ausgewählt', 'context_no_environment' => 'Keine Umgebung ausgewählt', 'context_workspace_chip' => 'Workspace: :workspace', 'context_provider_chip' => ':provider-Umgebung', @@ -162,15 +162,15 @@ 'status_not_ready' => 'Nicht bereit', 'status_evidence_available' => 'Nachweise verfügbar', 'status_needs_action' => 'Aufmerksamkeit erforderlich', - 'tenant_context_unavailable_headline' => 'Tenant-Kontext ist nicht verfügbar.', - 'tenant_context_unavailable_summary' => 'Wählen Sie einen Tenant aus, um die entscheidungsorientierte Dashboard-Übersicht anzuzeigen.', + 'tenant_context_unavailable_headline' => 'Umgebungskontext ist nicht verfügbar.', + 'tenant_context_unavailable_summary' => 'Wählen Sie eine Umgebung aus, um die entscheidungsorientierte Dashboard-Übersicht anzuzeigen.', 'environment_context_unavailable_headline' => 'Umgebungskontext ist nicht verfügbar.', 'environment_context_unavailable_summary' => 'Wählen Sie eine Umgebung aus, um die entscheidungsorientierte Dashboard-Übersicht anzuzeigen.', - 'posture_blocked_headline' => 'Provider-Berechtigungen blockieren Tenant-Workflows.', + 'posture_blocked_headline' => 'Provider-Berechtigungen blockieren Umgebungs-Workflows.', 'posture_blocked_summary' => 'Erforderliche Anwendungsberechtigungen fehlen. Provider-gestützte Abläufe können deshalb nicht als bereit bewertet werden.', - 'posture_calm_headline' => 'Kein unmittelbarer Tenant-Blocker ist sichtbar.', + 'posture_calm_headline' => 'Kein unmittelbarer Umgebungs-Blocker ist sichtbar.', 'posture_calm_summary' => 'Aktuelle Findings, Berechtigungen, Wiederherstellungsstatus und letzte Vorgänge zeigen derzeit keinen dringenden Folgeschritt.', - 'posture_action_needed_fallback_summary' => 'Der Tenant benötigt noch Operator-Nachverfolgung, bevor die Startseite als ruhig gelten kann.', + 'posture_action_needed_fallback_summary' => 'Die Umgebung benötigt noch Operator-Nachverfolgung, bevor die Startseite als ruhig gelten kann.', 'section_recommended_actions' => 'Empfohlene nächste Aktionen', 'section_governance_status' => 'Governance-Status', 'section_readiness' => 'Review- und Nachweisbereitschaft', @@ -180,9 +180,9 @@ 'label_reason' => 'Grund', 'label_impact' => 'Auswirkung', 'empty_recommended_actions_headline' => 'Derzeit wartet keine unmittelbare Aktion.', - 'empty_recommended_actions_summary' => 'Der Tenant wirkt aktuell ruhig. Nutzen Sie die Status- und Bereitschaftsbereiche unten, um zu bestätigen, was gesund ist und was lediglich nicht verfügbar ist.', + 'empty_recommended_actions_summary' => 'Die Umgebung wirkt aktuell ruhig. Nutzen Sie die Status- und Bereitschaftsbereiche unten, um zu bestätigen, was gesund ist und was lediglich nicht verfügbar ist.', 'empty_recent_operations_headline' => 'Noch keine letzten Vorgänge.', - 'empty_recent_operations_summary' => 'Sobald Tenant-Vorgänge laufen, erscheint hier der letzte Ausführungskontext, ohne die erste Entscheidungsebene zu übernehmen.', + 'empty_recent_operations_summary' => 'Sobald Umgebungs-Vorgänge laufen, erscheint hier der letzte Ausführungskontext, ohne die erste Entscheidungsebene zu übernehmen.', 'kpi_high_severity_label' => 'Findings mit hoher Kritikalität', 'kpi_high_severity_description' => 'Hohe oder kritische Findings warten noch auf Prüfung.', 'kpi_high_severity_tendency' => ':count aktiv', @@ -193,13 +193,13 @@ 'kpi_overdue_tendency' => ':count aktuell überfällig', 'kpi_overdue_tendency_none' => 'Nichts überfällig', 'kpi_missing_permissions_label' => 'Fehlende Berechtigungen', - 'kpi_missing_permissions_description' => 'Für diesen Tenant fehlen derzeit providerseitig erforderliche Berechtigungen.', + 'kpi_missing_permissions_description' => 'Für diese Umgebung fehlen derzeit providerseitig erforderliche Berechtigungen.', 'kpi_missing_permissions_tendency_split' => ':app App · :delegated delegiert fehlen', 'kpi_missing_permissions_tendency_app_only' => ':count App-Berechtigungen fehlen', 'kpi_missing_permissions_tendency_delegated_only' => ':count delegierte Berechtigungen fehlen', 'kpi_missing_permissions_tendency_none' => 'Berechtigungen vollständig', 'kpi_active_operations_label' => 'Vorgänge mit Aufmerksamkeitsbedarf', - 'kpi_active_operations_description' => 'Vorgangsläufe, die noch Follow-up benötigen, bevor der Tenant als gesund gelten kann.', + 'kpi_active_operations_description' => 'Vorgangsläufe, die noch Follow-up benötigen, bevor die Umgebung als gesund gelten kann.', 'kpi_active_operations_tendency' => ':count Vorgänge erfordern Aufmerksamkeit', 'kpi_active_operations_tendency_window' => ':count Vorgänge erfordern Aufmerksamkeit', 'kpi_active_operations_tendency_one' => '1 Vorgang benötigt Follow-up', @@ -230,18 +230,18 @@ 'reason_missing_application_permissions' => ':count Anwendungsberechtigung(en) fehlen noch.', 'impact_missing_application_permissions' => 'Provider-gestützte Inventarisierung, Verifikation und Berichte bleiben blockiert, bis die Zustimmung wiederhergestellt ist.', 'reason_missing_delegated_permissions' => ':count delegierte Berechtigung(en) benötigen noch Aufmerksamkeit.', - 'impact_missing_delegated_permissions' => 'Der Tenant bleibt nur teilweise bereit, bis delegierte Berechtigungen geprüft und bei Bedarf erteilt wurden.', + 'impact_missing_delegated_permissions' => 'Die Umgebung bleibt nur teilweise bereit, bis delegierte Berechtigungen geprüft und bei Bedarf erteilt wurden.', 'reason_high_severity_findings' => ':count Findings mit hoher Kritikalität benötigen noch eine Operator-Prüfung.', 'impact_high_severity_findings' => 'Schwere Drift bleibt ungelöst, bis diese Findings triagiert oder behoben sind.', 'reason_overdue_findings' => ':count Finding(s) sind bereits überfällig.', - 'impact_overdue_findings' => 'Workflow-Verzögerungen können die aktuelle Risikolage des Tenants verschleiern, bis überfällige Punkte bereinigt sind.', + 'impact_overdue_findings' => 'Workflow-Verzögerungen können die aktuelle Risikolage der Umgebung verschleiern, bis überfällige Punkte bereinigt sind.', 'reason_risk_exceptions' => ':count Risikoausnahme(n) oder Governance-Punkte mit akzeptiertem Risiko benötigen Nachverfolgung.', 'impact_risk_exceptions' => 'Aussagen zu akzeptierten Risiken verlieren an Vertrauenswürdigkeit, wenn ihre Ausnahmehistorie nicht mehr aktuell ist.', 'impact_recovery_posture' => 'Die Wiederherstellungsbereitschaft sollte geprüft werden, bevor kundensichere Aussagen auf Backup- oder Restore-Vertrauen beruhen.', 'reason_terminal_operations' => ':count Vorgangslauf/Läufe endeten blockiert, teilweise oder fehlgeschlagen.', - 'impact_terminal_operations' => 'Terminale Laufergebnisse benötigen Nachverfolgung, bevor der Tenant als ruhig gelten kann.', + 'impact_terminal_operations' => 'Terminale Laufergebnisse benötigen Nachverfolgung, bevor die Umgebung als ruhig gelten kann.', 'reason_operations_requiring_attention' => 'Ein oder mehrere Vorgänge endeten mit einem Ergebnis, das Follow-up benötigt.', - 'impact_operations_requiring_attention' => 'Der Tenant sollte nicht als vollständig gesund betrachtet werden, bis das Vorgangsergebnis geprüft wurde.', + 'impact_operations_requiring_attention' => 'Die Umgebung sollte nicht als vollständig gesund betrachtet werden, bis das Vorgangsergebnis geprüft wurde.', 'reason_continue_review' => 'Kundensichere Ausgabe ist noch nicht vollständig bereit.', 'impact_continue_review' => 'Die Review-Ausgabe bleibt teilweise, bis Review-, Nachweis- und Paketflächen sauber zusammenpassen.', 'operations_attention_title' => 'Vorgänge mit Aufmerksamkeitsbedarf', @@ -253,16 +253,16 @@ 'operations_attention_outcome_generic' => 'Der Vorgang endete mit einem Ergebnis, das Nachverfolgung benötigt.', 'operations_attention_outcome_stale' => 'Der Vorgang ist noch aktiv, liegt aber außerhalb seines erwarteten Lebenszyklusfensters.', 'operations_attention_outcome_provider_consent_required' => 'Die Prüfung ist abgeschlossen, aber die Provider-Zustimmung ist noch erforderlich.', - 'operations_attention_reason_fallback' => 'Das aufgezeichnete Ergebnis muss geprüft werden, bevor der Tenant als gesund gelten kann.', + 'operations_attention_reason_fallback' => 'Das aufgezeichnete Ergebnis muss geprüft werden, bevor die Umgebung als gesund gelten kann.', 'operations_attention_reason_stale' => 'Der Lauf liegt außerhalb seines normalen Lebenszyklusfensters und schreitet möglicherweise nicht mehr fort.', 'operations_attention_reason_provider_consent_required' => 'Eine Admin-Zustimmung ist erforderlich, bevor die Provider-Verbindung verwendet werden kann.', - 'operations_attention_impact_follow_up' => 'Die Tenant-Bereitschaft sollte nicht als vollständig gesund betrachtet werden, bis das Vorgangsergebnis geprüft wurde.', - 'operations_attention_impact_stale' => 'Die Tenant-Bereitschaft sollte nicht als aktuell betrachtet werden, bis der blockierte Lauf geprüft wurde.', - 'operations_attention_impact_provider_consent_required' => 'Die Tenant-Bereitschaft kann nicht als gesund betrachtet werden, bis dies geprüft wurde.', + 'operations_attention_impact_follow_up' => 'Die Umgebungsbereitschaft sollte nicht als vollständig gesund betrachtet werden, bis das Vorgangsergebnis geprüft wurde.', + 'operations_attention_impact_stale' => 'Die Umgebungsbereitschaft sollte nicht als aktuell betrachtet werden, bis der blockierte Lauf geprüft wurde.', + 'operations_attention_impact_provider_consent_required' => 'Die Umgebungsbereitschaft kann nicht als gesund betrachtet werden, bis dies geprüft wurde.', 'operations_attention_timing_completed' => 'Abgeschlossen :time', 'operations_attention_timing_started' => 'Gestartet :time', 'governance_baseline_compare_label' => 'Baseline Compare', - 'governance_baseline_compare_description' => 'Aktueller Compare-Status für die Tenant-Baseline.', + 'governance_baseline_compare_description' => 'Aktueller Compare-Status für die Umgebungs-Baseline.', 'governance_evidence_coverage_label' => 'Nachweisabdeckung', 'governance_review_freshness_label' => 'Review-Aktualität', 'governance_provider_permissions_label' => 'Provider-Berechtigungen', @@ -270,7 +270,7 @@ 'governance_backup_posture_unavailable_description' => 'Wiederherstellungsbereitschaft ist noch nicht verfügbar.', 'readiness_current_review_title' => 'Aktuelles Review', 'readiness_current_review_empty_status' => 'Kein aktives Review', - 'readiness_current_review_empty_description' => 'Für diesen Tenant ist aktuell kein Review in Bearbeitung.', + 'readiness_current_review_empty_description' => 'Für diese Umgebung ist aktuell kein Review in Bearbeitung.', 'readiness_current_review_updated_label' => 'Zuletzt aktualisiert', 'readiness_current_review_findings_progress_label' => 'Findings mit Ergebnis', 'readiness_current_review_completion_progress_label' => 'Review-Fortschritt', @@ -285,16 +285,16 @@ 'readiness_risk_exceptions_pending_label' => 'Ausstehende Freigaben', 'readiness_provider_health_title' => 'Provider Health', 'readiness_provider_health_empty_status' => 'Provider-Status nicht verfügbar', - 'readiness_provider_health_empty_description' => 'Für diesen Tenant liegt aktuell kein Provider-Health-Snapshot vor.', + 'readiness_provider_health_empty_description' => 'Für diese Umgebung liegt aktuell kein Provider-Health-Snapshot vor.', 'readiness_provider_health_last_check_label' => 'Letzter Check', 'readiness_provider_health_permissions_label' => 'Fehlende Berechtigungen', 'readiness_provider_health_snapshot_label' => 'Berechtigungs-Snapshot', - 'readiness_provider_health_degraded_description' => 'Der letzte Provider-Health-Check hat Warnungen für diesen Tenant gemeldet.', - 'readiness_provider_health_blocked_description' => 'Der Provider-Health-Check ist für diesen Tenant aktuell blockiert.', - 'readiness_provider_health_error_description' => 'Der Provider-Health-Check ist für diesen Tenant fehlgeschlagen.', - 'readiness_provider_health_pending_description' => 'Der letzte Provider-Health-Check läuft für diesen Tenant noch.', + 'readiness_provider_health_degraded_description' => 'Der letzte Provider-Health-Check hat Warnungen für diese Umgebung gemeldet.', + 'readiness_provider_health_blocked_description' => 'Der Provider-Health-Check ist für diese Umgebung aktuell blockiert.', + 'readiness_provider_health_error_description' => 'Der Provider-Health-Check ist für diese Umgebung fehlgeschlagen.', + 'readiness_provider_health_pending_description' => 'Der letzte Provider-Health-Check läuft für diese Umgebung noch.', 'readiness_provider_health_unknown_description' => 'Für diesen Tenant wurde noch kein Provider-Health-Check erfasst.', - 'helper_findings_requires_permissions' => 'Sie sehen den Tenant-Status, aber zum Öffnen von Findings sind zusätzliche Berechtigungen erforderlich.', + 'helper_findings_requires_permissions' => 'Sie sehen den Umgebungsstatus, aber zum Öffnen von Findings sind zusätzliche Berechtigungen erforderlich.', 'helper_risk_exceptions_requires_permissions' => 'Sie sehen die Zusammenfassung, aber zum Öffnen von Risikoausnahmen sind zusätzliche Berechtigungen erforderlich.', 'helper_review_requires_permissions' => 'Sie sehen die Zusammenfassung, aber zum Öffnen der Review-Details sind zusätzliche Berechtigungen erforderlich.', 'helper_continue_review_requires_manage' => 'Sie können den aktuellen Review-Status einsehen, aber nur Review-Manager können den Review-Workflow fortsetzen.', @@ -306,7 +306,7 @@ 'helper_backup_posture_requires_permissions' => 'Sie sehen die Zusammenfassung, aber zum Öffnen der Backup-Statusdetails sind zusätzliche Berechtigungen erforderlich.', 'evidence_unavailable_description' => 'Derzeit ist kein Nachweis-Snapshot für kundensichere Ausgabe verfügbar.', 'evidence_generated_prefix' => 'Letzter Nachweis-Snapshot erstellt :time.', - 'review_unavailable_description' => 'Für diesen Tenant ist derzeit kein Tenant-Review verfügbar.', + 'review_unavailable_description' => 'Für diese Umgebung ist derzeit kein Review verfügbar.', 'review_updated_prefix' => 'Letztes Review aktualisiert :time.', 'provider_permissions_ready' => 'Bereit', 'provider_permissions_blocked' => 'Blockiert', @@ -321,7 +321,7 @@ 'risk_exceptions_pending_description' => 'Ausstehende, auslaufende oder abgelaufene Ausnahmen benötigen weiterhin Governance-Nachverfolgung.', 'risk_exceptions_active_description' => 'Ausnahmen für akzeptierte Risiken bestehen, benötigen aktuell aber keine dringende Prüfung.', 'risk_exceptions_calm_description' => 'Derzeit benötigen keine Risikoausnahmen Aufmerksamkeit.', - 'recent_operation_fallback_summary' => 'Aktueller Vorgangskontext für diesen Tenant.', + 'recent_operation_fallback_summary' => 'Aktueller Vorgangskontext für diese Umgebung.', ], ], 'review' => [ diff --git a/apps/platform/lang/en/localization.php b/apps/platform/lang/en/localization.php index d0950a89..4c668f54 100644 --- a/apps/platform/lang/en/localization.php +++ b/apps/platform/lang/en/localization.php @@ -45,23 +45,23 @@ 'switch_environment' => 'Switch environment', 'clear_environment_scope' => 'Clear environment scope', 'back_to_environment_registry' => 'Back to environment registry', - 'tenant_scope' => 'Tenant scope', - 'select_tenant' => 'Select tenant', - 'selected_tenant' => 'Selected tenant', - 'no_tenant_selected' => 'No tenant selected', - 'switch_tenant' => 'Switch tenant', - 'clear_tenant_scope' => 'Clear tenant scope', + 'tenant_scope' => 'Environment scope', + 'select_tenant' => 'Select environment', + 'selected_tenant' => 'Selected environment', + 'no_tenant_selected' => 'No environment selected', + 'switch_tenant' => 'Switch environment', + 'clear_tenant_scope' => 'Clear environment scope', 'context_unavailable' => 'Context unavailable', 'context_unavailable_workspace' => 'The requested scope could not be restored. The shell is showing a valid workspace state instead.', 'context_unavailable_no_workspace' => 'Choose a workspace to continue with a valid admin context.', 'no_active_environments' => 'No active environments available', 'no_active_environments_description' => 'There are no selectable active environments for the normal operating context in this workspace. Workspace-level pages still work with no environment selected, and you can inspect onboarding or archived records through managed environments.', 'view_managed_environments' => 'View managed environments', - 'no_active_tenants' => 'No active tenants are available for the standard operating context in this workspace.', - 'view_managed_tenants' => 'View managed tenants', - 'workspace_wide_available' => 'No tenant selected. Workspace-wide pages remain available, and choosing a tenant only sets the normal active operating context.', + 'no_active_tenants' => 'No active environments are available for the standard operating context in this workspace.', + 'view_managed_tenants' => 'View managed environments', + 'workspace_wide_available' => 'No environment selected. Workspace-wide pages remain available, and choosing an environment only sets the normal active operating context.', 'search_environments' => 'Search environments...', - 'search_tenants' => 'Search tenants...', + 'search_tenants' => 'Search environments...', 'choose_workspace_first' => 'Choose a workspace first.', ], 'workspace' => [ @@ -81,7 +81,7 @@ 'auth' => [ 'microsoft_not_configured' => 'Microsoft sign-in is not configured.', 'sign_in_microsoft' => 'Sign in with Microsoft', - 'tenant_admin_membership_required' => 'Tenant Admin access requires a tenant membership.', + 'tenant_admin_membership_required' => 'Admin access requires an environment membership.', ], 'navigation' => [ 'findings' => 'Findings', @@ -96,7 +96,7 @@ 'dashboard' => 'Dashboard', ], 'dashboard' => [ - 'tenant_title' => 'Tenant dashboard', + 'tenant_title' => 'Environment dashboard', 'environment_title' => 'Environment dashboard', 'system_title' => 'System dashboard', 'more_actions' => 'More', @@ -139,7 +139,7 @@ 'open_external_ticket' => 'Open external ticket', 'open_support_diagnostics' => 'Open support diagnostics', 'support_diagnostics' => 'Support diagnostics', - 'support_diagnostics_description' => 'Redacted tenant context from existing records.', + 'support_diagnostics_description' => 'Redacted environment context from existing records.', 'close' => 'Close', 'time_window' => 'Time window', 'window' => 'Window', @@ -150,7 +150,7 @@ 'overview' => [ 'page_subheading' => 'Environment governance overview', 'context_workspace' => 'Current workspace', - 'context_no_tenant' => 'No tenant selected', + 'context_no_tenant' => 'No environment selected', 'context_no_environment' => 'No environment selected', 'context_workspace_chip' => 'Workspace: :workspace', 'context_provider_chip' => ':provider environment', @@ -162,15 +162,15 @@ 'status_not_ready' => 'Not ready', 'status_evidence_available' => 'Evidence available', 'status_needs_action' => 'Needs attention', - 'tenant_context_unavailable_headline' => 'Tenant context is not available.', - 'tenant_context_unavailable_summary' => 'Select a tenant to view the decision-first dashboard overview.', + 'tenant_context_unavailable_headline' => 'Environment context is not available.', + 'tenant_context_unavailable_summary' => 'Select an environment to view the decision-first dashboard overview.', 'environment_context_unavailable_headline' => 'Environment context is not available.', 'environment_context_unavailable_summary' => 'Select an environment to view the decision-first dashboard overview.', - 'posture_blocked_headline' => 'Provider permissions are blocking tenant workflows.', + 'posture_blocked_headline' => 'Provider permissions are blocking environment workflows.', 'posture_blocked_summary' => 'Required application permissions are missing, so provider-backed operations cannot be treated as healthy readiness.', - 'posture_calm_headline' => 'No immediate tenant blocker is visible.', + 'posture_calm_headline' => 'No immediate environment blocker is visible.', 'posture_calm_summary' => 'Current findings, permissions, recovery posture, and recent operations do not show an urgent follow-up path.', - 'posture_action_needed_fallback_summary' => 'The tenant still needs operator follow-up before the landing page can stay calm.', + 'posture_action_needed_fallback_summary' => 'The environment still needs operator follow-up before the landing page can stay calm.', 'section_recommended_actions' => 'Recommended next actions', 'section_governance_status' => 'Governance status', 'section_readiness' => 'Review & evidence readiness', @@ -180,9 +180,9 @@ 'label_reason' => 'Reason', 'label_impact' => 'Impact', 'empty_recommended_actions_headline' => 'No immediate action is waiting.', - 'empty_recommended_actions_summary' => 'The tenant currently looks calm. Use the status and readiness sections below to confirm what is healthy and what is simply unavailable.', + 'empty_recommended_actions_summary' => 'The environment currently looks calm. Use the status and readiness sections below to confirm what is healthy and what is simply unavailable.', 'empty_recent_operations_headline' => 'No recent operations yet.', - 'empty_recent_operations_summary' => 'Once tenant operations run, the most recent execution context will appear here without taking over the first decision layer.', + 'empty_recent_operations_summary' => 'Once environment operations run, the most recent execution context will appear here without taking over the first decision layer.', 'kpi_high_severity_label' => 'High severity findings', 'kpi_high_severity_description' => 'High or critical findings still needing review.', 'kpi_high_severity_tendency' => ':count active now', @@ -193,13 +193,13 @@ 'kpi_overdue_tendency' => ':count overdue now', 'kpi_overdue_tendency_none' => 'None overdue', 'kpi_missing_permissions_label' => 'Missing permissions', - 'kpi_missing_permissions_description' => 'Provider-required permissions currently missing for this tenant.', + 'kpi_missing_permissions_description' => 'Provider-required permissions currently missing for this environment.', 'kpi_missing_permissions_tendency_split' => ':app app · :delegated delegated missing', 'kpi_missing_permissions_tendency_app_only' => ':count app missing', 'kpi_missing_permissions_tendency_delegated_only' => ':count delegated missing', 'kpi_missing_permissions_tendency_none' => 'Permission set complete', 'kpi_active_operations_label' => 'Operations needing attention', - 'kpi_active_operations_description' => 'Operation runs that still need follow-up before the tenant can be treated as healthy.', + 'kpi_active_operations_description' => 'Operation runs that still need follow-up before the environment can be treated as healthy.', 'kpi_active_operations_tendency' => ':count operations require attention', 'kpi_active_operations_tendency_window' => ':count operations require attention', 'kpi_active_operations_tendency_one' => '1 operation needs follow-up', @@ -230,18 +230,18 @@ 'reason_missing_application_permissions' => ':count application permission(s) are still missing.', 'impact_missing_application_permissions' => 'Provider-backed inventory, verification, and reporting flows stay blocked until consent is restored.', 'reason_missing_delegated_permissions' => ':count delegated permission(s) still need attention.', - 'impact_missing_delegated_permissions' => 'The tenant stays partially ready until delegated permissions are reviewed and granted where needed.', + 'impact_missing_delegated_permissions' => 'The environment stays partially ready until delegated permissions are reviewed and granted where needed.', 'reason_high_severity_findings' => ':count high severity finding(s) still need operator review.', 'impact_high_severity_findings' => 'Severe drift stays unresolved until those findings are triaged or remediated.', 'reason_overdue_findings' => ':count finding(s) are already overdue.', - 'impact_overdue_findings' => 'Workflow delay can hide the tenant\'s current risk posture until overdue items are cleared.', + 'impact_overdue_findings' => 'Workflow delay can hide the environment\'s current risk posture until overdue items are cleared.', 'reason_risk_exceptions' => ':count risk exception(s) or accepted-risk governance item(s) need follow-up.', 'impact_risk_exceptions' => 'Accepted-risk statements stop being trustworthy when their exception history is no longer current.', 'impact_recovery_posture' => 'Recovery readiness should be checked before customer-safe claims rely on backup or restore confidence.', 'reason_terminal_operations' => ':count operation run(s) finished blocked, partial, or failed.', - 'impact_terminal_operations' => 'Terminal run outcomes need follow-up before the tenant can be treated as calm.', + 'impact_terminal_operations' => 'Terminal run outcomes need follow-up before the environment can be treated as calm.', 'reason_operations_requiring_attention' => 'One or more operations finished with an outcome that needs follow-up.', - 'impact_operations_requiring_attention' => 'The tenant should not be treated as fully healthy until the operation outcome has been reviewed.', + 'impact_operations_requiring_attention' => 'The environment should not be treated as fully healthy until the operation outcome has been reviewed.', 'reason_continue_review' => 'Customer-safe output is not fully ready yet.', 'impact_continue_review' => 'Review output stays partial until the review, evidence, and pack surfaces line up cleanly.', 'operations_attention_title' => 'Operations requiring attention', @@ -253,16 +253,16 @@ 'operations_attention_outcome_generic' => 'The operation finished with an outcome that needs follow-up.', 'operations_attention_outcome_stale' => 'The operation is still active, but it is past its expected lifecycle window.', 'operations_attention_outcome_provider_consent_required' => 'The check finished, but provider consent is still required.', - 'operations_attention_reason_fallback' => 'The recorded outcome still needs operator review before the tenant can be treated as healthy.', + 'operations_attention_reason_fallback' => 'The recorded outcome still needs operator review before the environment can be treated as healthy.', 'operations_attention_reason_stale' => 'The run is past its normal lifecycle window and may no longer be progressing.', 'operations_attention_reason_provider_consent_required' => 'Admin consent is required before the provider connection can be used.', - 'operations_attention_impact_follow_up' => 'Tenant readiness should not be treated as fully healthy until the operation outcome has been reviewed.', - 'operations_attention_impact_stale' => 'Tenant readiness should not be treated as current until the stalled run has been reviewed.', - 'operations_attention_impact_provider_consent_required' => 'Tenant readiness cannot be treated as healthy until this is reviewed.', + 'operations_attention_impact_follow_up' => 'Environment readiness should not be treated as fully healthy until the operation outcome has been reviewed.', + 'operations_attention_impact_stale' => 'Environment readiness should not be treated as current until the stalled run has been reviewed.', + 'operations_attention_impact_provider_consent_required' => 'Environment readiness cannot be treated as healthy until this is reviewed.', 'operations_attention_timing_completed' => 'Completed :time', 'operations_attention_timing_started' => 'Started :time', 'governance_baseline_compare_label' => 'Baseline compare', - 'governance_baseline_compare_description' => 'Current compare posture for the tenant baseline.', + 'governance_baseline_compare_description' => 'Current compare posture for the environment baseline.', 'governance_evidence_coverage_label' => 'Evidence coverage', 'governance_review_freshness_label' => 'Review freshness', 'governance_provider_permissions_label' => 'Provider permissions', @@ -270,7 +270,7 @@ 'governance_backup_posture_unavailable_description' => 'Recovery readiness is not yet available.', 'readiness_current_review_title' => 'Current review', 'readiness_current_review_empty_status' => 'No active review', - 'readiness_current_review_empty_description' => 'There is currently no review in progress for this tenant.', + 'readiness_current_review_empty_description' => 'There is currently no review in progress for this environment.', 'readiness_current_review_updated_label' => 'Last updated', 'readiness_current_review_findings_progress_label' => 'Findings with outcome', 'readiness_current_review_completion_progress_label' => 'Review completion', @@ -285,16 +285,16 @@ 'readiness_risk_exceptions_pending_label' => 'Pending approval', 'readiness_provider_health_title' => 'Provider Health', 'readiness_provider_health_empty_status' => 'Provider status unavailable', - 'readiness_provider_health_empty_description' => 'No provider health snapshot is currently available for this tenant.', + 'readiness_provider_health_empty_description' => 'No provider health snapshot is currently available for this environment.', 'readiness_provider_health_last_check_label' => 'Last check', 'readiness_provider_health_permissions_label' => 'Missing permissions', 'readiness_provider_health_snapshot_label' => 'Permissions snapshot', - 'readiness_provider_health_degraded_description' => 'The latest provider health check reported warnings for this tenant.', - 'readiness_provider_health_blocked_description' => 'The provider health check is currently blocked for this tenant.', - 'readiness_provider_health_error_description' => 'The provider health check failed for this tenant.', - 'readiness_provider_health_pending_description' => 'The latest provider health check is still pending.', + 'readiness_provider_health_degraded_description' => 'The latest provider health check reported warnings for this environment.', + 'readiness_provider_health_blocked_description' => 'The provider health check is currently blocked for this environment.', + 'readiness_provider_health_error_description' => 'The provider health check failed for this environment.', + 'readiness_provider_health_pending_description' => 'The latest provider health check is still pending for this environment.', 'readiness_provider_health_unknown_description' => 'No provider health check has been recorded yet.', - 'helper_findings_requires_permissions' => 'You can see the tenant posture, but opening findings requires additional permissions.', + 'helper_findings_requires_permissions' => 'You can see the environment posture, but opening findings requires additional permissions.', 'helper_risk_exceptions_requires_permissions' => 'You can see the summary, but opening risk exceptions requires additional permissions.', 'helper_review_requires_permissions' => 'You can see the summary, but opening review detail requires additional permissions.', 'helper_continue_review_requires_manage' => 'You can inspect the current review state, but only review managers can continue the review workflow.', @@ -306,7 +306,7 @@ 'helper_backup_posture_requires_permissions' => 'You can see the summary, but opening backup posture detail requires additional permissions.', 'evidence_unavailable_description' => 'No evidence snapshot is currently available for customer-safe output.', 'evidence_generated_prefix' => 'Latest evidence snapshot generated :time.', - 'review_unavailable_description' => 'No tenant review is currently available for this tenant.', + 'review_unavailable_description' => 'No review is currently available for this environment.', 'review_updated_prefix' => 'Latest review updated :time.', 'provider_permissions_ready' => 'Ready', 'provider_permissions_blocked' => 'Blocked', @@ -321,7 +321,7 @@ 'risk_exceptions_pending_description' => 'Pending, expiring, or expired exceptions still need governance follow-up.', 'risk_exceptions_active_description' => 'Accepted-risk exceptions exist but do not currently need urgent review.', 'risk_exceptions_calm_description' => 'No risk exceptions currently need attention.', - 'recent_operation_fallback_summary' => 'Recent operation context for this tenant.', + 'recent_operation_fallback_summary' => 'Recent operation context for this environment.', ], ], 'review' => [ diff --git a/apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php b/apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php index 01b278b0..23d3e21b 100644 --- a/apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php +++ b/apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php @@ -48,7 +48,7 @@ - Compare assigned tenants remains simulation only. This operator view changes presentation density, not compare truth, visible-set scope, or the existing drilldown path. + Compare assigned environments remains simulation only. This operator view changes presentation density, not compare truth, visible-set scope, or the existing drilldown path.
@@ -86,15 +86,15 @@

- Assigned tenants: {{ (int) ($reference['assignedTenantCount'] ?? 0) }}. - Visible tenants: {{ $visibleTenantCount }}. + Assigned environments: {{ (int) ($reference['assignedTenantCount'] ?? 0) }}. + Visible environments: {{ $visibleTenantCount }}. @if (filled($reference['referenceSnapshotCapturedAt'] ?? null)) Reference captured {{ \Illuminate\Support\Carbon::parse($reference['referenceSnapshotCapturedAt'])->diffForHumans() }}. @endif

- Auto mode resolves from the visible tenant set. Manual mode stays local to this route and never becomes stored preference truth. + Auto mode resolves from the visible environment set. Manual mode stays local to this route and never becomes stored preference truth.

@if (filled($reference['referenceReasonCode'] ?? null)) @@ -107,7 +107,7 @@
-
Visible tenants
+
Visible environments
{{ $visibleTenantCount }}
@@ -165,7 +165,7 @@ @else - Compact unlocks at one visible tenant + Compact unlocks at one visible environment @endif
@@ -201,7 +201,7 @@ @if ($hiddenAssignedTenantCount > 0) - Visible-set only. Hidden tenants never contribute to summaries or drilldowns. + Visible-set only. Hidden environments never contribute to summaries or drilldowns. @endif
@@ -223,7 +223,7 @@
Applied matrix scope

@if ($activeFilterCount === 0) - No narrowing filters are active. Showing every visible subject and tenant in the current baseline scope. + No narrowing filters are active. Showing every visible subject and environment in the current baseline scope. @else {{ $activeFilterCount }} active {{ \Illuminate\Support\Str::plural('filter', $activeFilterCount) }} are already shaping the rendered matrix. @endif @@ -250,7 +250,7 @@ - ManagedEnvironment sort: {{ $tenantSortOptions[$currentFilters['tenant_sort'] ?? 'tenant_name'] ?? 'ManagedEnvironment name' }} + Environment sort: {{ $tenantSortOptions[$currentFilters['tenant_sort'] ?? 'tenant_name'] ?? 'Environment name' }} @@ -337,8 +337,8 @@

Current scope

- {{ $visibleTenantCount }} visible {{ \Illuminate\Support\Str::plural('tenant', $visibleTenantCount) }}. - {{ $resolvedMode === 'dense' ? 'State-first dense scan stays active.' : 'Compact single-tenant review stays active.' }} + {{ $visibleTenantCount }} visible {{ \Illuminate\Support\Str::plural('environment', $visibleTenantCount) }}. + {{ $resolvedMode === 'dense' ? 'State-first dense scan stays active.' : 'Compact single-environment review stays active.' }}

@if ($policyTypeOptions !== []) @@ -454,7 +454,7 @@ - One visible tenant remains in scope, so the matrix collapses into a shorter subject-result list instead of a pseudo-grid. + One visible environment remains in scope, so the matrix collapses into a shorter subject-result list instead of a pseudo-grid. @if ($compactTenant) @@ -568,7 +568,7 @@
@if ($primaryUrl) - {{ filled($result['findingId'] ?? null) ? 'Open finding' : 'Open tenant compare' }} + {{ filled($result['findingId'] ?? null) ? 'Open finding' : 'Open environment compare' }} @endif @@ -591,7 +591,7 @@
@else - + The matrix body is state-first. Row click stays forbidden, the subject column stays pinned, and repeated follow-up actions move behind compact secondary reveals. @@ -649,7 +649,7 @@
@if ($tenantCompareUrl) - Open tenant compare + Open environment compare @endif @@ -830,7 +830,7 @@ @if ($primaryUrl)
- {{ filled($cell['findingId'] ?? null) ? 'Open finding' : 'Open tenant compare' }} + {{ filled($cell['findingId'] ?? null) ? 'Open finding' : 'Open environment compare' }}
@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 f186c06b..623703bc 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 @@ -32,7 +32,7 @@ {{ $summary['visible_unassigned'] }}
- Visible unassigned intake rows after the current tenant scope. + Visible unassigned intake rows after the current environment scope.
diff --git a/apps/platform/resources/views/filament/pages/governance/decision-register.blade.php b/apps/platform/resources/views/filament/pages/governance/decision-register.blade.php index e736bee9..19398550 100644 --- a/apps/platform/resources/views/filament/pages/governance/decision-register.blade.php +++ b/apps/platform/resources/views/filament/pages/governance/decision-register.blade.php @@ -57,10 +57,10 @@ class="inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm fo @if ($this->hasTenantPrefilter())
- The register is currently filtered to one tenant. + The register is currently filtered to one environment. - Clear tenant filter + Clear environment filter
@endif @@ -68,4 +68,4 @@ class="inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm fo
{{ $this->table }} - \ No newline at end of file + diff --git a/apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php b/apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php index b9d29f9d..2450db00 100644 --- a/apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php +++ b/apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php @@ -66,10 +66,10 @@ class="inline-flex items-center gap-2 rounded-full border px-3 py-1.5 text-sm fo @if ($this->hasTenantPrefilter())
- The inbox is currently filtered to one tenant. + The inbox is currently filtered to one environment. - Clear tenant filter + Clear environment filter
@endif diff --git a/apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php b/apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php index 215efdd6..038a37b3 100644 --- a/apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php +++ b/apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php @@ -8,7 +8,7 @@
- Review pending requests, expiring governance, and lapsed exception coverage across entitled tenants without leaving the Monitoring area. + Review pending requests, expiring governance, and lapsed exception coverage across entitled environments without leaving the Monitoring area.
@@ -48,7 +48,7 @@ Related drilldown
- Open tenant detail and Open finding stay available for context, but they no longer share the same semantic lane as the review decision. + Open environment detail and Open finding stay available for context, but they no longer share the same semantic lane as the review decision.
@@ -57,7 +57,7 @@ @else - Inspect an exception to enter the focused review lane. Scope, filters, and tenant drilldowns stay secondary until one request is actively under review. + Inspect an exception to enter the focused review lane. Scope, filters, and environment drilldowns stay secondary until one request is actively under review.
diff --git a/apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php b/apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php index 482e532f..d2a682b7 100644 --- a/apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php +++ b/apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php @@ -103,7 +103,7 @@
- Review what’s missing for this tenant and copy the missing permissions for admin consent. + Review what’s missing for this environment and copy the missing permissions for admin consent.
Stored-data view only. Last refreshed: {{ $lastRefreshedLabel }}{{ $isStale ? ' (stale)' : '' }}. @@ -140,7 +140,7 @@
No data available
- No stored verification data is available for this tenant. + No stored verification data is available for this environment. Start verification.
@@ -512,7 +512,7 @@ class="group rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-800
@if (! $tenant)
- No tenant selected. + No environment selected.
@else
diff --git a/apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php b/apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php index 7514001e..08aa96e7 100644 --- a/apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php +++ b/apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php @@ -49,7 +49,7 @@ $page ->assertNoJavaScriptErrors() ->waitForText('Requested: Auto mode. Resolved: Dense mode.') - ->assertSee('Dense multi-tenant scan') + ->assertSee('Dense multi-environment scan') ->assertSee('Applied filters and the focused subject are carried by the URL so the current matrix scan can be reopened or shared.') ->assertSee('Grouped legend') ->assertSee('Open finding') @@ -146,7 +146,7 @@ ->assertSee('Passive auto-refresh every 5 seconds') ->assertSee('Applied filters and the focused subject are carried by the URL so the current matrix scan can be reopened or shared.') ->click('Reset filters') - ->waitForText('Dense multi-tenant scan') + ->waitForText('Dense multi-environment scan') ->assertSee('Requested: Dense mode. Resolved: Dense mode.') ->assertNoJavaScriptErrors(); }); diff --git a/apps/platform/tests/Feature/Baselines/BaselineCompareStatsTest.php b/apps/platform/tests/Feature/Baselines/BaselineCompareStatsTest.php index 95177803..1ca359ba 100644 --- a/apps/platform/tests/Feature/Baselines/BaselineCompareStatsTest.php +++ b/apps/platform/tests/Feature/Baselines/BaselineCompareStatsTest.php @@ -10,11 +10,11 @@ use App\Models\OperationRun; use App\Support\Baselines\BaselineCompareStats; -it('returns no_tenant state when tenant is null', function (): void { +it('returns no_tenant state when environment is null', function (): void { $stats = BaselineCompareStats::forTenant(null); expect($stats->state)->toBe('no_tenant') - ->and($stats->message)->toContain('No tenant'); + ->and($stats->message)->toContain('No environment'); }); it('returns no_assignment state when tenant has no baseline assignment', function (): void { diff --git a/apps/platform/tests/Feature/Baselines/BaselineProfileWorkspaceOwnershipTest.php b/apps/platform/tests/Feature/Baselines/BaselineProfileWorkspaceOwnershipTest.php index a88ad50c..a5fdd4c7 100644 --- a/apps/platform/tests/Feature/Baselines/BaselineProfileWorkspaceOwnershipTest.php +++ b/apps/platform/tests/Feature/Baselines/BaselineProfileWorkspaceOwnershipTest.php @@ -6,12 +6,19 @@ use App\Models\BaselineProfile; use App\Support\Baselines\BaselineProfileStatus; use Filament\Facades\Filament; +use Illuminate\Http\Request; -it('keeps baseline profiles workspace-owned while retired tenant navigation URLs stay unavailable', function (): void { +it('keeps baseline profiles workspace-owned while environment navigation is route scoped', function (): void { Filament::setCurrentPanel('admin'); [$user, $tenant] = createUserWithTenant(role: 'owner'); + app()->instance('request', Request::create('/admin/workspaces/'.$tenant->workspace_id)); + + expect(BaselineProfileResource::shouldRegisterNavigation())->toBeFalse(); + + app()->instance('request', Request::create('/admin/workspaces/'.$tenant->workspace_id.'/environments/'.$tenant->slug)); + expect(BaselineProfileResource::shouldRegisterNavigation())->toBeTrue(); $this->actingAs($user) diff --git a/apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php b/apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php index 7edbe297..44da200d 100644 --- a/apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php +++ b/apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php @@ -56,8 +56,8 @@ ->assertDontSee('Passive auto-refresh every 5 seconds') ->assertSee('Grouped legend') ->assertSee('Apply filters') - ->assertSee('Compact unlocks at one visible tenant') - ->assertSee('Dense multi-tenant scan') + ->assertSee('Compact unlocks at one visible environment') + ->assertSee('Dense multi-environment scan') ->assertSee('Open finding') ->assertSee('More follow-up') ->assertSee('data-testid="baseline-compare-matrix-dense-shell"', false) @@ -248,10 +248,10 @@ ->test(BaselineCompareMatrix::class, ['record' => $fixture['profile']->getKey()]) ->assertActionVisible('compareAssignedTenants') ->assertActionDisabled('compareAssignedTenants') - ->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getTooltip() === 'The selected governed subjects span multiple compare strategy families and must be narrowed before comparing assigned tenants.'); + ->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getTooltip() === 'The selected governed subjects span multiple compare strategy families and must be narrowed before comparing assigned environments.'); }); -it('renders an empty state when the baseline profile has no assigned tenants', function (): void { +it('renders an empty state when the baseline profile has no assigned environments', function (): void { $fixture = $this->makeBaselineCompareMatrixFixture(); $fixture['profile']->tenantAssignments()->delete(); @@ -261,7 +261,7 @@ $this->withSession($session) ->get(BaselineProfileResource::compareMatrixUrl($fixture['profile'])) ->assertOk() - ->assertSee('No assigned tenants'); + ->assertSee('No assigned environments'); }); it('renders an empty state when the assigned set is not visible to the current actor', function (): void { @@ -280,7 +280,7 @@ $this->withSession($session) ->get(BaselineProfileResource::compareMatrixUrl($fixture['profile'])) ->assertOk() - ->assertSee('No visible assigned tenants'); + ->assertSee('No visible assigned environments'); }); it('renders a passive auto-refresh cue instead of a perpetual blocking state while compare runs remain active', function (): void { diff --git a/apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php b/apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php index 4bb2e89a..0949e280 100644 --- a/apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php +++ b/apps/platform/tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php @@ -181,7 +181,7 @@ function seedComparableBaselineProfileForTenant(ManagedEnvironment $tenant, Base $component = Livewire::actingAs($user) ->test(ViewBaselineProfile::class, ['record' => $profile->getKey()]) - ->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getLabel() === 'Compare assigned tenants' + ->assertActionExists('compareAssignedTenants', fn (Action $action): bool => $action->getLabel() === 'Compare assigned environments' && $action->isConfirmationRequired() && str_contains((string) $action->getModalDescription(), 'Simulation only.')); diff --git a/apps/platform/tests/Feature/Filament/BaselineProfileListFiltersTest.php b/apps/platform/tests/Feature/Filament/BaselineProfileListFiltersTest.php index 2f363020..d9a2fc93 100644 --- a/apps/platform/tests/Feature/Filament/BaselineProfileListFiltersTest.php +++ b/apps/platform/tests/Feature/Filament/BaselineProfileListFiltersTest.php @@ -94,7 +94,7 @@ Livewire::actingAs($user) ->test(ListBaselineProfiles::class) ->assertTableColumnExists('tenant_assignments_count', function (TextColumn $column): bool { - return $column->getLabel() === 'Assigned tenants' + return $column->getLabel() === 'Assigned environments' && (int) $column->getState() === 2; }, $assignedProfile) ->assertTableColumnExists('tenant_assignments_count', function (TextColumn $column): bool { diff --git a/apps/platform/tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php b/apps/platform/tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php index fb9254d3..d802a667 100644 --- a/apps/platform/tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php +++ b/apps/platform/tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php @@ -42,7 +42,7 @@ ->assertSee('Snapshot #'.$latestAttempt->getKey().' (Incomplete)') ->assertSee('Compare readiness') ->assertSee('No eligible compare target') - ->assertSee('Assign this baseline to a tenant you can compare, or use an account with access to an assigned tenant.'); + ->assertSee('Assign this baseline to an environment you can compare, or use an account with access to an assigned environment.'); }); it('shows compare readiness as ready when a consumable snapshot and eligible target tenant exist', function (): void { diff --git a/apps/platform/tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactAdminPanelRegistrationTest.php b/apps/platform/tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactAdminPanelRegistrationTest.php index 688447ba..1dcc6154 100644 --- a/apps/platform/tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactAdminPanelRegistrationTest.php +++ b/apps/platform/tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactAdminPanelRegistrationTest.php @@ -21,9 +21,11 @@ use App\Models\WorkspaceMembership; use App\Support\Workspaces\WorkspaceContext; use Filament\Facades\Filament; +use Illuminate\Http\Request; it('registers governance resource navigation on the admin panel', function (): void { Filament::setCurrentPanel('admin'); + app()->instance('request', Request::create('/admin/workspaces/spec-282-workspace/environments/spec-282-production')); expect(FindingResource::shouldRegisterNavigation())->toBeTrue() ->and(FindingExceptionResource::shouldRegisterNavigation())->toBeTrue() @@ -171,4 +173,4 @@ expect($resourceClass::hasPage('view') || $resourceClass::hasPage('edit')) ->toBeTrue($resourceClass.' must keep a truthful global-search destination on the admin panel.'); } -}); \ No newline at end of file +}); diff --git a/apps/platform/tests/Feature/Filament/Localization/CoreGovernanceSurfaceLocalizationTest.php b/apps/platform/tests/Feature/Filament/Localization/CoreGovernanceSurfaceLocalizationTest.php index 5526d811..a5abfc72 100644 --- a/apps/platform/tests/Feature/Filament/Localization/CoreGovernanceSurfaceLocalizationTest.php +++ b/apps/platform/tests/Feature/Filament/Localization/CoreGovernanceSurfaceLocalizationTest.php @@ -8,7 +8,7 @@ it('resolves first-wave governance labels from the active locale', function (): void { App::setLocale('de'); - expect(__('localization.dashboard.tenant_title'))->toBe('Tenant-Dashboard') + expect(__('localization.dashboard.tenant_title'))->toBe('Umgebungs-Dashboard') ->and(FindingResource::getNavigationGroup())->toBe('Governance') ->and(__('localization.findings.needs_action'))->toBe('Handlungsbedarf') ->and(__('baseline-compare.stat_total_findings'))->toBe('Findings gesamt') diff --git a/apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php b/apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php index f1554f42..91003772 100644 --- a/apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php +++ b/apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php @@ -14,9 +14,11 @@ use App\Filament\Resources\PolicyResource; use App\Filament\Resources\PolicyVersionResource; use App\Filament\Resources\RestoreRunResource; +use App\Support\ManagedEnvironmentLinks; use App\Support\Workspaces\WorkspaceContext; use Filament\Facades\Filament; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Http\Request; uses(RefreshDatabase::class); @@ -30,7 +32,7 @@ EntraGroupResource::class, ]); -dataset('admin visible navigation classes', [ +dataset('environment visible navigation classes', [ InventoryItemResource::class, PolicyResource::class, PolicyVersionResource::class, @@ -40,17 +42,30 @@ FindingResource::class, ]); +function bindNavigationRequestPath(string $path): void +{ + app()->instance('request', Request::create($path)); +} + it('keeps admin-hidden tenant surfaces out of navigation registration', function (string $class): void { Filament::setCurrentPanel('admin'); expect($class::shouldRegisterNavigation())->toBeFalse(); })->with('admin hidden navigation classes'); -it('registers current tenant-owned surfaces on the admin navigation', function (string $class): void { +it('hides environment-owned navigation classes on workspace surfaces', function (string $class): void { Filament::setCurrentPanel('admin'); + bindNavigationRequestPath('/admin/workspaces/workspace-alpha'); + + expect($class::shouldRegisterNavigation())->toBeFalse(); +})->with('environment visible navigation classes'); + +it('registers environment-owned surfaces only on environment surfaces', function (string $class): void { + Filament::setCurrentPanel('admin'); + bindNavigationRequestPath('/admin/workspaces/workspace-alpha/environments/environment-alpha'); expect($class::shouldRegisterNavigation())->toBeTrue(); -})->with('admin visible navigation classes'); +})->with('environment visible navigation classes'); it('keeps retired tenant-panel entry routes unavailable', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); @@ -61,12 +76,25 @@ ]) ->get("/admin/t/{$tenant->external_id}") ->assertNotFound(); + + $this->actingAs($user) + ->withSession([ + WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, + ]) + ->get("/admin/tenants/{$tenant->external_id}") + ->assertNotFound(); }); -it('keeps baseline navigation on the admin panel and off retired tenant routes', function (): void { +it('keeps baseline navigation route scoped and off retired tenant routes', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); Filament::setCurrentPanel('admin'); + bindNavigationRequestPath('/admin/workspaces/workspace-alpha'); + + expect(BaselineProfileResource::shouldRegisterNavigation())->toBeFalse(); + expect(BaselineSnapshotResource::shouldRegisterNavigation())->toBeFalse(); + + bindNavigationRequestPath('/admin/workspaces/workspace-alpha/environments/environment-alpha'); expect(BaselineProfileResource::shouldRegisterNavigation())->toBeTrue(); expect(BaselineSnapshotResource::shouldRegisterNavigation())->toBeTrue(); @@ -89,6 +117,8 @@ it('keeps the workspace panel sidebar free of tenant-sensitive entries even with a remembered tenant', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); + Filament::setTenant($tenant, true); + $response = $this->actingAs($user) ->withSession([ WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, @@ -99,6 +129,16 @@ ->get(route('admin.workspace.home', ['workspace' => $tenant->workspace_id])) ->assertOk(); + $response->assertSeeText('Overview'); + $response->assertSeeText('Operations'); + $response->assertSeeText('Alerts'); + $response->assertSeeText('Audit Log'); + $response->assertSeeText('Governance inbox'); + $response->assertSeeText('Customer reviews'); + $response->assertSeeText('Manage workspaces'); + $response->assertSeeText('Integrations'); + $response->assertSeeText('Settings'); + $response->assertDontSee('>Items', false); $response->assertDontSee('>Policies', false); $response->assertDontSee('>Policy Versions', false); @@ -106,4 +146,32 @@ $response->assertDontSee('>Backup Sets', false); $response->assertDontSee('>Restore Runs', false); $response->assertDontSee('>Findings', false); + $response->assertDontSee('>Baselines', false); + $response->assertDontSee('>Baseline Snapshots', false); + $response->assertDontSee('>Baseline Compare', false); + $response->assertDontSee('>Evidence', false); + $response->assertDontSee('>Risk exceptions', false); +}); + +it('shows environment-owned sidebar entries on the canonical environment route', function (): void { + [$user, $tenant] = createUserWithTenant(role: 'owner'); + + $response = $this->actingAs($user) + ->withSession([ + WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, + ]) + ->get(ManagedEnvironmentLinks::viewUrl($tenant)) + ->assertOk(); + + $response->assertSeeText('Policies'); + $response->assertSeeText('Policy Versions'); + $response->assertSeeText('Backup Schedules'); + $response->assertSeeText('Backup Sets'); + $response->assertSeeText('Restore Runs'); + $response->assertSeeText('Findings'); + $response->assertSeeText('Baselines'); + $response->assertSeeText('Baseline Snapshots'); + $response->assertSeeText('Baseline Compare'); + $response->assertSeeText('Evidence'); + $response->assertSeeText('Risk exceptions'); }); diff --git a/apps/platform/tests/Feature/Filament/WorkspaceOverviewContentTest.php b/apps/platform/tests/Feature/Filament/WorkspaceOverviewContentTest.php index bb3e539a..1998bfbe 100644 --- a/apps/platform/tests/Feature/Filament/WorkspaceOverviewContentTest.php +++ b/apps/platform/tests/Feature/Filament/WorkspaceOverviewContentTest.php @@ -45,8 +45,8 @@ ->assertSee('Open alerts') ->assertSee('Review current and recent workspace-wide operations.') ->assertSee('Activity only. Active execution does not imply governance health.') - ->assertSee('Visible tenants with non-healthy backup posture.') - ->assertSee('Visible tenants with weakened or unvalidated recovery evidence.') + ->assertSee('Visible environments with non-healthy backup posture.') + ->assertSee('Visible environments with weakened or unvalidated recovery evidence.') ->assertSee('Governance risk counts affected tenants') ->assertSee('Backup health stays separate from recovery evidence') ->assertSee('Calm wording stays bounded to visible tenants and checked domains') diff --git a/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php b/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php index 742b3733..e5eb20ff 100644 --- a/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php +++ b/apps/platform/tests/Feature/Findings/FindingsAssignmentHygieneReportTest.php @@ -381,7 +381,7 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu $component = findingsHygienePage($user) ->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey()) ->assertCanNotSeeTableRecords([$tenantAIssue]) - ->assertSee('No hygiene issues match this tenant scope') + ->assertSee('No hygiene issues match this environment scope') ->assertActionVisible('clear_tenant_filter'); $component->callAction('clear_tenant_filter') diff --git a/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php b/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php index d1eaaf04..91b7c470 100644 --- a/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php +++ b/apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php @@ -336,7 +336,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []): findingsIntakePage($user, [ 'tenant' => (string) $tenantA->external_id, ]) - ->assertSee('No intake findings match this tenant scope') + ->assertSee('No intake findings match this environment scope') ->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']); Finding::query()->delete(); diff --git a/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php b/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php index bbd0b8aa..0a878ff6 100644 --- a/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php +++ b/apps/platform/tests/Feature/Findings/MyWorkInboxTest.php @@ -122,7 +122,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, ], [ 'key' => 'tenant', - 'label' => 'ManagedEnvironment', + 'label' => 'Managed environment', 'fixed' => false, 'options' => [ ['value' => (string) $tenantA->getKey(), 'label' => 'Alpha ManagedEnvironment'], @@ -150,7 +150,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, ]); }); -it('defaults to the active tenant prefilter and lets the operator clear it without dropping personal scope', function (): void { +it('defaults to the active environment prefilter and lets the operator clear it without dropping personal scope', function (): void { [$user, $tenantA] = myWorkInboxActingUser(); $tenantB = ManagedEnvironment::factory()->create([ @@ -268,7 +268,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, ->assertCanNotSeeTableRecords([$ordinary]); }); -it('renders the tenant-prefilter empty-state branch and offers only a clear-filter recovery action', function (): void { +it('renders the environment-prefilter empty-state branch and offers only a clear-filter recovery action', function (): void { [$user, $tenantA] = myWorkInboxActingUser(); $tenantB = ManagedEnvironment::factory()->create([ @@ -286,7 +286,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, 'tenant' => (string) $tenantA->external_id, ]) ->assertCanNotSeeTableRecords([]) - ->assertSee('No assigned findings match this tenant scope') + ->assertSee('No assigned findings match this environment scope') ->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']); expect($component->instance()->summaryCounts())->toBe([ @@ -295,7 +295,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, ]); }); -it('renders the calm zero-work branch and points back to tenant selection when no active tenant context exists', function (): void { +it('renders the calm zero-work branch and points back to environment selection when no active environment context exists', function (): void { [$user, $tenant] = myWorkInboxActingUser(); session()->forget(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY); @@ -306,7 +306,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, ->assertTableEmptyStateActionsExistInOrder(['choose_tenant_empty']); }); -it('uses the active visible tenant for the calm empty-state drillback when tenant context exists', function (): void { +it('uses the active visible environment for the calm empty-state drillback when environment context exists', function (): void { [$user, $tenant] = myWorkInboxActingUser(); session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [ @@ -319,13 +319,13 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, expect($component->instance()->emptyState())->toMatchArray([ 'action_name' => 'open_tenant_findings_empty', - 'action_label' => 'Open tenant findings', + 'action_label' => 'Open environment findings', 'action_kind' => 'url', 'action_url' => FindingResource::getUrl('index', panel: 'admin', tenant: $tenant), ]); }); -it('builds tenant detail drilldowns with inbox continuity', function (): void { +it('builds environment detail drilldowns with inbox continuity', function (): void { [$user, $tenant] = myWorkInboxActingUser(); $finding = makeAssignedFindingForInbox($tenant, $user, [ diff --git a/apps/platform/tests/Feature/Governance/DecisionRegisterPageTest.php b/apps/platform/tests/Feature/Governance/DecisionRegisterPageTest.php index 65f61c51..c6a0e8a8 100644 --- a/apps/platform/tests/Feature/Governance/DecisionRegisterPageTest.php +++ b/apps/platform/tests/Feature/Governance/DecisionRegisterPageTest.php @@ -120,8 +120,8 @@ ->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin').'?managed_environment_id='.(string) $alphaTenant->getKey()) ->assertOk() - ->assertSee('This tenant filter is hiding other visible decision follow-through') - ->assertSee('Clear tenant filter'); + ->assertSee('This environment filter is hiding other visible decision follow-through') + ->assertSee('Clear environment filter'); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id]) @@ -175,4 +175,4 @@ function decisionRegisterPageException( $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save(); return $exception->fresh(['currentDecision']); -} \ No newline at end of file +} diff --git a/apps/platform/tests/Feature/Governance/GovernanceInboxPageTest.php b/apps/platform/tests/Feature/Governance/GovernanceInboxPageTest.php index 1d294905..b57d8d43 100644 --- a/apps/platform/tests/Feature/Governance/GovernanceInboxPageTest.php +++ b/apps/platform/tests/Feature/Governance/GovernanceInboxPageTest.php @@ -155,15 +155,15 @@ ->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id]) ->get(GovernanceInbox::getUrl(panel: 'admin').'?managed_environment_id='.(string) $alphaTenant->getKey()) ->assertOk() - ->assertSee('This tenant filter is hiding other visible attention') - ->assertSee('Clear tenant filter'); + ->assertSee('This environment filter is hiding other visible attention') + ->assertSee('Clear environment filter'); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id]) ->get(GovernanceInbox::getUrl(panel: 'admin').'?managed_environment_id='.(string) $alphaTenant->getKey().'&family=alert_delivery_failures') ->assertOk() ->assertSee('Alert delivery failures') - ->assertSee('No failed alert deliveries match this tenant filter right now.') + ->assertSee('No failed alert deliveries match this environment filter right now.') ->assertDontSee('Open my findings'); }); diff --git a/apps/platform/tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php b/apps/platform/tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php index b9e1c0df..d29d677f 100644 --- a/apps/platform/tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php +++ b/apps/platform/tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php @@ -46,20 +46,83 @@ 'all entitled tenants', 'tenant-specific context', ], + 'apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php' => [ + 'View tenant register', + 'Open tenant detail', + ], + 'apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php' => [ + 'Clear tenant filter', + 'No assigned findings match this tenant scope', + 'Open tenant findings', + 'Choose a tenant', + ], + 'apps/platform/app/Filament/Resources/BackupScheduleResource.php' => [ + 'No tenant selected', + ], + 'apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php' => [ + 'Remove tenant assignment', + 'No tenants assigned', + ], 'apps/platform/resources/views/filament/pages/baseline-compare-landing.blade.php' => [ 'tenant landing', ], + 'apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php' => [ + 'Open tenant compare', + 'Visible tenants', + 'Assigned tenants', + 'Compact unlocks at one visible tenant', + 'Dense multi-tenant scan', + 'No assigned tenants', + 'No visible assigned tenants', + ], + 'apps/platform/app/Filament/Pages/BaselineCompareMatrix.php' => [ + 'Compare assigned tenants', + 'tenant-owned baseline compare', + 'comparing assigned tenants', + 'No visible assigned tenants', + 'visible tenant before compare', + ], + 'apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php' => [ + 'Compare assigned tenants', + 'tenant-owned baseline compare', + 'comparing assigned tenants', + 'No visible assigned tenants', + ], + 'apps/platform/app/Support/ReasonTranslation/ReasonTranslator.php' => [ + 'assigned tenant with compare', + 'access to an assigned tenant', + ], + 'apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php' => [ + 'Open tenant detail', + 'tenant drilldowns', + ], + 'apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php' => [ + 'No tenant selected', + ], 'apps/platform/lang/en/localization.php' => [ 'build this tenant\'s policy inventory', + 'Tenant scope', + 'Select tenant', + 'No tenant selected', + 'No active tenants', + 'Tenant dashboard', + 'tenant blocker', ], 'apps/platform/app/Support/Baselines/BaselineCompareSummaryAssessor.php' => [ 'This tenant does not have an assigned baseline yet.', ], 'apps/platform/app/Support/Baselines/BaselineCompareStats.php' => [ 'This tenant has no baseline assignment. A workspace manager can assign a baseline profile to this tenant.', + 'No tenant selected.', ], 'apps/platform/app/Services/Tenants/TenantActionPolicySurface.php' => [ 'Add tenant', + 'Restore tenant', + 'Archive tenant', + ], + 'apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php' => [ + 'Restore tenant', + 'Archive tenant', ], ]; diff --git a/apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php b/apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php index f08b5433..b3ae40f4 100644 --- a/apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php +++ b/apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php @@ -76,7 +76,7 @@ expect($hits)->toBeEmpty("Retired management path or tenant-panel route emission detected on a Spec 288 seam:\n".implode("\n", $hits)); }); -it('keeps spec 288 proof seams free of retired tenant-panel helper bootstrapping', function (): void { +it('keeps spec 288 proof seams free of reintroduced retired tenant-panel helpers', function (): void { $root = base_path(); $forbiddenPatternsByFile = [ @@ -136,5 +136,5 @@ } } - expect($hits)->toBeEmpty("Retired tenant-panel helper or bootstrapping detected on a Spec 288 proof seam:\n".implode("\n", $hits)); -}); \ No newline at end of file + expect($hits)->toBeEmpty("Forbidden retired tenant-panel helper or bootstrapping detected on a Spec 288 proof seam; setTenantPanelContext() must not be reintroduced as a current helper:\n".implode("\n", $hits)); +}); diff --git a/apps/platform/tests/Feature/Localization/AuthAndSystemSurfaceLocalizationTest.php b/apps/platform/tests/Feature/Localization/AuthAndSystemSurfaceLocalizationTest.php index ee460ad9..86c9e213 100644 --- a/apps/platform/tests/Feature/Localization/AuthAndSystemSurfaceLocalizationTest.php +++ b/apps/platform/tests/Feature/Localization/AuthAndSystemSurfaceLocalizationTest.php @@ -10,7 +10,7 @@ ->get('/admin/login') ->assertSuccessful() ->assertSee('Mit Microsoft anmelden') - ->assertSee('ManagedEnvironment-Admin-Zugriff erfordert eine ManagedEnvironment-Mitgliedschaft'); + ->assertSee('Admin-Zugriff erfordert eine Umgebungsmitgliedschaft'); }); it('keeps system plane resolution independent from user and workspace preferences', function (): void { diff --git a/apps/platform/tests/Unit/Support/GovernanceInbox/GovernanceInboxSectionBuilderTest.php b/apps/platform/tests/Unit/Support/GovernanceInbox/GovernanceInboxSectionBuilderTest.php index 0be0f8cc..af4f19df 100644 --- a/apps/platform/tests/Unit/Support/GovernanceInbox/GovernanceInboxSectionBuilderTest.php +++ b/apps/platform/tests/Unit/Support/GovernanceInbox/GovernanceInboxSectionBuilderTest.php @@ -224,7 +224,7 @@ expect($payload['sections'])->toHaveCount(1) ->and($payload['sections'][0]['key'])->toBe('alert_delivery_failures') ->and($payload['sections'][0]['count'])->toBe(0) - ->and($payload['sections'][0]['empty_state'])->toContain('tenant filter'); + ->and($payload['sections'][0]['empty_state'])->toContain('environment filter'); }); it('omits finding exceptions when the exception family is hidden or tenant scope is inaccessible', function (): void { diff --git a/specs/298-managed-environment-terminology-copy-cleanup/checklists/requirements.md b/specs/298-managed-environment-terminology-copy-cleanup/checklists/requirements.md new file mode 100644 index 00000000..4a8c9224 --- /dev/null +++ b/specs/298-managed-environment-terminology-copy-cleanup/checklists/requirements.md @@ -0,0 +1,68 @@ +# Requirements Checklist: Managed Environment Terminology & Copy Cleanup + +**Purpose**: Validate that Spec 298 preparation is bounded, implementation-ready, and constitution-aligned. +**Created**: 2026-05-13 +**Feature**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/spec.md) + +## Applicability And Low-Impact Gate + +- [x] CHK001 The change explicitly affects operator-facing copy, localization values, tests, guards, and possible browser smoke anchors. +- [x] CHK002 The spec, plan, and tasks carry the same native/custom classification, shared-family relevance, state-layer ownership, and exception policy. + +## Native, Shared-Family, And State Ownership + +- [x] CHK003 The scope keeps existing Laravel/Filament/Blade surfaces and does not introduce a new custom UI system. +- [x] CHK004 No shared-detail or shared-family surface is replaced; labels and selectors are updated in place. +- [x] CHK005 Copy/value, page/action label, view text, test helper wording, guard regex descriptions, and browser selectors are named as separate state layers. +- [x] CHK006 Existing action hierarchy and inspect/open models remain unchanged unless an affected label needs a noun update. + +## Shared Pattern Reuse + +- [x] CHK007 The cross-cutting interaction classes are explicitly marked: localization, shell/context copy, action labels, guard tests, and browser anchors. +- [x] CHK008 The existing shared paths are named: localization arrays, `ManagedEnvironmentLinks`, `setAdminEnvironmentContext()`, and current guard/browser patterns. +- [x] CHK009 No parallel operator-facing vocabulary framework is introduced. + +## OperationRun Start UX Contract + +- [x] CHK010 The spec states that no new OperationRun start/completion/link behavior is introduced. +- [x] CHK011 Existing OperationRun UX/link contracts remain the owner if copy around operations is touched. +- [x] CHK012 No queued DB notification or terminal notification behavior changes are planned. + +## Provider Boundary And Vocabulary + +- [x] CHK013 Provider-owned terms such as Microsoft tenant ID and Entra tenant ID are allowed only when the external provider is the subject. +- [x] CHK014 Generic platform/operator vocabulary is required to use workspace, managed environment, environment, provider connection, operation, finding, review, evidence, and governance terms. + +## Signals, Exceptions, And Test Depth + +- [x] CHK015 Repository signals are classified as review-mandatory: final scans, guard literals, browser selector changes, localization value decisions, and destructive action label changes. +- [x] CHK016 Remaining tenant references must be documented in `terminology-audit.md` with explicit categories. +- [x] CHK017 Required surface test profiles are explicit: `standard-native-filament`, `global-context-shell`, and targeted `browser-smoke` only when touched. +- [x] CHK018 The chosen test lanes are focused and do not require a full-suite repair loop. + +## Audience-Aware Disclosure And Decision Hierarchy + +- [x] CHK019 Default-visible context copy must be environment-first while provider/raw/support details remain in existing disclosure tiers. +- [x] CHK020 Customer/read-only paths do not gain raw JSON, copied context payloads, fingerprints, or internal reason ownership. +- [x] CHK021 The spec does not add new primary actions; it only relabels existing actions where needed. +- [x] CHK022 Duplicate or contradictory tenant/environment wording is treated as drift to fix or document. + +## Filament v5 Contract + +- [x] CHK023 Filament v5 / Livewire v4.0+ compliance is explicit. +- [x] CHK024 Laravel 12 panel provider registration remains in `apps/platform/bootstrap/providers.php`. +- [x] CHK025 Globally searchable resource handling is called out for any touched resource. +- [x] CHK026 Destructive actions that are relabeled must retain `->action(...)`, `->requiresConfirmation()`, and authorization. +- [x] CHK027 Asset strategy is unchanged unless implementation unexpectedly registers assets, in which case `filament:assets` is required in deployment notes. +- [x] CHK028 Testing plan names affected pages/actions/guards/browser anchors. + +## Review Outcome + +- [x] CHK029 Review outcome class: `acceptable-special-case`. +- [x] CHK030 Workflow outcome: `keep`. +- [x] CHK031 Final note location: active feature PR close-out entry `Guardrail / Terminology Cleanup / Smoke Coverage`. + +## Notes + +- This is a cleanup spec, not a broad rename or localization foundation. +- Remaining provider/internal/historical tenant references are allowed only when categorized in the audit. diff --git a/specs/298-managed-environment-terminology-copy-cleanup/plan.md b/specs/298-managed-environment-terminology-copy-cleanup/plan.md new file mode 100644 index 00000000..a9755ce0 --- /dev/null +++ b/specs/298-managed-environment-terminology-copy-cleanup/plan.md @@ -0,0 +1,358 @@ +# Implementation Plan: Managed Environment Terminology & Copy Cleanup + +**Branch**: `298-managed-environment-terminology-copy-cleanup` | **Date**: 2026-05-13 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/spec.md) +**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/spec.md` + +## Summary + +Spec 298 is a bounded post-cutover terminology and navigation cleanup. The implementation will audit remaining tenant-first copy, update active visible UI/localization/test vocabulary to Workspace and Managed Environment / Environment terminology, clarify forbidden legacy guard literals, update affected browser-smoke selectors, gate environment-owned navigation by the current route/surface, and document allowed provider/internal/historical references. It must not rename the database/model layer or reintroduce legacy tenant routes, panels, helper aliases, or compatibility surfaces. + +This plan is preparation only. It does not implement application code. + +## Technical Context + +**Language/Version**: PHP 8.4.15 +**Primary Dependencies**: Laravel 12.52.0, Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, Laravel Sail 1.52.0 +**Storage**: PostgreSQL through Laravel/Sail for tests; no schema or persistence change planned +**Testing**: Pest via `./vendor/bin/sail artisan test --compact`; targeted Browser tests only if visible smoke anchors are touched +**Validation Lanes**: Feature/Guards, Feature/Localization, Feature/Workspaces, Feature/ProviderConnections, Feature/RequiredPermissions, Feature/Filament, affected Feature areas, targeted Browser smoke +**Target Platform**: Laravel Sail local runtime and Gitea-compatible CI runners +**Project Type**: Laravel web application under `apps/platform` +**Performance Goals**: Keep scans and tests focused; no full-suite repair and no browser timeout inflation +**Constraints**: no `/admin/t...` restoration, no `/admin/tenants...` restoration, no `TenantPanelProvider`, no `setTenantPanelContext()` alias, no DB/model rename, no new localization architecture, no broad UI polish +**Scale/Scope**: Copy, localization values, test wording, guard descriptions, browser selectors, route-scope-first sidebar gating, and spec-local audit only + +## Initial Repo Baseline + +Preparation scans on 2026-05-13 found: + +- Current branch before Spec Kit execution was `platform-dev`; Spec Kit switched to `298-managed-environment-terminology-copy-cleanup`. +- Working tree was clean before creating the package. +- No existing `specs/298-*` package or matching branch was present. +- Related specs: + - `specs/297-managed-environment-canonical-route-cutover/` is dependency/context and carries completed-task signals; do not rewrite it. + - `specs/286-ui-copy-ia-localization-neutralization/` is adjacent context and carries completed/review signals; do not rewrite it. + - `specs/288-quality-gates-no-legacy-enforcement/` is adjacent guard context and includes explicit legacy guard concepts; do not weaken it. +- Read-only source scan over `apps/platform/app`, `apps/platform/resources`, and `apps/platform/routes` returned no hits for active old route/generator patterns: + +```bash +rg "filament\.admin\.resources\.tenants|/admin/tenants|/admin/t/|TenantResource::getUrl|TenantDashboard::getUrl|TenantRequiredPermissions::getUrl|setTenantPanelContext|panel:\s*'tenant'|panel:\s*\"tenant\"" apps/platform/app apps/platform/resources apps/platform/routes --glob '!vendor' --glob '!node_modules' +``` + +- Read-only copy/test scan found representative hits in: + - `apps/platform/lang/en/localization.php` + - `apps/platform/lang/de/localization.php` + - `apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php` + - `apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php` + - `apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php` + - `apps/platform/app/Services/Tenants/TenantActionPolicySurface.php` + - `apps/platform/app/Support/Baselines/BaselineCompareStats.php` + - `apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php` + - `apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php` + - `apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php` + - `apps/platform/app/Filament/Resources/BackupScheduleResource.php` + - `apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php` + - guard/localization/findings tests +- `apps/platform/tests/Pest.php` already contains `setAdminEnvironmentContext()`. +- `apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php` still contains `setTenantPanelContext` regex literals as forbidden-pattern checks; implementation must make the guard wording unambiguously legacy-forbidden. + +The implementation must refresh `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md` with full required scans before application edits. + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: changed visible copy and validation wording on existing surfaces; no new product workflow. +- **Native vs custom classification summary**: Existing native Filament/Laravel/Blade surfaces; no custom UI redesign. +- **Shared-family relevance**: shell/context labels, dashboard copy, action labels, modal headings, empty states, guard tests, browser anchors, localization values. +- **State layers in scope**: copy/value, page/action label, view text, test helper wording, guard regex descriptions, browser selectors. +- **Audience modes in scope**: operator-MSP and support-platform. Customer-facing localization may be touched only where targeted strings are already active and in scope. +- **Decision/diagnostic/raw hierarchy plan**: Preserve existing hierarchy. Replace wrong tenant-first terms; do not move diagnostic/raw content. +- **Raw/support gating plan**: unchanged. +- **One-primary-action / duplicate-truth control**: Do not add new actions. If labels change, keep the existing action hierarchy and only update the object noun. +- **Handling modes by drift class or surface**: fix active UI/test terminology; document provider/internal/historical/regression-guard exceptions; defer DB/model rename and broad historical cleanup. +- **Repository-signal treatment**: review-mandatory for final scans, guard literals, browser selector changes, localization key/value decisions, and destructive action label changes. +- **Special surface test profiles**: `standard-native-filament`, `global-context-shell`, `browser-smoke` if browser files change. +- **Required tests or manual smoke**: focused Feature tests for touched areas; targeted Browser smokes when browser anchors change. +- **Exception path and spread control**: Every final tenant-related hit must be in `terminology-audit.md` with category and reason. +- **Active feature PR close-out entry**: Guardrail / Terminology Cleanup / Smoke Coverage. + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: yes. +- **Systems touched**: localization arrays, Filament page/resource labels, support copy emitters, Blade views, tests, browser smokes, and guard tests. +- **Shared abstractions reused**: existing localization keys where safe, `ManagedEnvironmentLinks`, `setAdminEnvironmentContext()`, existing data-testid/browser patterns, existing guard-test pattern. +- **New abstraction introduced? why?**: one small `NavigationScope` helper is allowed for route-scope-first sidebar gating. It centralizes the existing `TenantPageCategory` decision and avoids per-resource ad hoc checks against stale `Filament::getTenant()` state. +- **Why the existing abstraction was sufficient or insufficient**: Canonical route/helper truth already exists. The remaining issue is copy/test terminology, which can be fixed in place. +- **Bounded deviation / spread control**: Technical class/model/table names and provider-specific Microsoft/Entra tenant wording remain only with audit documentation. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no new start/completion/link behavior; possible label/copy updates only. +- **Central contract reused**: existing OperationRun UX/link contracts if touched. +- **Delegated UX behaviors**: unchanged. +- **Surface-owned behavior kept local**: copy only. +- **Queued DB-notification policy**: N/A. +- **Terminal notification path**: unchanged. +- **Exception path**: none. + +## Provider Boundary & Portability Fit + +- **Shared provider/platform boundary touched?**: yes, vocabulary only. +- **Provider-owned seams**: Microsoft tenant ID, Entra tenant ID, Microsoft Graph, Intune, provider permission names, provider payload metadata. +- **Platform-core seams**: Workspace, Managed Environment, Environment scope, Provider Connection, Operation, Finding, Review, Evidence, Governance. +- **Neutral platform terms / contracts preserved**: current UI and tests should prefer workspace/environment terms in generic product surfaces. +- **Retained provider-specific semantics and why**: Microsoft/Entra tenant terms remain when external identity or provider data is the subject. +- **Bounded extraction or follow-up path**: no provider framework. Follow-up only for structural DB/model rename or broad localization productization. + +## Constitution Check + +*GATE: Must pass before runtime implementation and re-check before close-out.* + +- Inventory-first: no inventory/snapshot truth change. +- Read/write separation: no new write workflow. Touched destructive labels such as restore/remove must keep existing confirmation, authorization, and audit behavior. +- Single Graph contract path: no Graph calls added. +- Deterministic capabilities: no capability resolver change. +- Proportionality / no premature abstraction: copy updates are in place. The route-scope navigation helper is intentionally small, derived from existing route categories, and carries no persisted state. +- No new persisted truth: no migrations, tables, columns, compatibility shims, or dual-read paths. +- Workspace isolation: copy/helper/test changes must not weaken workspace membership checks. +- Tenant/managed-environment isolation: existing environment entitlement checks remain intact. +- RBAC-UX: non-member/out-of-scope remains 404; established member missing capability remains 403. +- Provider boundary: generic platform copy moves away from tenant-first wording; provider tenant terms remain provider-owned. +- Test governance: targeted guard/feature/browser lanes only; no hidden broad suite repair. +- Filament-native UI: Filament remains v5 on Livewire v4; no v3/v4 APIs; no custom UI styling changes. +- Deployment/ops: no asset registration is planned. If assets are unexpectedly registered, deploy notes must include `cd apps/platform && php artisan filament:assets`. + +## Filament v5 Output Contract + +- **Livewire compliance**: Filament v5 targets Livewire v4.0+; current app has Livewire 4.1.4. +- **Provider registration location**: Laravel 12 provider registration remains in `apps/platform/bootstrap/providers.php`; this spec must not add or restore a panel provider. +- **Globally searchable resources**: No new searchable resource is planned. Any touched globally searchable resource must retain Edit/View pages or have global search disabled. +- **Destructive actions**: This spec may relabel existing destructive actions such as restore/remove. They must still execute via `->action(...)`, require `->requiresConfirmation()`, and enforce server-side authorization. +- **Asset strategy**: No new Filament assets are planned. If implementation unexpectedly registers assets, deployment must include `cd apps/platform && php artisan filament:assets`. +- **Testing plan**: Touched Filament pages/resources/actions are covered with Pest/Filament tests; guard tests cover forbidden legacy helper/route wording; browser smokes cover affected anchors. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: Feature guard tests for terminology and route/runtime regression; Feature/Localization and Feature/Filament tests for labels/copy; Browser only for affected smoke anchors. +- **Affected validation lanes**: Feature/Guards, Feature/Localization, Feature/Workspaces, Feature/ProviderConnections, Feature/RequiredPermissions, Feature/Filament, affected Feature directories, Browser smoke anchors. +- **Why this lane mix is the narrowest sufficient proof**: The change is copy/test terminology cleanup with route-regression protection, not a new workflow. +- **Narrowest proving command(s)**: + +```bash +cd apps/platform +./vendor/bin/sail artisan test --compact tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php +./vendor/bin/sail artisan test --compact tests/Feature/Guards +./vendor/bin/sail artisan test --compact tests/Feature/Localization +``` + +- **Fixture / helper / factory / seed / context cost risks**: Avoid widening `setAdminEnvironmentContext()` or adding provider/browser setup defaults. +- **Expensive defaults or shared helper growth introduced?**: none planned. +- **Heavy-family additions, promotions, or visibility changes**: none planned; targeted browser smokes only when affected. +- **Surface-class relief / special coverage rule**: Standard-native Filament relief unless a browser-visible context-shell anchor changes. +- **Closing validation and reviewer handoff**: run final scans, focused guards, affected feature lanes, browser anchors if touched, Pint dirty, and `git diff --check`. +- **Budget / baseline / trend follow-up**: none expected. +- **Review-stop questions**: Did old tenant-first wording remain in active UI? Did a guard literal become ambiguous? Did a browser smoke still click old copy? Did route scans remain clean? Did destructive action relabeling preserve confirmation/authorization? +- **Escalation path**: document-in-feature for allowed exceptions; follow-up-spec for structural rename/localization beyond scope. +- **Active feature PR close-out entry**: Guardrail / Terminology Cleanup / Smoke Coverage. +- **Why no dedicated follow-up spec is needed**: The current work is a bounded cleanup. Structural rename and broad localization remain explicit follow-ups. + +## Project Structure + +### Documentation (this feature) + +```text +specs/298-managed-environment-terminology-copy-cleanup/ +├── spec.md +├── plan.md +├── tasks.md +├── terminology-audit.md +└── checklists/ + └── requirements.md +``` + +### Source Code (repository root) + +Expected touched surfaces during implementation: + +```text +apps/platform/lang/ +├── en/localization.php +└── de/localization.php + +apps/platform/resources/views/ +├── filament/partials/context-bar.blade.php +├── filament/pages/** +└── livewire/** + +apps/platform/app/ +├── Filament/** +├── Support/** +└── Services/** + +apps/platform/tests/ +├── Pest.php +├── Feature/** +└── Browser/** +``` + +**Structure Decision**: Use existing Laravel/Filament app structure and existing localization/test conventions. Do not create a new base application folder or dependency. + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|---|---|---| +| Spec-local terminology audit | Remaining `Tenant` references need classification so the implementation does not remove provider/internal/historical truth blindly | Ad hoc final summary would not give reviewers a durable exception map | +| Cross-cutting copy/test updates | The old vocabulary appears across multiple existing layers | One local label change would leave contradictory product truth | +| Guard wording update | Legacy helper regex literals remain useful but ambiguous | Removing the regex would weaken Spec 288 protection | + +## Phase 0: Safety Gate + +1. Run: + +```bash +git status --short --branch +git diff --stat +git log -1 --oneline +``` + +2. Confirm the implementation branch is `298-managed-environment-terminology-copy-cleanup` or an isolated session branch derived from it. +3. Stop if unrelated uncommitted changes exist. +4. Read: + +```text +.specify/memory/constitution.md +specs/297-managed-environment-canonical-route-cutover/ +specs/288-quality-gates-no-legacy-enforcement/ +specs/286-ui-copy-ia-localization-neutralization/ +``` + +## Phase 1: Baseline Scan And Audit + +Refresh `terminology-audit.md` before application edits: + +```bash +git status --short --branch +git diff --stat + +cd apps/platform +./vendor/bin/sail artisan route:list | rg "admin/tenants|admin/t/" && exit 1 || true + +rg "filament\.admin\.resources\.tenants|/admin/tenants|/admin/t/|TenantResource::getUrl|TenantDashboard::getUrl|TenantRequiredPermissions::getUrl|setTenantPanelContext|panel:\s*'tenant'|panel:\s*\"tenant\"" app resources routes --glob '!vendor' --glob '!node_modules' + +rg "setTenantPanelContext|panel:\s*'tenant'|panel:\s*\"tenant\"" tests --glob '!vendor' --glob '!node_modules' + +rg "Tenant dashboard|Tenant detail|Open tenant|Select tenant|Tenant scope|No tenant selected|No active tenants|Remove tenant|Restore tenant|Tenant memberships|tenant blocker" app resources lang tests --glob '!vendor' --glob '!node_modules' +``` + +Classify findings as `fixed`, `allowed-provider-term`, `allowed-internal-model`, `allowed-historical`, `allowed-regression-guard`, `out-of-scope-db-model-rename`, or `needs-follow-up`. + +## Phase 2: Guard And Test Helper Cleanup + +- Keep `setAdminEnvironmentContext()` as the active helper if repo-real. +- Do not add `setTenantPanelContext()` alias. +- Update guard test descriptions and failure messages so remaining `setTenantPanelContext` regex literals clearly forbid a retired helper. +- Update active test names/comments that describe current runtime as tenant panel context. +- Run: + +```bash +cd apps/platform +./vendor/bin/sail artisan test --compact tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php +./vendor/bin/sail artisan test --compact tests/Feature/Guards +``` + +## Phase 3: Localization And Visible Copy Cleanup + +- Update EN/DE localization values first; rename keys only when usage is fully bounded. +- Replace targeted active UI phrases: + - `Tenant dashboard` -> `Environment dashboard` or `Managed environment dashboard` + - `Tenant detail` / `Open tenant detail` -> `Environment detail` / `Open environment detail` + - `Select tenant` -> `Select environment` + - `Tenant scope` -> `Environment scope` + - `No tenant selected` -> `No environment selected` + - `No active tenants` -> `No active environments` + - `Tenant memberships` -> `Environment access scopes` + - `Remove tenant` -> `Remove environment` + - `Restore tenant` -> `Restore environment` + - `tenant blocker` -> `environment blocker` +- Keep Microsoft/Entra tenant ID wording where provider-specific. +- Preserve existing Filament action hierarchy and destructive-action safeguards. +- Run focused tests for touched areas. + +## Phase 4: Blade, Filament, Support Copy, And Browser Smokes + +- Update context-bar, dashboard, monitoring, baseline compare, required-permissions, backup schedule, support diagnostics, governance action, and policy surface copy as discovered by the audit. +- Prefer stable `data-testid` selectors for browser smokes if old copy is not a stable product contract. +- Do not increase timeouts without a documented timing cause. +- Run affected browser smokes individually before broader browser anchors. + +## Phase 5: Final Scans And Proof Pack + +Run the final scans from Phase 1 again. Every remaining hit must be absent or documented in `terminology-audit.md`. + +## Phase 5A: Navigation Segregation Addendum + +The implementation must also close the observed post-cutover navigation leak: + +```bash +cd apps/platform +./vendor/bin/sail artisan route:list | rg "workspaces/.*/environments|admin/tenants|admin/t|operations|provider-connections|required-permissions" +rg "shouldRegisterNavigation|getNavigationGroup|getNavigationLabel|getNavigationSort|navigation" app/Filament app/Providers resources tests --glob '!vendor' --glob '!node_modules' +rg "Filament::getTenant|Filament::setTenant|WorkspaceContext|ManagedEnvironment|current.*tenant|tenant context|environment context|setAdminEnvironmentContext|SESSION_KEY" app tests --glob '!vendor' --glob '!node_modules' +``` + +Implementation direction: + +- Reuse `TenantPageCategory` through a small central helper that answers workspace surface vs environment surface. +- Current route/surface wins over remembered environment, session environment, query hints, or stale `Filament::getTenant()`. +- Keep workspace-owned navigation visible on workspace surfaces. +- Hide environment-owned Resource/Page navigation on workspace surfaces. +- Show environment-owned navigation again on canonical environment routes such as `/admin/workspaces/{workspace}/environments/{environment}` and child routes. +- Keep retired `/admin/t...`, `/admin/tenants...`, and `TenantPanelProvider` absent. + +Focused proof commands: + +```bash +cd apps/platform +./vendor/bin/sail artisan test --compact tests/Feature/Filament/PanelNavigationSegregationTest.php +./vendor/bin/sail artisan test --compact tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php tests/Feature/Guards/NoLegacyTenantPanelRuntimeTest.php +./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php +``` + +Run proof commands: + +```bash +cd apps/platform +./vendor/bin/sail artisan test --compact tests/Feature/Guards +./vendor/bin/sail artisan test --compact tests/Feature/Localization +./vendor/bin/sail artisan test --compact tests/Feature/Workspaces +./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections +./vendor/bin/sail artisan test --compact tests/Feature/RequiredPermissions +./vendor/bin/sail artisan test --compact tests/Feature/Filament +./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php +./vendor/bin/sail bin pint --dirty --format agent +``` + +Then run from the repo root: + +```bash +git diff --check +``` + +## Phase 6: Close-Out + +- Update `terminology-audit.md` with final status and allowed references. +- Record the Filament v5 output contract in the implementation summary. +- Final decision must be one of: + - `298 merge-ready; terminology cleanup complete` + - `298 merge-ready with documented allowed technical tenant references` + - `298 blocked by active legacy tenant copy` + - `298 blocked by runtime legacy regression` + +## Explicit Follow-Ups / Out of Scope + +- Database/model rename from `Tenant` to `ManagedEnvironment` +- Broad localization v1 or customer-facing localization adoption +- Historical spec/doc rewrite +- New customer review workspace +- Decision inbox or new governance workflow +- New RBAC or provider abstraction +- UI redesign diff --git a/specs/298-managed-environment-terminology-copy-cleanup/spec.md b/specs/298-managed-environment-terminology-copy-cleanup/spec.md new file mode 100644 index 00000000..6a97048d --- /dev/null +++ b/specs/298-managed-environment-terminology-copy-cleanup/spec.md @@ -0,0 +1,307 @@ +# Feature Specification: Managed Environment Terminology & Copy Cleanup + +**Feature Branch**: `298-managed-environment-terminology-copy-cleanup` +**Created**: 2026-05-13 +**Status**: Ready +**Input**: User-provided Spec 298 draft: clean up remaining visible and test-side tenant terminology after Spec 297 retired the active legacy tenant route/runtime layer. + +## Spec Candidate Check *(mandatory - SPEC-GATE-001)* + +- **Problem**: The route/runtime cutover from legacy tenant surfaces is complete enough to make old tenant-first language misleading, but active copy, localization values, tests, guard descriptions, browser-smoke selectors, and some sidebar navigation decisions still expose the retired TenantPanel mental model. +- **Today's failure**: Operators and contributors can still see or assert strings such as `Tenant dashboard`, `Tenant scope`, `Select tenant`, `No active tenants`, `Open tenant detail`, `Remove tenant`, `Restore tenant`, `Tenant memberships`, `tenant blocker`, or guard/helper language around `setTenantPanelContext()` even though the product is workspace-first and managed-environment-first. A stale selected environment can also make Workspace Overview show environment-owned navigation such as Policies, Policy Versions, Backup Sets, Restore Runs, Findings, Baselines, Evidence, or Risk exceptions. +- **User-visible improvement**: Current UI surfaces, localization strings, tests, smoke anchors, and sidebar navigation read as Workspace -> Managed Environment / Environment context, while provider-specific phrases such as Microsoft tenant ID remain allowed only where they describe external Microsoft/Entra truth. +- **Smallest enterprise-capable version**: Run and record a terminology/navigation audit, update active user-facing copy and localization values for the targeted phrases, clarify/rename ambiguous test helper and guard wording, update affected browser-smoke selectors or assertions, gate environment-owned navigation by route surface instead of stale session/Filament tenant state, keep active legacy route scans clean, and document allowed technical/provider/historical exceptions. +- **Explicit non-goals**: No database/table/model rename from `Tenant` to `ManagedEnvironment`, no migration rewrite, no new routing architecture, no new localization foundation, no UI redesign, no RBAC remodel, no old route or provider restoration, no broad historical spec rewrite, and no full-suite fix-all. +- **Permanent complexity imported**: One spec-local terminology audit artifact, one small route-scope navigation helper, plus targeted tests/guard updates. No new table, enum, status family, provider framework, broad route framework, or localization architecture is introduced. +- **Why now**: Spec 297 retired active legacy tenant route surfaces and centralized canonical managed-environment links. Leaving visible/test copy on the old vocabulary makes future specs and tests regress toward tenant-first product truth. +- **Why not local**: The drift crosses localization catalogs, Blade views, Filament labels/actions, support copy, tests, guard regex literals, and browser smokes. A single local copy fix would leave contradictory terminology in other active surfaces. +- **Approval class**: Cleanup +- **Red flags triggered**: Cross-cutting copy/test cleanup and terminology audit. Defense: the scope is explicitly bounded to existing strings, tests, and guard wording; it does not introduce a new vocabulary framework or rename persistence. +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12** +- **Decision**: approve + +## Spec Scope Fields *(mandatory)* + +- **Scope**: canonical-view +- **Primary Routes**: + - Canonical context: `/admin/workspaces/{workspace}/environments` + - Canonical context: `/admin/workspaces/{workspace}/environments/{environment}` + - Canonical environment support surfaces such as required permissions, diagnostics, access scopes, provider connections, operations, findings, reviews, evidence, and stored reports where repo-real + - Retired and forbidden as product truth: `/admin/t...` and `/admin/tenants...` +- **Data Ownership**: + - No persisted data ownership changes. + - Existing internal `Tenant` model/table/column names may remain technical implementation truth where DB/model rename is out of scope. + - Provider-specific Microsoft/Entra tenant identifiers remain provider-owned external truth, not TenantPilot platform vocabulary. +- **RBAC**: + - Workspace membership remains the primary authorization boundary. + - Managed-environment access scoping remains narrowing/access-scope behavior where repo-real. + - Non-member or out-of-scope access remains deny-as-not-found (404). + - Established member missing capability remains 403. + - UI visibility is not authorization; Gates/Policies remain server-side truth. + +For canonical-view specs: + +- **Default filter behavior when tenant-context is active**: Current environment context may prefilter workspace-wide pages, but copy must name that state as environment or managed-environment context, not tenant-panel context. +- **Explicit entitlement checks preventing cross-tenant leakage**: This spec must not weaken existing workspace/environment entitlement checks. Any changed links, labels, or smoke selectors must continue to use the canonical route/link helpers and existing authorization proof. + +## Cross-Cutting / Shared Pattern Reuse *(mandatory)* + +- **Cross-cutting feature?**: yes +- **Interaction class(es)**: localization values, Filament page/resource labels, action labels, modal headings, empty states, helper text, notification/modal copy, context-bar copy, Blade view copy, test names, test helper wording, guard regex descriptions, browser-smoke selectors. +- **Systems touched**: + - `apps/platform/lang/en/localization.php` + - `apps/platform/lang/de/localization.php` + - `apps/platform/resources/views/**` + - `apps/platform/app/Filament/**` + - `apps/platform/app/Support/**` + - `apps/platform/app/Services/**` where visible copy is emitted + - `apps/platform/tests/Pest.php` + - `apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php` + - affected `apps/platform/tests/Feature/**` and `apps/platform/tests/Browser/**` + - `specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md` +- **Existing pattern(s) to extend**: Spec 297 canonical managed-environment route/link contract, existing `ManagedEnvironmentLinks`, existing `setAdminEnvironmentContext()` helper, current localization arrays, existing `TenantPageCategory` route categorization, existing Spec 288 guard-test style, existing browser-smoke anchors. +- **Shared contract / presenter / builder / renderer to reuse**: Use existing localization keys where key rename is risky; update visible values first. Use `ManagedEnvironmentLinks`, `TenantPageCategory`, and existing `data-testid` patterns for browser-safe anchors when selectors are needed. +- **Why the existing shared path is sufficient or insufficient**: The canonical route/link path and route category enum already exist. What remains insufficient is visible/test vocabulary and sidebar navigation that still use tenant-first product language or stale selected-environment state for current workspace surfaces. +- **Allowed deviation and why**: Internal model/class names such as `TenantResource`, `TenantDashboard`, `TenantRequiredPermissions`, `tenant_id`, and Microsoft/Entra tenant ID copy may remain where they are internal, provider-specific, historical, or out of scope for DB/model rename. They must be documented in the audit if surfaced by final scans. +- **Consistency impact**: Current UI, tests, localization values, smoke anchors, and guard descriptions must converge on Workspace, Managed Environment, Environment, Provider Connection, Operation, Finding, Review, Evidence, and Governance vocabulary. +- **Review focus**: Reviewers must verify that no active product UI uses retired TenantPanel or tenant-first language, route/runtime legacy scans remain clean, and guard literals only remain when they explicitly forbid retired behavior. + +## OperationRun UX Impact *(mandatory)* + +- **Touches OperationRun start/completion/link UX?**: no new OperationRun behavior; possible copy-only updates on operation-related strings. +- **Shared OperationRun UX contract/layer reused**: Existing `OperationRunLinks`, `OperationUxPresenter`, and canonical operations routes remain unchanged if touched. +- **Delegated start/completion UX behaviors**: Existing queued/running/terminal UX behavior remains owned by the shared OperationRun path. +- **Local surface-owned behavior that remains**: Copy and labels only. +- **Queued DB-notification policy**: `N/A`. +- **Terminal notification path**: unchanged. +- **Exception required?**: none. + +## Provider Boundary / Platform Core Check *(mandatory)* + +- **Shared provider/platform boundary touched?**: yes, through operator vocabulary and allowed provider-specific terminology. +- **Boundary classification**: mixed +- **Seams affected**: localization values, visible provider readiness copy, required-permissions copy, support diagnostics copy, dashboard copy, Microsoft/Entra tenant ID labels, and tests that distinguish platform vocabulary from provider vocabulary. +- **Neutral platform terms preserved or introduced**: workspace, managed environment, environment, provider connection, environment scope, required permissions, diagnostics, operation, finding, review, evidence, governance. +- **Provider-specific semantics retained and why**: Microsoft tenant ID, Entra tenant ID, Microsoft Graph, Intune, and tenant ID payload metadata remain valid when the external Microsoft provider is the subject. +- **Why this does not deepen provider coupling accidentally**: The spec removes generic platform tenant-first vocabulary and narrows provider-specific `tenant` wording to Microsoft/Entra contexts only. +- **Follow-up path**: document-in-feature for remaining technical/internal names; follow-up-spec only for structural DB/model rename or broader customer-facing localization adoption. + +## UI / Surface Guardrail Impact *(mandatory)* + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| Localization values | yes | Existing Laravel translation arrays | shell, dashboard, customer workspace, support copy | copy/value only | no | key rename optional only when low risk | +| Filament labels/actions/empty states | yes | Native Filament surfaces | action labels, modal headings, page titles, empty states | page/action/copy | no | no layout redesign | +| Blade/context-bar copy | yes | Existing Blade/Filament partials | shell/context, dashboard, matrix, queue copy | view copy only | no | no new custom styling | +| Tests/guards/browser smokes | no direct operator UI | N/A | validation and smoke anchors | test wording/selectors | no | selectors should be stable and not timeout-based | + +## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)* + +| 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 | +|---|---|---|---|---|---|---|---| +| Environment dashboard/detail copy | Primary Decision Surface | Operator reviews current environment state | environment context, readiness, current blockers, next action | diagnostics and raw provider detail remain secondary | Primary because environment detail is the canonical environment entry point | Workspace -> Managed Environment -> domain pages | removes tenant-first ambiguity | +| Context bar / workspace shell copy | Secondary Context Surface | Operator confirms current workspace/environment context | workspace and environment labels | none unless existing shell exposes detail | Secondary because it orients current scope | workspace-first context switching | clarifies workspace-wide pages can exist with no environment selected | +| Required permissions/provider readiness copy | Secondary Context Surface | Operator checks provider readiness | environment/provider permission state | provider-specific Graph/Microsoft detail | Secondary because it supports readiness decisions | provider detail under environment context | keeps Microsoft tenant terms provider-specific | +| Browser/test anchors | N/A | maintainer validation | stable selectors or current copy assertions | test output only | not an operator surface | validation lane only | prevents brittle old-copy assertions | + +## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)* + +| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention | +|---|---|---|---|---|---|---|---| +| Environment dashboard/detail copy | operator-MSP, support-platform | environment status, readiness, next action | provider detail, support diagnostics | raw payloads only where already permitted | page-owned primary action | raw/support detail | one environment noun across labels | +| Context bar / shell copy | operator-MSP, support-platform | workspace and environment scope | unavailable context reason | none | choose/switch environment when applicable | none added | no tenant-panel wording | +| Required permissions/provider copy | operator-MSP, support-platform | required permission state and provider context | Microsoft/Graph/Entra details | raw provider IDs if already shown | review/grant required permissions | raw payloads | provider-specific tenant wording only where externally true | + +## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)* + +| 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 | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Managed environments context | Shell / Detail | Workspace-scoped environment context | Choose or inspect environment | existing canonical route/link | existing behavior | existing placement | unchanged; verify confirmation if label touched | `/admin/workspaces/{workspace}/environments` | `/admin/workspaces/{workspace}/environments/{environment}` | workspace + environment | Managed environment / Environment | selected or missing environment state | no route redesign | +| Required permissions/provider readiness | Detail / Readiness | Environment-scoped provider readiness | Review permissions | existing page route | N/A | existing placement | none added | inherited environment route | `/admin/workspaces/{workspace}/environments/{environment}/required-permissions` | workspace + environment + provider | Required permissions | missing permission state | provider tenant terms allowed only for Microsoft/Entra | +| Tests and guards | Validation | Guard/browser smoke | Prove retired copy/routes stay retired | N/A | N/A | N/A | N/A | N/A | N/A | test context | Admin environment context | forbidden legacy patterns | allowed regression-guard literals only | + +## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)* + +| 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 | +|---|---|---|---|---|---|---|---|---|---|---| +| Environment dashboard/detail copy | Workspace operator | Decide what needs attention in the selected environment | detail/dashboard | What needs action in this environment? | environment identity, readiness, blockers, next action | provider IDs, support diagnostics | readiness, governance result, lifecycle/outcome where existing | unchanged | existing page actions with environment wording | unchanged | +| Context bar / shell copy | Workspace operator | Confirm or switch current context | shell/context | Which workspace/environment am I operating in? | workspace, environment or no-environment state | none added | context availability | TenantPilot only | choose/switch/clear environment | none | +| Required permissions/provider readiness | Workspace operator | Decide whether provider permission state blocks environment workflows | readiness detail | Which provider permissions are missing? | required permission state and remediation path | Graph/Microsoft detail | provider readiness | Microsoft tenant only when existing action says so | review required permissions | unchanged | + +## Proportionality Review *(mandatory when structural complexity is introduced)* + +- **New source of truth?**: no +- **New persisted entity/table/artifact?**: no application persistence; one spec-local `terminology-audit.md` artifact records scan results and allowed exceptions. +- **New abstraction?**: no. +- **New enum/state/reason family?**: no. +- **New cross-domain UI framework/taxonomy?**: no. +- **Current operator problem**: old tenant-first wording in active UI/tests makes the managed-environment cutover look incomplete and encourages future contributors to reintroduce retired route/runtime concepts. +- **Existing structure is insufficient because**: copy drift is scattered across localization, Filament labels, Blade, support services, and tests; implementation needs an audit artifact and focused guard proof, not a new framework. +- **Narrowest correct implementation**: update visible values and tests in place, prefer existing localization keys where key rename is risky, document allowed technical/provider references, and add or adjust only focused guards. +- **Ownership cost**: low; one audit artifact and targeted copy/test updates. +- **Alternative intentionally rejected**: repo-wide removal of every `Tenant` string or DB/model rename. Those would be larger structural changes and out of scope. +- **Release truth**: current-release cleanup after route/runtime cutover in a pre-production environment. + +### Compatibility posture + +This feature assumes the repo's pre-production lean doctrine. + +Backward compatibility aliases, legacy route fallbacks, old helper aliases, and tenant-first compatibility copy are out of scope. Current visible copy should be replaced rather than preserved with aliases unless the phrase is provider-specific, internal, historical, or explicitly documented as a regression guard. + +## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* + +- **Test purpose / classification**: Feature guard tests for route/copy/helper contracts; Feature/Filament tests for affected labels/actions/pages; Browser tests only for affected smoke anchors; no Unit lane unless pure helper logic is touched. +- **Validation lane(s)**: Feature/Guards, Feature/Localization, Feature/Filament, Feature/Workspaces, Feature/ProviderConnections, Feature/RequiredPermissions, affected Feature areas, and targeted Browser smoke anchors. +- **Why this classification and these lanes are sufficient**: The risk is terminology, labels, selectors, and guard drift, not a new product workflow. Focused scans and existing affected suites prove the cleanup without pulling in unrelated full-suite repair. +- **New or expanded test families**: possible focused guard assertions for terminology; no new permanent lane. +- **Fixture / helper cost impact**: no new expensive setup. Existing `setAdminEnvironmentContext()` remains the current helper; do not introduce provider setup or browser defaults into cheap Feature tests. +- **Heavy-family visibility / justification**: Browser lane is explicit and limited to smoke tests whose selectors/copy are touched. +- **Special surface test profile**: `standard-native-filament`, `global-context-shell`, and `browser-smoke` for touched browser anchors. +- **Standard-native relief or required special coverage**: ordinary Pest/Filament coverage is sufficient for copy-only native Filament updates unless browser-visible anchors change. +- **Reviewer handoff**: Reviewers must confirm Filament v5 on Livewire v4.0+, panel providers remain registered through `apps/platform/bootstrap/providers.php`, globally searchable resources have Edit/View pages or disabled global search, touched destructive actions still use `->action(...)`, `->requiresConfirmation()`, and authorization, asset strategy is unchanged, and tests cover changed pages/actions/widgets through Livewire/Filament where applicable. +- **Budget / baseline / trend impact**: no planned material runtime shift. Browser tests remain targeted; no blind timeout increases. +- **Escalation needed**: document-in-feature for allowed technical/provider/historical tenant references; follow-up-spec only for structural DB/model rename or broader customer localization adoption. +- **Active feature PR close-out entry**: Guardrail / Terminology Cleanup / Smoke Coverage. +- **Planned validation commands**: + +```bash +cd apps/platform +./vendor/bin/sail artisan test --compact tests/Feature/Guards +./vendor/bin/sail artisan test --compact tests/Feature/Localization +./vendor/bin/sail artisan test --compact tests/Feature/Workspaces +./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections +./vendor/bin/sail artisan test --compact tests/Feature/RequiredPermissions +./vendor/bin/sail artisan test --compact tests/Feature/Filament +./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php +./vendor/bin/sail bin pint --dirty --format agent +git diff --check +``` + +## Repo Baseline From Preparation + +Read-only preparation scans on 2026-05-13 found: + +- The active route/link source scan for retired route generators under `apps/platform/app`, `apps/platform/resources`, and `apps/platform/routes` returned no hits for `filament.admin.resources.tenants`, `/admin/tenants`, `/admin/t/`, direct legacy `TenantResource::getUrl(...)`, `TenantDashboard::getUrl(...)`, `TenantRequiredPermissions::getUrl(...)`, or tenant panel IDs. +- `ManagedEnvironmentLinks` and `setAdminEnvironmentContext()` are repo-real and should remain the canonical route/helper vocabulary. +- Remaining target copy hits exist in localization values, Blade copy, Filament/support copy, and tests. Representative hits include `Tenant scope`, `Select tenant`, `No tenant selected`, `No active tenants`, `Tenant dashboard`, `Open tenant detail`, `Restore tenant`, `Remove tenant assignment`, and `tenant blocker`. +- `Spec288NoLegacyRouteAndHelperGuardTest.php` still contains `setTenantPanelContext` regex literals as forbidden-pattern checks. These are allowed only if the test description clearly says they forbid reintroducing the retired helper. + +The implementation must refresh `terminology-audit.md` with full required scans before editing runtime code. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Operators See Environment Vocabulary (Priority: P1) + +As a workspace operator, I need active admin surfaces to describe current context as workspace, managed environment, or environment, not as a tenant panel. + +**Why this priority**: This is the visible product truth after Spec 297. + +**Independent Test**: Render affected shell/dashboard/detail/readiness surfaces and scan active UI/localization values for targeted tenant-first phrases. + +**Acceptance Scenarios**: + +1. **Given** a workspace-wide page with no environment selected, **When** the context copy renders, **Then** it says no environment is selected and does not say no tenant is selected. +2. **Given** an environment dashboard/detail surface, **When** the heading, empty states, and helper text render, **Then** they use Environment or Managed environment wording. +3. **Given** provider-specific Microsoft detail, **When** it refers to external Microsoft identity, **Then** Microsoft tenant ID or Entra tenant ID may remain. + +--- + +### User Story 2 - Tests And Guards Describe Current Runtime Truth (Priority: P1) + +As a maintainer, I need test names, helpers, and guard messages to use admin environment context vocabulary unless they are explicitly forbidding retired TenantPanel behavior. + +**Why this priority**: Test vocabulary is a durable product contract for future agents and contributors. + +**Independent Test**: Run the guard scans and focused guard tests; verify remaining `setTenantPanelContext` text appears only as forbidden legacy regex literals with clear descriptions. + +**Acceptance Scenarios**: + +1. **Given** `tests/Pest.php`, **When** helper names are scanned, **Then** the active helper is `setAdminEnvironmentContext()` or equivalent current vocabulary with no `setTenantPanelContext()` alias. +2. **Given** Spec 288 guard tests, **When** they retain `setTenantPanelContext` regex literals, **Then** the test description and failure message state that the retired helper must not be reintroduced. +3. **Given** feature/browser tests for current admin environment flows, **When** their names and assertions are reviewed, **Then** they do not describe the current runtime as tenant panel context. + +--- + +### User Story 3 - Browser Smokes Remain Stable After Copy Cleanup (Priority: P2) + +As a maintainer, I need browser smokes to follow stable current selectors or intentionally stable current copy without increasing timeouts. + +**Why this priority**: Copy cleanup should not make the browser lane brittle. + +**Independent Test**: Run affected browser smokes individually after selector/copy updates. + +**Acceptance Scenarios**: + +1. **Given** a smoke test that previously clicked old tenant copy, **When** the target UI has a stable `data-testid`, **Then** the smoke uses that selector. +2. **Given** a smoke test that asserts current UI copy, **When** the copy is a product contract, **Then** the assertion uses the new environment wording. +3. **Given** an affected browser smoke, **When** it is updated, **Then** no timeout is raised without a documented timing cause. + +## Functional Requirements + +- **FR-001 Initial scan before changes**: Before implementation edits, run the status, route, source, test, and copy scans listed in `plan.md` and record results in `terminology-audit.md`. +- **FR-002 Guard literals updated**: `Spec288NoLegacyRouteAndHelperGuardTest.php` must not present `setTenantPanelContext` as a current helper. It may retain the regex only as an explicit forbidden legacy pattern. +- **FR-003 Test helper terminology finalized**: Current tests must use admin/managed-environment context wording and active helper names such as `setAdminEnvironmentContext()`. +- **FR-004 User-facing tenant copy neutralized**: Active UI copy for the targeted phrases must move to Environment or Managed Environment vocabulary, except provider-specific Microsoft/Entra terms. +- **FR-005 Filament labels and actions cleaned**: Page titles, navigation labels, breadcrumbs, action labels, empty states, helper texts, badge labels, modal headings, and notifications in active surfaces must not use old tenant-first product language. +- **FR-006 Blade/context-bar copy cleaned**: Active Blade and Filament partials must use environment context wording and avoid class-name-like visible copy. +- **FR-007 Localization values cleaned**: Existing EN/DE localization values must output current vocabulary. Key renames are optional and only allowed when all usages are safely updated. +- **FR-008 Browser smokes updated**: Affected smokes must use stable selectors or current copy, with no blind timeout increases. +- **FR-009 Allowed exceptions documented**: Remaining tenant-related hits must be categorized in `terminology-audit.md`. +- **FR-010 No active legacy route reintroduced**: Final route/source scans must remain clean for `/admin/t...`, `/admin/tenants...`, old URL generators, tenant panel IDs, and `setTenantPanelContext()` in runtime surfaces. +- **FR-011 Route-scope-first navigation gating**: Sidebar navigation must decide workspace vs environment navigation from the current route/surface first. Stale session, remembered environment, or `Filament::getTenant()` state must not make Workspace Overview show environment-owned navigation. +- **FR-012 Workspace sidebar stays portfolio-oriented**: Workspace-level surfaces must keep repo-real workspace-owned navigation available, including Overview, Operations, Alerts, Audit Log, Governance inbox, Customer reviews, Manage workspaces, Integrations, and Settings where authorized. Stored reports and Review Packs remain environment-owned until a separate workspace aggregate route exists. +- **FR-013 Environment sidebar appears only in environment surface scope**: Environment-owned entries such as Policies, Policy Versions, Backup Schedules, Backup Sets, Restore Runs, Findings, Baselines, Baseline Snapshots, Baseline Compare, Evidence, Risk exceptions, Required permissions, and Diagnostics may appear only when the route is clearly environment-scoped. + +## Acceptance Criteria + +- **AC-001**: Source scan is clean for active old TenantPanel route/generator patterns in `app`, `resources`, and `routes`. +- **AC-002**: Test helper scan is clean or contains only explicit forbidden legacy guard literals with clear descriptions. +- **AC-003**: Targeted tenant-first copy is absent from active UI surfaces or documented as provider-specific, internal, historical, or regression-guard-only. +- **AC-004**: `terminology-audit.md` documents every remaining allowed tenant reference found by final scans. +- **AC-005**: Route scan does not show active `/admin/t...` or `/admin/tenants...` product routes. +- **AC-006**: Spec 297 route/runtime proof remains intact. +- **AC-007**: Canonical browser anchors pass when affected. +- **AC-008**: Workspace Overview hides environment-owned primary navigation even when a previous environment remains selected in session or Filament tenant state. +- **AC-009**: Environment dashboard/detail routes show the intended environment-owned navigation for entitled users. +- **AC-008**: Pint dirty and `git diff --check` pass. + +## Out Of Scope + +- DB table rename +- `Tenant` model rename +- `tenant_id` column rename +- migration rewrite +- historical spec rewrite +- broad docs cleanup +- new localization architecture +- new customer workspace feature +- new decision inbox feature +- new routing model +- new RBAC model +- new provider abstraction +- UI redesign +- reactivation of `/admin/t...`, `/admin/tenants...`, `TenantPanelProvider`, or `setTenantPanelContext()` + +## Risks + +- Some localization keys still contain `tenant_*`; changing keys broadly could create avoidable risk. Values should be updated first unless key rename is proven bounded. +- Some visible `tenant` terms may be valid provider-specific Microsoft/Entra terminology. The audit must distinguish provider truth from product vocabulary. +- Browser smokes may rely on old visible text. Prefer stable selectors where the copy is not the contract. +- Guard regex literals can look like drift if test names/messages are ambiguous. Clarify the guard contract rather than weakening it. + +## Assumptions + +- Spec 297 has retired active route/runtime legacy surfaces and `ManagedEnvironmentLinks` is the canonical URL owner. +- The repo remains pre-production, so old compatibility aliases are not required. +- The active product mental model is Workspace first, then Managed Environment / Environment context. +- Internal technical names may remain until a separate DB/model rename spec exists. + +## Open Questions + +- None blocking preparation. Implementation must update the audit if scans reveal an active visible tenant-first phrase outside the targeted list that is clearly current product UX. + +## Follow-Up Spec Candidates + +- Structural DB/model rename from `Tenant` to `ManagedEnvironment`, if product and persistence maturity require it later. +- Broader customer-facing localization adoption beyond the targeted cleanup, aligned with the existing localization roadmap. +- Historical docs/spec cleanup, if the repository later decides historical artifacts should be normalized separately. diff --git a/specs/298-managed-environment-terminology-copy-cleanup/tasks.md b/specs/298-managed-environment-terminology-copy-cleanup/tasks.md new file mode 100644 index 00000000..6cb9948f --- /dev/null +++ b/specs/298-managed-environment-terminology-copy-cleanup/tasks.md @@ -0,0 +1,166 @@ +--- +description: "Task list for Managed Environment Terminology & Copy Cleanup" +--- + +# Tasks: Managed Environment Terminology & Copy Cleanup + +**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/` +**Prerequisites**: `spec.md`, `plan.md`, `terminology-audit.md`, `checklists/requirements.md` + +**Tests**: Required (Pest) for guard/test/localization/copy changes. Browser smoke is required only if visible browser anchors/selectors are touched. +**Operations**: No new `OperationRun` behavior. Existing operation links and copy must keep the shared OperationRun UX contract if touched. +**RBAC**: No authorization model change. Existing 404/403 semantics, capability checks, and destructive action authorization must remain intact. +**Filament / Panel Guardrails**: Filament remains v5 on Livewire v4. Provider registration remains in `apps/platform/bootstrap/providers.php`. No new panel. No asset-strategy change unless explicitly documented. +**Review Outcome**: preparation-ready +**Workflow Outcome**: keep +**Test-governance Outcome**: keep + +## Test Governance Checklist + +- [x] Lane assignment is named and is the narrowest sufficient proof for each changed behavior. +- [x] New or changed tests stay in the smallest honest family; browser/heavy-governance additions are explicit. +- [x] Shared helpers, factories, seeds, fixtures, provider setup, workspace context, session state, and capability defaults stay cheap by default. +- [x] Planned validation commands cover terminology, route guard, helper, and browser-anchor changes without pulling in unrelated lane cost. +- [x] The declared surface test profile or `standard-native-filament` relief is explicit. +- [x] Any material runtime, budget, baseline, trend, or escalation note is recorded in the active spec close-out. + +## Phase 1: Safety Gate And Baseline Audit + +**Purpose**: Start from a clean branch and record repo truth before runtime edits. + +- [x] T001 Run `git status --short --branch`, `git diff --stat`, and `git log -1 --oneline` in `/Users/ahmeddarrazi/Documents/projects/wt-plattform`; stop if unrelated uncommitted changes are present. +- [x] T002 Confirm the implementation branch is `298-managed-environment-terminology-copy-cleanup` or an isolated session branch derived from it. +- [x] T003 Review `/Users/ahmeddarrazi/Documents/projects/wt-plattform/.specify/memory/constitution.md`, this spec package, and related Specs 286, 288, and 297 as context only. +- [x] T004 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan route:list | rg "admin/tenants|admin/t/" && exit 1 || true`. +- [x] T005 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && rg "filament\\.admin\\.resources\\.tenants|/admin/tenants|/admin/t/|TenantResource::getUrl|TenantDashboard::getUrl|TenantRequiredPermissions::getUrl|setTenantPanelContext|panel:\\s*'tenant'|panel:\\s*\\\"tenant\\\"" app resources routes --glob '!vendor' --glob '!node_modules'`. +- [x] T006 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && rg "setTenantPanelContext|panel:\\s*'tenant'|panel:\\s*\\\"tenant\\\"" tests --glob '!vendor' --glob '!node_modules'`. +- [x] T007 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && rg "Tenant dashboard|Tenant detail|Open tenant|Select tenant|Tenant scope|No tenant selected|No active tenants|Remove tenant|Restore tenant|Tenant memberships|tenant blocker" app resources lang tests --glob '!vendor' --glob '!node_modules'`. +- [x] T008 Update `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md` with every initial finding before editing application code. +- [x] T009 Confirm the scope boundary remains explicit: no DB/model rename, no migration rewrite, no old route restoration, no helper alias, no broad localization architecture, and no UI redesign. + +## Phase 2: Guard And Test Helper Terminology + +**Goal**: Make current test vocabulary match admin environment context while retaining explicit retired-helper guards. + +- [x] T010 [P] Inspect `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Pest.php` and confirm `setAdminEnvironmentContext()` is the active helper and `setTenantPanelContext()` is absent. +- [x] T011 [P] Inspect `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php` for `setTenantPanelContext` forbidden-pattern literals. +- [x] T012 Update Spec 288 guard test names, comments, and failure messages so any remaining `setTenantPanelContext` regex literal clearly forbids reintroducing the retired tenant panel context helper. +- [x] T013 Update active test names/comments that describe current runtime setup as `tenant panel`, `tenant panel context`, `panel tenant`, or equivalent legacy wording. +- [x] T014 Ensure no compatibility alias named `setTenantPanelContext()` is introduced. +- [x] T015 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php`. +- [x] T016 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards`. + +## Phase 3: Localization Values + +**Goal**: Make EN/DE values output environment-first product language. + +- [x] T017 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/en/localization.php` for targeted tenant-first values. +- [x] T018 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/lang/de/localization.php` for targeted tenant-first values. +- [x] T019 Update EN shell/dashboard/support/customer-visible values so active generic UI says environment or managed environment instead of tenant-first wording. +- [x] T020 Update DE shell/dashboard/support/customer-visible values with equivalent environment wording. +- [x] T021 Keep existing localization keys if renaming them would broaden scope; update visible values first. +- [x] T022 If any key is renamed, update all usages and tests in the same phase. +- [x] T023 Update localization tests under `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Localization` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Feature/Guards` that assert old copy. +- [x] T024 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization`. + +## Phase 4: Active UI Copy In App And Views + +**Goal**: Replace targeted tenant-first visible copy in active Filament, Blade, support, and service surfaces. + +- [x] T025 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/resources/views/filament/partials/context-bar.blade.php` and other active context-bar/shell views for visible `Tenant scope`, `No tenant selected`, and `Open tenant` wording. +- [x] T026 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php`, and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php`. +- [x] T027 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php`, `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php`, and relevant `app/Filament/**` surfaces for targeted labels/actions/headings. +- [x] T028 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Support/**` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/app/Services/**` for emitted visible copy such as `Restore tenant`, `No tenant selected`, and `tenant blocker`. +- [x] T029 Replace active visible `Open tenant detail`, `Open tenant compare`, and similar labels with `Open environment ...` wording unless the target is provider-specific Microsoft tenant detail. +- [x] T030 Replace active visible `Tenant scope`, `Select tenant`, `No tenant selected`, and `No active tenants` with environment wording. +- [x] T031 Replace active visible `Remove tenant`, `Restore tenant`, and `Tenant memberships` with environment/access-scope wording while preserving destructive action confirmation and authorization. +- [x] T032 Replace active visible `tenant blocker` or similar dashboard/readiness phrasing with environment wording. +- [x] T033 Preserve provider-specific phrases such as Microsoft tenant ID and Entra tenant ID when the external provider is the subject. +- [x] T034 Update affected Feature/Filament tests to assert current copy or stable semantics. + +## Phase 5: Browser Smoke Selectors + +**Goal**: Keep browser smoke tests stable after copy changes. + +- [x] T035 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php`. +- [x] T036 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php`. +- [x] T037 [P] Audit `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Spec192RecordPageHeaderDisciplineSmokeTest.php` and `/Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform/tests/Browser/Dashboard/TenantDashboardProductizationSmokeTest.php` if copy changes affect them. +- [x] T038 Prefer stable `data-testid` selectors for changed click targets when copy is not the product contract. +- [x] T039 If adding a new `data-testid`, keep it narrowly scoped to the existing element and avoid layout/style changes. +- [x] T040 Do not increase browser timeouts unless a real timing cause is identified and documented. +- [x] T041 Run each affected browser test individually before the final browser anchor command. + +## Phase 6: Audit Exceptions And Final Scans + +**Goal**: Ensure every remaining tenant reference is intentional and documented. + +- [x] T042 Run the final route scan: `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan route:list | rg "admin/tenants|admin/t/" && exit 1 || true`. +- [x] T043 Run the final source scan from T005 and ensure it is clean or documented with explicit allowed technical exceptions. +- [x] T044 Run the final test-helper scan from T006 and ensure only explicit forbidden-legacy guard literals remain. +- [x] T045 Run the final copy scan from T007 and ensure every remaining hit is absent or documented. +- [x] T046 Update `/Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md` with categories for every remaining hit: `fixed`, `allowed-provider-term`, `allowed-internal-model`, `allowed-historical`, `allowed-regression-guard`, `out-of-scope-db-model-rename`, or `needs-follow-up`. +- [x] T047 Confirm no active runtime route, provider, compatibility alias, or global-search route regression from Spec 297 was reintroduced. + +## Phase 7: Proof Pack And Formatting + +**Goal**: Prove focused lanes remain green. + +**Close-out note**: The full Filament feature lane was rerun after formatting and the terminology fixes. It completed with one order-sensitive failure in `tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactEnvironmentContextTest.php` where `ReviewPackResource::executeGeneration()` did not create a `ReviewPack` during the full suite; the same test file passed immediately when run alone. No changed implementation file participates in that governance artifact generation path, so this is recorded as residual non-terminology test-order risk rather than an in-scope Spec 298 finding. + +- [x] T048 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards`. +- [x] T049 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization`. +- [x] T050 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Workspaces`. +- [x] T051 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections`. +- [x] T052 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/RequiredPermissions`. +- [x] T053 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament`. +- [x] T054 If browser files or visible browser anchors changed, run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php` plus any touched browser file. +- [x] T055 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`. +- [x] T056 Run `git diff --check` from `/Users/ahmeddarrazi/Documents/projects/wt-plattform`. + +## Phase 8: Close-Out Summary + +**Goal**: Finish with a reviewer-ready proof summary and decision. + +- [x] T057 Confirm the Filament v5 output contract in the final implementation summary: Livewire v4.0+ compliance, provider registration in `bootstrap/providers.php`, global-search handling, destructive action confirmation/authorization, asset strategy, and testing plan. +- [x] T058 Record commands run and results in the final implementation summary. +- [x] T059 Record fixed terminology groups with before/after examples. +- [x] T060 Record remaining allowed references and reasons from `terminology-audit.md`. +- [x] T061 Choose one final decision string: `298 merge-ready; terminology cleanup complete`, `298 merge-ready with documented allowed technical tenant references`, `298 blocked by active legacy tenant copy`, or `298 blocked by runtime legacy regression`. + +## Phase 9: Navigation Segregation Addendum + +**Goal**: Prevent stale environment/session context from making Workspace Overview show environment-owned primary navigation. + +- [x] T062 Run the navigation and context baseline scans from `plan.md` Phase 5A and record the current leak path in `terminology-audit.md`. +- [x] T063 Add or reuse one central route-scope helper that distinguishes workspace surfaces from environment surfaces with current route scope taking precedence over session and `Filament::getTenant()` state. +- [x] T064 Update the Admin panel navigation override so workspace surfaces render workspace-owned navigation even when a stale environment is remembered or selected. +- [x] T065 Gate environment-owned Resource/Page navigation so Policies, Policy Versions, Backup Schedules, Backup Sets, Restore Runs, Findings, Baselines, Baseline Snapshots, Baseline Compare, Evidence, Risk exceptions, Stored reports, Review Packs, and Reviews do not register on Workspace Overview. +- [x] T066 Keep repo-real workspace-owned navigation visible on workspace surfaces: Overview, Operations, Alerts, Audit Log, Governance inbox, Customer reviews, Manage workspaces, Integrations, and Settings where authorized. Do not invent workspace aggregate routes for Stored reports or Review Packs under this cleanup spec. +- [x] T067 Update focused navigation guard tests for Workspace Overview with stale Filament tenant, canonical environment route visibility, and retired `/admin/t...` and `/admin/tenants...` route absence. +- [x] T068 Run `cd /Users/ahmeddarrazi/Documents/projects/wt-plattform/apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/PanelNavigationSegregationTest.php`. +- [x] T069 Run affected legacy/canonical guard tests and targeted browser smoke anchors only; do not run a raw full suite. + +## Dependencies & Execution Order + +- Phase 1 blocks all runtime edits. +- Phase 2 can run in parallel with Phase 3 after Phase 1 completes. +- Phase 4 depends on the audit from Phase 1 and may proceed alongside localization updates if file ownership stays clear. +- Phase 5 depends on knowing whether copy changes touched browser-visible anchors. +- Phase 6 must run after all copy/test updates. +- Phase 7 and Phase 8 close the proof loop. + +## Parallel Execution Examples + +- T010 and T017 can run in parallel because test-helper inspection and localization inspection touch different files. +- T025, T026, T027, and T028 can run in parallel because they audit different UI/code surfaces. +- T035 and T036 can run in parallel because the browser smoke files are independent. + +## Explicit Follow-Ups / Out of Scope + +- Database/model rename from `Tenant` to `ManagedEnvironment` +- Broad localization v1 or customer-facing localization adoption +- Historical spec/doc rewrite +- New customer review workspace or decision inbox +- New RBAC or provider abstraction +- UI redesign +- Reactivation of `/admin/t...`, `/admin/tenants...`, `TenantPanelProvider`, or `setTenantPanelContext()` diff --git a/specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md b/specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md new file mode 100644 index 00000000..44585cfb --- /dev/null +++ b/specs/298-managed-environment-terminology-copy-cleanup/terminology-audit.md @@ -0,0 +1,119 @@ +# Terminology Audit: Managed Environment Terminology & Copy Cleanup + +**Feature**: `298-managed-environment-terminology-copy-cleanup` +**Created**: 2026-05-13 +**Status**: Implemented close-out. Final scans refreshed on 2026-05-13. + +## Categories + +| Category | Meaning | +|---|---| +| `fixed` | The implementation replaced or removed the old tenant-first wording. | +| `allowed-provider-term` | The term describes external Microsoft/Entra provider truth, such as Microsoft tenant ID. | +| `allowed-internal-model` | The term is an internal model/class/table/column name and DB/model rename is out of scope. | +| `allowed-historical` | The term appears in historical specs/docs/audit history that this spec does not rewrite. | +| `allowed-regression-guard` | The term is kept only as a forbidden legacy pattern in a guard test. | +| `out-of-scope-db-model-rename` | The term requires a structural DB/model rename outside Spec 298. | +| `needs-follow-up` | The term is real drift but cannot be safely fixed inside Spec 298. | + +## Implementation Baseline Scan + +Read-only implementation scans were refreshed on 2026-05-13 before application edits. + +| Command | Result | Decision | +|---|---|---| +| `git status --short --branch && git diff --stat && git log -1 --oneline` | Active branch is `298-managed-environment-terminology-copy-cleanup`; only the active spec package was untracked before audit edit; base commit `3ec582a1 feat: retire legacy tenant route surfaces (#352)`. | Safe to continue because the untracked files are the active prepared spec artifacts. | +| `cd apps/platform && ./vendor/bin/sail artisan route:list \| rg "admin/tenants\|admin/t/" && exit 1 \|\| true` | No output; no active route-list matches for retired route families. | Route baseline clean. | +| `cd apps/platform && rg "filament\\.admin\\.resources\\.tenants\|/admin/tenants\|/admin/t/\|TenantResource::getUrl\|TenantDashboard::getUrl\|TenantRequiredPermissions::getUrl\|setTenantPanelContext\|panel:\\s*'tenant'\|panel:\\s*\\\"tenant\\\"" app resources routes --glob '!vendor' --glob '!node_modules'` | No output; no active runtime source matches in `app`, `resources`, or `routes`. | Source baseline clean. | +| `cd apps/platform && rg "setTenantPanelContext\|panel:\\s*'tenant'\|panel:\\s*\\\"tenant\\\"" tests --glob '!vendor' --glob '!node_modules'` | Five `setTenantPanelContext` regex literals remain in `tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php`. | Allowed only as regression-guard literals after wording clarification. | +| `cd apps/platform && rg "Tenant dashboard\|Tenant detail\|Open tenant\|Select tenant\|Tenant scope\|No tenant selected\|No active tenants\|Remove tenant\|Restore tenant\|Tenant memberships\|tenant blocker" app resources lang tests --glob '!vendor' --glob '!node_modules'` | Targeted active copy/test hits listed below. | In-scope cleanup targets except provider/internal/historical references. | + +### Baseline Findings To Fix + +| Pattern | File | Category | Decision | Reason | +|---|---|---|---|---| +| `Tenant scope`, `Select tenant`, `No tenant selected`, `No active tenants`, `Tenant dashboard`, `tenant blocker` | `apps/platform/lang/en/localization.php` | fixed | planned | Active localization values should output environment-first wording. Existing keys may remain if key rename is risky. | +| German tenant-first equivalents for context/dashboard copy | `apps/platform/lang/de/localization.php` | fixed | planned | Active DE localization values should output environment-first wording. | +| `Open tenant detail` | `apps/platform/resources/views/filament/pages/monitoring/finding-exceptions-queue.blade.php` | fixed | planned | Active view copy should say `Open environment detail` unless provider-specific. | +| `Open tenant compare` | `apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php` | fixed | planned | Baseline compare is an environment-context action after cutover. | +| `No tenant selected.` | `apps/platform/resources/views/filament/pages/tenant-required-permissions.blade.php` | fixed | planned | Required permissions page should describe missing environment context. | +| `Restore tenant` | `apps/platform/app/Services/Tenants/TenantActionPolicySurface.php` | fixed | planned | Visible operator copy should say restore environment while preserving confirmation/authorization. | +| `Restore tenant` | `apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php` | fixed | planned | Governance action copy should say restore environment while preserving confirmation/authorization. | +| `Open tenant detail` | `apps/platform/app/Filament/Pages/Monitoring/FindingExceptionsQueue.php` | fixed | planned | Active action label should use environment wording. | +| `Open tenant findings` | `apps/platform/app/Filament/Pages/Findings/MyFindingsInbox.php`, `apps/platform/tests/Feature/Findings/MyWorkInboxTest.php` | fixed | planned | Active action label should use environment wording if it targets environment-scoped findings. | +| `No tenant selected` | `apps/platform/app/Filament/Resources/BackupScheduleResource.php` | fixed | planned | Backup schedule context copy should use environment wording. | +| `Remove tenant assignment` | `apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php` | fixed | planned | Visible destructive label should use environment assignment wording while preserving confirmation/authorization. | +| `No tenant selected.` | `apps/platform/app/Support/Baselines/BaselineCompareStats.php` | fixed | planned | Empty compare state should use environment context wording. | +| Old-copy assertions | `apps/platform/tests/Feature/Localization/EnvironmentContextTerminologyTest.php`, `apps/platform/tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php` | fixed | planned | Tests should assert current environment vocabulary and forbid retired product copy. | +| `setTenantPanelContext` regex literals | `apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php` | allowed-regression-guard | clarify | May remain only as explicit forbidden legacy pattern. Test names/messages must not suggest a current helper. | + +## Final Audit + +| Pattern | File | Category | Decision | Reason | +|---|---|---|---|---| +| Retired route families `/admin/tenants` and `/admin/t/` | `apps/platform` route list | fixed | clean | Final route scan returned no matches. | +| Retired tenant panel route/helper patterns from T005 | `apps/platform/app`, `apps/platform/resources`, `apps/platform/routes` | fixed | clean | Final source scan returned no matches for retired route names, helper names, tenant panel provider usage, or tenant-panel route generation. | +| `setTenantPanelContext` | `apps/platform/tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php` | allowed-regression-guard | keep | Remaining hits are forbidden-pattern regex literals and a clarified failure message preventing reintroduction of the retired helper. | +| `Tenant scope`, `Select tenant`, `No tenant selected`, `No active tenants`, `Tenant dashboard`, `tenant blocker` | `apps/platform/lang/en/localization.php`, `apps/platform/lang/de/localization.php`, context/support tests | fixed | complete | Active localization values now use environment/managed-environment wording. Existing key names remain where key renames would broaden scope. | +| Finding queue and governance inbox tenant-first labels | `apps/platform/app/Filament/Pages/**`, `apps/platform/resources/views/filament/pages/**`, related tests | fixed | complete | Active visible labels now say environment detail, environment findings, environment filter, or environment scope as appropriate. | +| Baseline compare tenant-first labels and empty states | `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php`, `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php`, `apps/platform/app/Support/Baselines/**`, `apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php`, related tests | fixed | complete | Visible compare labels now say assigned environments, visible assigned environments, and multi-environment scan. Internal action IDs and model names remain unchanged. | +| Destructive governance/environment action copy | `apps/platform/app/Services/Tenants/TenantActionPolicySurface.php`, `apps/platform/app/Support/Ui/GovernanceActions/GovernanceActionCatalog.php`, `apps/platform/app/Filament/Resources/BaselineProfileResource/RelationManagers/BaselineTenantAssignmentsRelationManager.php` | fixed | complete | Visible copy now says restore/archive/remove environment assignment while existing `->action(...)`, `->requiresConfirmation()`, and authorization surfaces remain intact. | +| Old product-copy strings in copy scan | `apps/platform/tests/Feature/Localization/EnvironmentContextTerminologyTest.php`, `apps/platform/tests/Feature/Guards/EnvironmentCopyNeutralizationGuardTest.php` | allowed-regression-guard | keep | Final copy scan hits are forbidden strings used only by regression guards/assertions to ensure active UI does not reintroduce them. | +| Microsoft tenant / Entra tenant identifiers | Provider/auth/Graph-facing code and tests | allowed-provider-term | keep | These terms describe external Microsoft provider concepts and are not product context labels. | +| `Tenant`, `tenant_id`, `tenantRouteKey`, `TenantResource`, tenant review model/resource names | Models, resources, relations, fixtures, historical test names | allowed-internal-model | keep | DB/model/resource rename is explicitly out of scope for Spec 298. Runtime routes were not restored. | +| Historical specs, archived decision context, and prior spec names | `specs/**`, `.specify/**`, historical tests where applicable | allowed-historical | keep | This cleanup does not rewrite historical records or prior spec names. | + +## Navigation Segregation Addendum + +The workspace/environment sidebar leak was confirmed as a route/context-gating issue: the admin middleware previously switched back to full Filament navigation whenever `Filament::getTenant()` was filled, even when the current route was Workspace Overview. Spec 298 now treats the current route/surface as the navigation source of truth. + +| Entry | Workspace nav | Environment nav | Reason | +|---|---:|---:|---| +| Overview | yes | yes | Workspace landing remains the stable return point. | +| Operations | yes | yes | Workspace-scoped operations can optionally filter by environment. | +| Alerts | yes | yes | Workspace monitoring remains portfolio-oriented. | +| Audit Log | yes | yes | Workspace monitoring remains portfolio-oriented. | +| Governance inbox | yes | yes | Existing workspace-level governance triage surface. | +| Customer reviews | yes | yes | Existing workspace-level review workspace. | +| Manage workspaces | yes | yes | Workspace administration entry point. | +| Integrations | yes | yes | Existing provider-connection workspace route. | +| Settings | yes | yes | Existing workspace settings route, capability-gated. | +| Policies / Policy Versions / Inventory Items | no | yes | Tenant-owned inventory routes live under canonical environment routes. | +| Backup Schedules / Backup Sets / Restore Runs | no | yes | Backup/restore records are tenant-owned environment routes. | +| Findings / Risk exceptions / Evidence | no | yes | Governance artifacts are environment-owned records. | +| Baselines / Baseline Snapshots / Baseline Compare | no | yes | Main navigation is hidden on Workspace Overview and restored on environment surfaces; profile URLs remain workspace-owned until broader IA changes. | +| Stored reports / Review Packs / Reviews | no | yes | Current repo routes are environment-owned; no workspace aggregate route was invented under this cleanup spec. | + +Implementation files: + +- `apps/platform/app/Support/Navigation/NavigationScope.php`: central route-scope helper, including Livewire update referer handling. +- `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`: admin sidebar now uses `NavigationScope::isEnvironmentSurface()` instead of stale `Filament::getTenant()` to decide full environment navigation. +- Environment-owned Filament resources/pages now call `NavigationScope::shouldRegisterEnvironmentNavigation()` from `shouldRegisterNavigation()`. +- `apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php`: covers workspace overview with stale Filament tenant, environment route visibility, and retired `/admin/t...` plus `/admin/tenants...` absence. + +## Final Verification Evidence + +| Command | Result | +|---|---| +| `cd apps/platform && ./vendor/bin/sail artisan route:list \| rg "workspaces/.*/environments\|admin/tenants\|admin/t\|operations\|provider-connections\|required-permissions"` | Confirmed canonical workspace/environment routes; no retired route family restored. | +| `cd apps/platform && rg "shouldRegisterNavigation|getNavigationGroup|getNavigationLabel|getNavigationSort|navigation" app/Filament app/Providers resources tests --glob '!vendor' --glob '!node_modules'` | Confirmed navigation registration surfaces and updated route-scope gating. | +| `cd apps/platform && rg "Filament::getTenant|Filament::setTenant|WorkspaceContext|ManagedEnvironment|current.*tenant|tenant context|environment context|setAdminEnvironmentContext|SESSION_KEY" app tests --glob '!vendor' --glob '!node_modules'` | Confirmed context seams; navigation decision no longer relies on stale selected environment alone. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/PanelNavigationSegregationTest.php` | Passed: 21 tests, 59 assertions after navigation addendum. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactAdminPanelRegistrationTest.php` | Passed: 4 tests, 48 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Baselines/BaselineProfileWorkspaceOwnershipTest.php` | Passed: 2 tests, 9 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/Spec288NoLegacyRouteAndHelperGuardTest.php tests/Feature/Guards/ManagedEnvironmentCanonicalRouteContractTest.php tests/Feature/Guards/NoLegacyTenantPanelRuntimeTest.php tests/Feature/Filament/WorkspaceContextTopbarAndTenantSelectionTest.php tests/Feature/Filament/WorkspaceOverviewArrivalContextTest.php` | Passed: 11 tests, 97 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/WorkspaceOverviewNavigationTest.php tests/Feature/Filament/WorkspaceOverviewArrivalContextTest.php tests/Feature/Filament/WorkspaceContextTopbarAndTenantSelectionTest.php` | Passed: 6 tests, 52 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php` | Passed: 2 tests, 29 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards` | Passed: 265 tests, 4705 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization` | Passed: 16 tests, 95 assertions after updating stale German auth copy expectation. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Workspaces` | Passed: 96 tests, 276 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections` | Passed: 78 tests, 588 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/RequiredPermissions` | Passed: 21 tests, 82 assertions. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament` | Rerun after formatting completed with 764 passed, 5 skipped, 1 failed. The failure was an order-sensitive `ReviewPack` creation assertion in `GovernanceArtifactEnvironmentContextTest`, outside changed terminology code. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/GovernanceArtifacts/GovernanceArtifactEnvironmentContextTest.php` | Passed: 5 tests, 12 assertions immediately after the full-suite failure. | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php tests/Browser/Dashboard/TenantDashboardProductizationSmokeTest.php` | Passed: 6 tests, 105 assertions. | +| `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` | Passed. | + +## Residual Risk + +The only remaining validation issue is the order-sensitive full Filament lane failure documented above. It does not touch the Spec 298 terminology files and passes in isolation, so it is not treated as an in-scope terminology finding. It should be tracked separately if full-suite determinism is required before merge.