refactor: consolidate internal tenant model naming (#355)

## Summary
- consolidate internal platform naming from `Tenant` to `Environment` / `ManagedEnvironment` across models, controllers, services, and Filament resources
- rename environment-scoped UI surfaces such as dashboards, chooser flows, navigation, and related widgets to match the updated environment-first domain language
- align middleware, onboarding/review lifecycle services, jobs, and route/context controllers with the new environment-scoped architecture

## Validation
- not rerun as part of this commit/push/PR request

## Notes
- branch is 1 commit ahead of `platform-dev`
- main commit: `refactor: consolidate internal tenant model naming`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #355
This commit is contained in:
ahmido 2026-05-14 11:13:28 +00:00
parent b98bafcf86
commit 292d555eac
574 changed files with 6178 additions and 4846 deletions

View File

@ -40,8 +40,9 @@ public static function getUrl(?string $name = null, array $parameters = [], bool
return url('/admin'); return url('/admin');
} }
$parameters['tenant'] ??= $resolvedTenant; $parameters['environment'] ??= $resolvedTenant;
$parameters['workspace'] ??= $workspace; $parameters['workspace'] ??= $workspace;
unset($parameters['tenant']);
return parent::getUrl($name, $parameters, $isAbsolute, $panelId, null, $shouldGuessMissingParameters); return parent::getUrl($name, $parameters, $isAbsolute, $panelId, null, $shouldGuessMissingParameters);
} }
@ -52,7 +53,7 @@ protected static function workspaceScopedSlug(string $slug, ?Panel $panel = null
return $slug; return $slug;
} }
$prefix = 'workspaces/{workspace}/environments/{tenant}/'; $prefix = 'workspaces/{workspace}/environments/{environment}/';
return str_starts_with($slug, $prefix) return str_starts_with($slug, $prefix)
? $slug ? $slug

View File

@ -18,7 +18,7 @@
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class ChooseTenant extends Page class ChooseEnvironment extends Page
{ {
protected static string $layout = 'filament-panels::components.layout.simple'; protected static string $layout = 'filament-panels::components.layout.simple';
@ -26,9 +26,9 @@ class ChooseTenant extends Page
protected static bool $isDiscovered = false; protected static bool $isDiscovered = false;
protected static ?string $slug = 'choose-tenant'; protected static ?string $slug = 'choose-environment';
protected string $view = 'filament.pages.choose-tenant'; protected string $view = 'filament.pages.choose-environment';
public function getTitle(): string public function getTitle(): string
{ {
@ -66,7 +66,7 @@ public function getTenants(): Collection
return app(TenantOperabilityService::class)->filterSelectable(collect($tenants)); return app(TenantOperabilityService::class)->filterSelectable(collect($tenants));
} }
public function selectTenant(int $tenantId): void public function selectEnvironment(int $tenantId): void
{ {
$user = auth()->user(); $user = auth()->user();

View File

@ -11,7 +11,7 @@
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Auth\CapabilityResolver; use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver; use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\PortfolioCompare\CrossTenantPromotionExecutionService; use App\Services\PortfolioCompare\CrossEnvironmentPromotionExecutionService;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\ManagedEnvironmentLinks; use App\Support\ManagedEnvironmentLinks;
use App\Support\Navigation\CanonicalNavigationContext; use App\Support\Navigation\CanonicalNavigationContext;
@ -19,9 +19,9 @@
use App\Support\OperationRunLinks; use App\Support\OperationRunLinks;
use App\Support\OpsUx\OpsUxBrowserEvents; use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\OpsUx\ProviderOperationStartResultPresenter; use App\Support\OpsUx\ProviderOperationStartResultPresenter;
use App\Support\PortfolioCompare\CrossTenantComparePreviewBuilder; use App\Support\PortfolioCompare\CrossEnvironmentComparePreviewBuilder;
use App\Support\PortfolioCompare\CrossTenantCompareSelection; use App\Support\PortfolioCompare\CrossEnvironmentCompareSelection;
use App\Support\PortfolioCompare\CrossTenantPromotionPreflight; use App\Support\PortfolioCompare\CrossEnvironmentPromotionPreflight;
use App\Support\Rbac\WorkspaceUiEnforcement; use App\Support\Rbac\WorkspaceUiEnforcement;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -42,13 +42,13 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
use UnitEnum; use UnitEnum;
class CrossTenantComparePage extends Page implements HasForms class CrossEnvironmentComparePage extends Page implements HasForms
{ {
use InteractsWithForms; use InteractsWithForms;
private const string SOURCE_TENANT_QUERY_KEY = 'source_tenant_id'; private const string SOURCE_ENVIRONMENT_QUERY_KEY = 'source_environment_id';
private const string TARGET_TENANT_QUERY_KEY = 'target_tenant_id'; private const string TARGET_ENVIRONMENT_QUERY_KEY = 'target_environment_id';
private const string POLICY_TYPE_QUERY_KEY = 'policy_type'; private const string POLICY_TYPE_QUERY_KEY = 'policy_type';
@ -60,15 +60,15 @@ class CrossTenantComparePage extends Page implements HasForms
protected static string|UnitEnum|null $navigationGroup = 'Governance'; protected static string|UnitEnum|null $navigationGroup = 'Governance';
protected static ?string $title = 'Cross-ManagedEnvironment Compare'; protected static ?string $title = 'Cross-environment compare';
protected static ?string $slug = 'cross-tenant-compare'; protected static ?string $slug = 'cross-environment-compare';
protected string $view = 'filament.pages.cross-tenant-compare'; protected string $view = 'filament.pages.cross-environment-compare';
public ?string $sourceTenantId = null; public ?string $sourceEnvironmentId = null;
public ?string $targetTenantId = null; public ?string $targetEnvironmentId = null;
/** /**
* @var list<string> * @var list<string>
@ -95,12 +95,12 @@ class CrossTenantComparePage extends Page implements HasForms
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{ {
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly) return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions preserve return navigation, tenant drill-downs, and one dominant promotion-preflight action.') ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions preserve return navigation, environment drill-downs, and one dominant promotion-preflight action.')
->exempt(ActionSurfaceSlot::InspectAffordance, 'The compare page uses explicit selection controls instead of row-click inspection.') ->exempt(ActionSurfaceSlot::InspectAffordance, 'The compare page uses explicit selection controls instead of row-click inspection.')
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Cross-tenant compare renders focused subject summaries instead of row-level overflow actions.') ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Cross-environment compare renders focused subject summaries instead of row-level overflow actions.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The compare page has no bulk actions.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The compare page has no bulk actions.')
->satisfy(ActionSurfaceSlot::ListEmptyState, 'The compare page explains when a selection is incomplete or invalid before any preview exists.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The compare page explains when a selection is incomplete or invalid before any preview exists.')
->exempt(ActionSurfaceSlot::DetailHeader, 'Cross-tenant compare is a workspace decision page, not a record detail header.'); ->exempt(ActionSurfaceSlot::DetailHeader, 'Cross-environment compare is a workspace decision page, not a record detail header.');
} }
public function mount(): void public function mount(): void
@ -124,24 +124,24 @@ public function form(Schema $schema): Schema
'xl' => 3, 'xl' => 3,
]) ])
->schema([ ->schema([
Select::make('sourceTenantId') Select::make('sourceEnvironmentId')
->label('Source tenant') ->label('Source environment')
->options(fn (): array => $this->tenantOptions()) ->options(fn (): array => $this->environmentOptions())
->searchable() ->searchable()
->preload() ->preload()
->native(false) ->native(false)
->placeholder('Select a source tenant') ->placeholder('Select a source environment')
->extraFieldWrapperAttributes(['data-testid' => 'cross-tenant-source']) ->extraFieldWrapperAttributes(['data-testid' => 'cross-environment-source'])
->extraInputAttributes(['data-testid' => 'cross-tenant-source']), ->extraInputAttributes(['data-testid' => 'cross-environment-source']),
Select::make('targetTenantId') Select::make('targetEnvironmentId')
->label('Target tenant') ->label('Target environment')
->options(fn (): array => $this->tenantOptions()) ->options(fn (): array => $this->environmentOptions())
->searchable() ->searchable()
->preload() ->preload()
->native(false) ->native(false)
->placeholder('Select a target tenant') ->placeholder('Select a target environment')
->extraFieldWrapperAttributes(['data-testid' => 'cross-tenant-target']) ->extraFieldWrapperAttributes(['data-testid' => 'cross-environment-target'])
->extraInputAttributes(['data-testid' => 'cross-tenant-target']), ->extraInputAttributes(['data-testid' => 'cross-environment-target']),
Select::make('selectedPolicyTypes') Select::make('selectedPolicyTypes')
->label('Governed subjects') ->label('Governed subjects')
->options(fn (): array => $this->policyTypeOptions()) ->options(fn (): array => $this->policyTypeOptions())
@ -151,10 +151,10 @@ public function form(Schema $schema): Schema
->native(false) ->native(false)
->placeholder('All governed subjects') ->placeholder('All governed subjects')
->helperText(fn (): ?string => $this->policyTypeOptions() === [] ->helperText(fn (): ?string => $this->policyTypeOptions() === []
? 'Governed subject filters appear after authorized tenant inventory exists in the active workspace.' ? 'Governed subject filters appear after authorized environment inventory exists in the active workspace.'
: null) : null)
->extraFieldWrapperAttributes(['data-testid' => 'cross-tenant-policy-types']) ->extraFieldWrapperAttributes(['data-testid' => 'cross-environment-policy-types'])
->extraInputAttributes(['data-testid' => 'cross-tenant-policy-types']), ->extraInputAttributes(['data-testid' => 'cross-environment-policy-types']),
]), ]),
]); ]);
} }
@ -176,24 +176,24 @@ protected function getHeaderActions(): array
->url($navigationContext->backLinkUrl); ->url($navigationContext->backLinkUrl);
} }
$sourceTenant = $this->selectedSourceTenant(); $sourceEnvironment = $this->selectedSourceEnvironment();
if ($sourceTenant instanceof ManagedEnvironment) { if ($sourceEnvironment instanceof ManagedEnvironment) {
$actions[] = Action::make('open_source_tenant') $actions[] = Action::make('open_source_environment')
->label('Open source tenant') ->label('Open source environment')
->icon('heroicon-o-arrow-top-right-on-square') ->icon('heroicon-o-arrow-top-right-on-square')
->color('gray') ->color('gray')
->url(ManagedEnvironmentLinks::viewUrl($sourceTenant)); ->url(ManagedEnvironmentLinks::viewUrl($sourceEnvironment));
} }
$targetTenant = $this->selectedTargetTenant(); $targetEnvironment = $this->selectedTargetEnvironment();
if ($targetTenant instanceof ManagedEnvironment) { if ($targetEnvironment instanceof ManagedEnvironment) {
$actions[] = Action::make('open_target_tenant') $actions[] = Action::make('open_target_environment')
->label('Open target tenant') ->label('Open target environment')
->icon('heroicon-o-arrow-top-right-on-square') ->icon('heroicon-o-arrow-top-right-on-square')
->color('gray') ->color('gray')
->url(ManagedEnvironmentLinks::viewUrl($targetTenant)); ->url(ManagedEnvironmentLinks::viewUrl($targetEnvironment));
} }
$preflightAction = Action::make('generatePromotionPreflight') $preflightAction = Action::make('generatePromotionPreflight')
@ -254,15 +254,15 @@ public function applySelection(): void
$this->selectionMessage = null; $this->selectionMessage = null;
$this->preflight = null; $this->preflight = null;
$this->sourceTenantId = $this->normalizeTenantIdentifier($this->sourceTenantId); $this->sourceEnvironmentId = $this->normalizeEnvironmentIdentifier($this->sourceEnvironmentId);
$this->targetTenantId = $this->normalizeTenantIdentifier($this->targetTenantId); $this->targetEnvironmentId = $this->normalizeEnvironmentIdentifier($this->targetEnvironmentId);
$this->selectedPolicyTypes = $this->normalizePolicyTypes($this->selectedPolicyTypes); $this->selectedPolicyTypes = $this->normalizePolicyTypes($this->selectedPolicyTypes);
if ($this->sourceTenantId !== null if ($this->sourceEnvironmentId !== null
&& $this->targetTenantId !== null && $this->targetEnvironmentId !== null
&& $this->sourceTenantId === $this->targetTenantId) { && $this->sourceEnvironmentId === $this->targetEnvironmentId) {
$this->selectionMessage = 'Choose two different tenants.'; $this->selectionMessage = 'Choose two different environments.';
$this->addError('targetTenantId', $this->selectionMessage); $this->addError('targetEnvironmentId', $this->selectionMessage);
return; return;
} }
@ -285,20 +285,20 @@ public function generatePromotionPreflight(): void
$selection = $this->compareSelection(); $selection = $this->compareSelection();
if (! $selection instanceof CrossTenantCompareSelection) { if (! $selection instanceof CrossEnvironmentCompareSelection) {
return; return;
} }
$this->preflight = app(CrossTenantPromotionPreflight::class)->build($this->preview); $this->preflight = app(CrossEnvironmentPromotionPreflight::class)->build($this->preview);
$workspace = $this->workspace(); $workspace = $this->workspace();
$user = auth()->user(); $user = auth()->user();
if ($workspace instanceof Workspace && $user instanceof User) { if ($workspace instanceof Workspace && $user instanceof User) {
app(WorkspaceAuditLogger::class)->logCrossTenantPromotionPreflightGenerated( app(WorkspaceAuditLogger::class)->logCrossEnvironmentPromotionPreflightGenerated(
workspace: $workspace, workspace: $workspace,
sourceTenant: $selection->sourceTenant, sourceEnvironment: $selection->sourceEnvironment,
targetTenant: $selection->targetTenant, targetEnvironment: $selection->targetEnvironment,
preflight: $this->preflight, preflight: $this->preflight,
actor: $user, actor: $user,
); );
@ -323,7 +323,7 @@ public function executePromotion(): void
$selection = $this->compareSelection(); $selection = $this->compareSelection();
$user = auth()->user(); $user = auth()->user();
if (! $selection instanceof CrossTenantCompareSelection || ! $user instanceof User) { if (! $selection instanceof CrossEnvironmentCompareSelection || ! $user instanceof User) {
Notification::make() Notification::make()
->title('Promotion execution unavailable') ->title('Promotion execution unavailable')
->body('Refresh the compare selection before executing promotion.') ->body('Refresh the compare selection before executing promotion.')
@ -334,7 +334,7 @@ public function executePromotion(): void
} }
try { try {
$result = app(CrossTenantPromotionExecutionService::class)->start( $result = app(CrossEnvironmentPromotionExecutionService::class)->start(
selection: $selection, selection: $selection,
preview: $this->preview, preview: $this->preview,
preflight: $this->preflight, preflight: $this->preflight,
@ -376,8 +376,8 @@ public function executePromotion(): void
public function clearSelectionUrl(): string public function clearSelectionUrl(): string
{ {
return static::getUrl($this->routeParameters([ return static::getUrl($this->routeParameters([
self::SOURCE_TENANT_QUERY_KEY => null, self::SOURCE_ENVIRONMENT_QUERY_KEY => null,
self::TARGET_TENANT_QUERY_KEY => null, self::TARGET_ENVIRONMENT_QUERY_KEY => null,
self::POLICY_TYPE_QUERY_KEY => null, self::POLICY_TYPE_QUERY_KEY => null,
]), panel: 'admin'); ]), panel: 'admin');
} }
@ -388,18 +388,18 @@ public function selectionUrl(): string
} }
public static function launchUrl( public static function launchUrl(
?ManagedEnvironment $sourceTenant = null, ?ManagedEnvironment $sourceEnvironment = null,
?ManagedEnvironment $targetTenant = null, ?ManagedEnvironment $targetEnvironment = null,
?CanonicalNavigationContext $navigationContext = null, ?CanonicalNavigationContext $navigationContext = null,
): string { ): string {
$parameters = []; $parameters = [];
if ($sourceTenant instanceof ManagedEnvironment) { if ($sourceEnvironment instanceof ManagedEnvironment) {
$parameters[self::SOURCE_TENANT_QUERY_KEY] = (int) $sourceTenant->getKey(); $parameters[self::SOURCE_ENVIRONMENT_QUERY_KEY] = (int) $sourceEnvironment->getKey();
} }
if ($targetTenant instanceof ManagedEnvironment) { if ($targetEnvironment instanceof ManagedEnvironment) {
$parameters[self::TARGET_TENANT_QUERY_KEY] = (int) $targetTenant->getKey(); $parameters[self::TARGET_ENVIRONMENT_QUERY_KEY] = (int) $targetEnvironment->getKey();
} }
if ($navigationContext instanceof CanonicalNavigationContext) { if ($navigationContext instanceof CanonicalNavigationContext) {
@ -411,8 +411,8 @@ public static function launchUrl(
public function hasActiveSelection(): bool public function hasActiveSelection(): bool
{ {
return $this->sourceTenantId !== null return $this->sourceEnvironmentId !== null
|| $this->targetTenantId !== null || $this->targetEnvironmentId !== null
|| $this->selectedPolicyTypes !== []; || $this->selectedPolicyTypes !== [];
} }
@ -438,26 +438,26 @@ public function reasonLabel(string $reasonCode): string
return Str::headline(str_replace('_', ' ', $reasonCode)); return Str::headline(str_replace('_', ' ', $reasonCode));
} }
public function sourceTenantUrl(): ?string public function sourceEnvironmentUrl(): ?string
{ {
$tenant = $this->selectedSourceTenant(); $environment = $this->selectedSourceEnvironment();
if (! $tenant instanceof ManagedEnvironment) { if (! $environment instanceof ManagedEnvironment) {
return null; return null;
} }
return ManagedEnvironmentLinks::viewUrl($tenant); return ManagedEnvironmentLinks::viewUrl($environment);
} }
public function targetTenantUrl(): ?string public function targetEnvironmentUrl(): ?string
{ {
$tenant = $this->selectedTargetTenant(); $environment = $this->selectedTargetEnvironment();
if (! $tenant instanceof ManagedEnvironment) { if (! $environment instanceof ManagedEnvironment) {
return null; return null;
} }
return ManagedEnvironmentLinks::viewUrl($tenant); return ManagedEnvironmentLinks::viewUrl($environment);
} }
/** /**
@ -466,16 +466,16 @@ public function targetTenantUrl(): ?string
private function formState(): array private function formState(): array
{ {
return [ return [
'sourceTenantId' => $this->sourceTenantId, 'sourceEnvironmentId' => $this->sourceEnvironmentId,
'targetTenantId' => $this->targetTenantId, 'targetEnvironmentId' => $this->targetEnvironmentId,
'selectedPolicyTypes' => $this->selectedPolicyTypes, 'selectedPolicyTypes' => $this->selectedPolicyTypes,
]; ];
} }
private function hydrateSelectionFromRequest(): void private function hydrateSelectionFromRequest(): void
{ {
$this->sourceTenantId = $this->normalizeTenantIdentifier(request()->query(self::SOURCE_TENANT_QUERY_KEY)); $this->sourceEnvironmentId = $this->normalizeEnvironmentIdentifier(request()->query(self::SOURCE_ENVIRONMENT_QUERY_KEY));
$this->targetTenantId = $this->normalizeTenantIdentifier(request()->query(self::TARGET_TENANT_QUERY_KEY)); $this->targetEnvironmentId = $this->normalizeEnvironmentIdentifier(request()->query(self::TARGET_ENVIRONMENT_QUERY_KEY));
$this->selectedPolicyTypes = $this->normalizePolicyTypes(request()->query(self::POLICY_TYPE_QUERY_KEY, [])); $this->selectedPolicyTypes = $this->normalizePolicyTypes(request()->query(self::POLICY_TYPE_QUERY_KEY, []));
} }
@ -487,11 +487,11 @@ private function refreshPreview(): void
$selection = $this->compareSelection(); $selection = $this->compareSelection();
if (! $selection instanceof CrossTenantCompareSelection) { if (! $selection instanceof CrossEnvironmentCompareSelection) {
return; return;
} }
$this->preview = app(CrossTenantComparePreviewBuilder::class)->build($selection); $this->preview = app(CrossEnvironmentComparePreviewBuilder::class)->build($selection);
} }
private function authorizePageAccess(): void private function authorizePageAccess(): void
@ -554,61 +554,61 @@ private function authorizePromotionExecution(): void
abort(403); abort(403);
} }
$targetTenant = $this->selectedTargetTenant(); $targetEnvironment = $this->selectedTargetEnvironment();
if (! $targetTenant instanceof ManagedEnvironment) { if (! $targetEnvironment instanceof ManagedEnvironment) {
abort(404); abort(404);
} }
/** @var CapabilityResolver $resolver */ /** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class); $resolver = app(CapabilityResolver::class);
if (! $resolver->can($user, $targetTenant, Capabilities::TENANT_MANAGE)) { if (! $resolver->can($user, $targetEnvironment, Capabilities::TENANT_MANAGE)) {
abort(403); abort(403);
} }
} }
private function compareSelection(): ?CrossTenantCompareSelection private function compareSelection(): ?CrossEnvironmentCompareSelection
{ {
$sourceTenant = $this->selectedSourceTenant(); $sourceEnvironment = $this->selectedSourceEnvironment();
$targetTenant = $this->selectedTargetTenant(); $targetEnvironment = $this->selectedTargetEnvironment();
if (! $sourceTenant instanceof ManagedEnvironment || ! $targetTenant instanceof ManagedEnvironment) { if (! $sourceEnvironment instanceof ManagedEnvironment || ! $targetEnvironment instanceof ManagedEnvironment) {
return null; return null;
} }
if ((int) $sourceTenant->getKey() === (int) $targetTenant->getKey()) { if ((int) $sourceEnvironment->getKey() === (int) $targetEnvironment->getKey()) {
$this->selectionMessage = 'Choose two different tenants.'; $this->selectionMessage = 'Choose two different environments.';
return null; return null;
} }
return new CrossTenantCompareSelection( return new CrossEnvironmentCompareSelection(
sourceTenant: $sourceTenant, sourceEnvironment: $sourceEnvironment,
targetTenant: $targetTenant, targetEnvironment: $targetEnvironment,
policyTypes: $this->selectedPolicyTypes, policyTypes: $this->selectedPolicyTypes,
); );
} }
private function selectedSourceTenant(): ?ManagedEnvironment private function selectedSourceEnvironment(): ?ManagedEnvironment
{ {
if ($this->sourceTenantId === null) { if ($this->sourceEnvironmentId === null) {
return null; return null;
} }
return $this->resolveAuthorizedTenant($this->sourceTenantId); return $this->resolveAuthorizedEnvironment($this->sourceEnvironmentId);
} }
private function selectedTargetTenant(): ?ManagedEnvironment private function selectedTargetEnvironment(): ?ManagedEnvironment
{ {
if ($this->targetTenantId === null) { if ($this->targetEnvironmentId === null) {
return null; return null;
} }
return $this->resolveAuthorizedTenant($this->targetTenantId); return $this->resolveAuthorizedEnvironment($this->targetEnvironmentId);
} }
private function resolveAuthorizedTenant(string $tenantId): ManagedEnvironment private function resolveAuthorizedEnvironment(string $environmentId): ManagedEnvironment
{ {
$workspace = $this->workspace(); $workspace = $this->workspace();
$user = auth()->user(); $user = auth()->user();
@ -617,29 +617,29 @@ private function resolveAuthorizedTenant(string $tenantId): ManagedEnvironment
abort(404); abort(404);
} }
$tenant = ManagedEnvironment::query() $environment = ManagedEnvironment::query()
->where('workspace_id', (int) $workspace->getKey()) ->where('workspace_id', (int) $workspace->getKey())
->whereKey((int) $tenantId) ->whereKey((int) $environmentId)
->first(); ->first();
if (! $tenant instanceof ManagedEnvironment) { if (! $environment instanceof ManagedEnvironment) {
abort(404); abort(404);
} }
/** @var CapabilityResolver $resolver */ /** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class); $resolver = app(CapabilityResolver::class);
if (! $user->canAccessTenant($tenant) || ! $resolver->can($user, $tenant, Capabilities::TENANT_VIEW)) { if (! $user->canAccessTenant($environment) || ! $resolver->can($user, $environment, Capabilities::TENANT_VIEW)) {
abort(404); abort(404);
} }
return $tenant; return $environment;
} }
/** /**
* @return array<string, string> * @return array<string, string>
*/ */
private function tenantOptions(): array private function environmentOptions(): array
{ {
$workspace = $this->workspace(); $workspace = $this->workspace();
$user = auth()->user(); $user = auth()->user();
@ -651,17 +651,17 @@ private function tenantOptions(): array
/** @var CapabilityResolver $resolver */ /** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class); $resolver = app(CapabilityResolver::class);
$tenants = $user->accessibleManagedEnvironmentsQuery((int) $workspace->getKey()) $environments = $user->accessibleManagedEnvironmentsQuery((int) $workspace->getKey())
->select('managed_environments.*') ->select('managed_environments.*')
->orderBy('managed_environments.name') ->orderBy('managed_environments.name')
->get(); ->get();
$resolver->primeMemberships($user, $tenants->modelKeys()); $resolver->primeMemberships($user, $environments->modelKeys());
return $tenants return $environments
->filter(fn (ManagedEnvironment $tenant): bool => $resolver->can($user, $tenant, Capabilities::TENANT_VIEW)) ->filter(fn (ManagedEnvironment $environment): bool => $resolver->can($user, $environment, Capabilities::TENANT_VIEW))
->mapWithKeys(fn (ManagedEnvironment $tenant): array => [ ->mapWithKeys(fn (ManagedEnvironment $environment): array => [
(string) $tenant->getKey() => (string) $tenant->name, (string) $environment->getKey() => (string) $environment->name,
]) ])
->all(); ->all();
} }
@ -671,14 +671,14 @@ private function tenantOptions(): array
*/ */
private function policyTypeOptions(): array private function policyTypeOptions(): array
{ {
$tenantIds = array_map(static fn (string $tenantId): int => (int) $tenantId, array_keys($this->tenantOptions())); $environmentIds = array_map(static fn (string $environmentId): int => (int) $environmentId, array_keys($this->environmentOptions()));
if ($tenantIds === []) { if ($environmentIds === []) {
return []; return [];
} }
return InventoryItem::query() return InventoryItem::query()
->whereIn('managed_environment_id', $tenantIds) ->whereIn('managed_environment_id', $environmentIds)
->whereNotNull('policy_type') ->whereNotNull('policy_type')
->where('policy_type', '!=', '') ->where('policy_type', '!=', '')
->distinct() ->distinct()
@ -697,7 +697,7 @@ private function preflightDisabledReason(): ?string
} }
if (! is_array($this->preview)) { if (! is_array($this->preview)) {
return 'Select an authorized source and target tenant to generate a promotion preflight.'; return 'Select an authorized source and target environment to generate a promotion preflight.';
} }
if ((int) data_get($this->preview, 'summary.total', 0) === 0) { if ((int) data_get($this->preview, 'summary.total', 0) === 0) {
@ -737,14 +737,14 @@ private function executePromotionDisabledReason(): ?string
return 'You need workspace baseline manage access to execute promotion.'; return 'You need workspace baseline manage access to execute promotion.';
} }
$targetTenant = $this->selectedTargetTenant(); $targetEnvironment = $this->selectedTargetEnvironment();
if ($targetTenant instanceof ManagedEnvironment) { if ($targetEnvironment instanceof ManagedEnvironment) {
/** @var CapabilityResolver $resolver */ /** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class); $resolver = app(CapabilityResolver::class);
if (! $resolver->can($user, $targetTenant, Capabilities::TENANT_MANAGE)) { if (! $resolver->can($user, $targetEnvironment, Capabilities::TENANT_MANAGE)) {
return 'You need target tenant manage access to execute promotion.'; return 'You need target environment manage access to execute promotion.';
} }
} }
} }
@ -760,13 +760,13 @@ private function executePromotionConfirmationDescription(): string
$manualMappingRequired = (int) data_get($this->preflight, 'summary.manual_mapping_required', 0); $manualMappingRequired = (int) data_get($this->preflight, 'summary.manual_mapping_required', 0);
$excluded = $blocked + $manualMappingRequired; $excluded = $blocked + $manualMappingRequired;
$sourceTenantName = $selection?->sourceTenant->name ?? 'Source tenant'; $sourceEnvironmentName = $selection?->sourceEnvironment->name ?? 'Source environment';
$targetTenantName = $selection?->targetTenant->name ?? 'Target tenant'; $targetEnvironmentName = $selection?->targetEnvironment->name ?? 'Target environment';
return sprintf( return sprintf(
'Queue one promotion run from %s to %s for %d ready governed subject%s. %d subject%s remain excluded on the compare page.', 'Queue one promotion run from %s to %s for %d ready governed subject%s. %d subject%s remain excluded on the compare page.',
$sourceTenantName, $sourceEnvironmentName,
$targetTenantName, $targetEnvironmentName,
$ready, $ready,
$ready === 1 ? '' : 's', $ready === 1 ? '' : 's',
$excluded, $excluded,
@ -777,7 +777,7 @@ private function executePromotionConfirmationDescription(): string
/** /**
* @param mixed $value * @param mixed $value
*/ */
private function normalizeTenantIdentifier(mixed $value): ?string private function normalizeEnvironmentIdentifier(mixed $value): ?string
{ {
if (! is_string($value) && ! is_int($value)) { if (! is_string($value) && ! is_int($value)) {
return null; return null;
@ -815,8 +815,8 @@ private function normalizePolicyTypes(mixed $value): array
private function routeParameters(array $overrides = []): array private function routeParameters(array $overrides = []): array
{ {
$parameters = [ $parameters = [
self::SOURCE_TENANT_QUERY_KEY => $this->sourceTenantId, self::SOURCE_ENVIRONMENT_QUERY_KEY => $this->sourceEnvironmentId,
self::TARGET_TENANT_QUERY_KEY => $this->targetTenantId, self::TARGET_ENVIRONMENT_QUERY_KEY => $this->targetEnvironmentId,
self::POLICY_TYPE_QUERY_KEY => $this->selectedPolicyTypes, self::POLICY_TYPE_QUERY_KEY => $this->selectedPolicyTypes,
]; ];

View File

@ -5,9 +5,9 @@
namespace App\Filament\Pages; namespace App\Filament\Pages;
use App\Filament\Pages\Governance\GovernanceInbox; use App\Filament\Pages\Governance\GovernanceInbox;
use App\Filament\Widgets\Tenant\TenantTriageArrivalContinuity; use App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentTriageArrivalContinuity;
use App\Filament\Widgets\Dashboard\TenantDashboardContextChips; use App\Filament\Widgets\Dashboard\EnvironmentDashboardContextChips;
use App\Filament\Widgets\Dashboard\TenantDashboardOverview; use App\Filament\Widgets\Dashboard\EnvironmentDashboardOverview;
use App\Models\SupportRequest; use App\Models\SupportRequest;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\User; use App\Models\User;
@ -21,8 +21,8 @@
use App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder; use App\Support\SupportDiagnostics\SupportDiagnosticBundleBuilder;
use App\Support\SupportRequests\ExternalSupportDeskHandoffService; use App\Support\SupportRequests\ExternalSupportDeskHandoffService;
use App\Support\SupportRequests\SupportRequestSubmissionService; use App\Support\SupportRequests\SupportRequestSubmissionService;
use App\Support\TenantDashboard\TenantDashboardSummary; use App\Support\EnvironmentDashboard\EnvironmentDashboardSummary;
use App\Support\TenantDashboard\TenantDashboardSummaryBuilder; use App\Support\EnvironmentDashboard\EnvironmentDashboardSummaryBuilder;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Facades\Filament; use Filament\Facades\Filament;
@ -43,7 +43,7 @@
use App\Filament\Widgets\Dashboard\DashboardKpis; use App\Filament\Widgets\Dashboard\DashboardKpis;
class TenantDashboard extends Dashboard class EnvironmentDashboard extends Dashboard
{ {
protected Width|string|null $maxContentWidth = Width::Full; protected Width|string|null $maxContentWidth = Width::Full;
@ -52,7 +52,7 @@ class TenantDashboard extends Dashboard
*/ */
public array $supportDiagnosticsAuditKeys = []; public array $supportDiagnosticsAuditKeys = [];
private ?TenantDashboardSummary $dashboardSummary = null; private ?EnvironmentDashboardSummary $dashboardSummary = null;
public static function getNavigationLabel(): string public static function getNavigationLabel(): string
{ {
@ -69,7 +69,7 @@ public function getTitle(): string | Htmlable
$summary = $this->dashboardSummary(); $summary = $this->dashboardSummary();
if (! $summary instanceof TenantDashboardSummary) { if (! $summary instanceof EnvironmentDashboardSummary) {
return (string) $tenant->name; return (string) $tenant->name;
} }
@ -112,7 +112,7 @@ public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?
protected function getHeaderWidgets(): array protected function getHeaderWidgets(): array
{ {
return [ return [
TenantDashboardContextChips::class, EnvironmentDashboardContextChips::class,
]; ];
} }
@ -127,9 +127,9 @@ public function getHeaderWidgetsColumns(): int|array
public function getWidgets(): array public function getWidgets(): array
{ {
return [ return [
TenantTriageArrivalContinuity::class, ManagedEnvironmentTriageArrivalContinuity::class,
DashboardKpis::class, DashboardKpis::class,
TenantDashboardOverview::class, EnvironmentDashboardOverview::class,
]; ];
} }
@ -204,7 +204,7 @@ private function primaryFollowUpHeaderPayload(): ?array
{ {
$summary = $this->dashboardSummary(); $summary = $this->dashboardSummary();
if (! $summary instanceof TenantDashboardSummary) { if (! $summary instanceof EnvironmentDashboardSummary) {
return null; return null;
} }
@ -222,7 +222,7 @@ private function secondaryHeaderPayload(): ?array
{ {
$summary = $this->dashboardSummary(); $summary = $this->dashboardSummary();
if (! $summary instanceof TenantDashboardSummary) { if (! $summary instanceof EnvironmentDashboardSummary) {
return null; return null;
} }
@ -304,9 +304,9 @@ private function summaryHeaderAction(string $name, array $payload, string $color
return $action; return $action;
} }
private function dashboardSummary(): ?TenantDashboardSummary private function dashboardSummary(): ?EnvironmentDashboardSummary
{ {
if ($this->dashboardSummary instanceof TenantDashboardSummary) { if ($this->dashboardSummary instanceof EnvironmentDashboardSummary) {
return $this->dashboardSummary; return $this->dashboardSummary;
} }
@ -317,7 +317,7 @@ private function dashboardSummary(): ?TenantDashboardSummary
return null; return null;
} }
$this->dashboardSummary = app(TenantDashboardSummaryBuilder::class)->build($tenant, $user); $this->dashboardSummary = app(EnvironmentDashboardSummaryBuilder::class)->build($tenant, $user);
return $this->dashboardSummary; return $this->dashboardSummary;
} }

View File

@ -6,8 +6,8 @@
use App\Filament\Concerns\ResolvesPanelTenantContext; use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Models\User; use App\Models\User;
use App\Services\Auth\TenantDiagnosticsService; use App\Services\Auth\ManagedEnvironmentDiagnosticsService;
use App\Services\Auth\TenantMembershipManager; use App\Services\Auth\ManagedEnvironmentMembershipManager;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\Rbac\UiTooltips; use App\Support\Rbac\UiTooltips;
@ -17,7 +17,7 @@
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Pages\Page; use Filament\Pages\Page;
class TenantDiagnostics extends Page class EnvironmentDiagnostics extends Page
{ {
use ResolvesPanelTenantContext; use ResolvesPanelTenantContext;
@ -25,7 +25,7 @@ class TenantDiagnostics extends Page
protected static ?string $slug = 'diagnostics'; protected static ?string $slug = 'diagnostics';
protected string $view = 'filament.pages.tenant-diagnostics'; protected string $view = 'filament.pages.environment-diagnostics';
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{ {
@ -44,14 +44,14 @@ public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
public function mount(): void public function mount(): void
{ {
$tenant = static::resolveTenantContextForCurrentPanelOrFail(); $tenant = static::resolveTenantContextForCurrentPanelOrFail();
$this->missingOwner = app(TenantDiagnosticsService::class)->tenantHasNoOwners($tenant); $this->missingOwner = app(ManagedEnvironmentDiagnosticsService::class)->tenantHasNoOwners($tenant);
$user = auth()->user(); $user = auth()->user();
if (! $user instanceof User) { if (! $user instanceof User) {
abort(403, 'Not allowed'); abort(403, 'Not allowed');
} }
$this->hasDuplicateMembershipsForCurrentUser = app(TenantDiagnosticsService::class) $this->hasDuplicateMembershipsForCurrentUser = app(ManagedEnvironmentDiagnosticsService::class)
->userHasDuplicateMemberships($tenant, $user); ->userHasDuplicateMemberships($tenant, $user);
} }
@ -96,7 +96,7 @@ public function bootstrapOwner(): void
abort(403, 'Not allowed'); abort(403, 'Not allowed');
} }
app(TenantMembershipManager::class)->grantScope($tenant, $user, $user, sourceRef: 'diagnostic'); app(ManagedEnvironmentMembershipManager::class)->grantScope($tenant, $user, $user, sourceRef: 'diagnostic');
$this->mount(); $this->mount();
} }
@ -110,7 +110,7 @@ public function mergeDuplicateMemberships(): void
abort(403, 'Not allowed'); abort(403, 'Not allowed');
} }
app(TenantDiagnosticsService::class)->mergeDuplicateMembershipsForUser($tenant, $user, $user); app(ManagedEnvironmentDiagnosticsService::class)->mergeDuplicateMembershipsForUser($tenant, $user, $user);
$this->mount(); $this->mount();
} }

View File

@ -7,7 +7,7 @@
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\User; use App\Models\User;
use App\Models\WorkspaceMembership; use App\Models\WorkspaceMembership;
use App\Services\Intune\TenantRequiredPermissionsViewModelBuilder; use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Support\Badges\BadgeDomain; use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer; use App\Support\Badges\BadgeRenderer;
use App\Support\Filament\TablePaginationProfiles; use App\Support\Filament\TablePaginationProfiles;
@ -29,7 +29,7 @@
use Livewire\Attributes\Locked; use Livewire\Attributes\Locked;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class TenantRequiredPermissions extends Page implements HasTable class EnvironmentRequiredPermissions extends Page implements HasTable
{ {
use InteractsWithTable; use InteractsWithTable;
@ -37,11 +37,11 @@ class TenantRequiredPermissions extends Page implements HasTable
protected static bool $shouldRegisterNavigation = false; protected static bool $shouldRegisterNavigation = false;
protected static ?string $slug = 'workspaces/{workspace}/environments/{tenant}/required-permissions'; protected static ?string $slug = 'workspaces/{workspace}/environments/{environment}/required-permissions';
protected static ?string $title = 'Required permissions'; protected static ?string $title = 'Required permissions';
protected string $view = 'filament.pages.tenant-required-permissions'; protected string $view = 'filament.pages.environment-required-permissions';
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{ {
@ -73,9 +73,9 @@ public function currentTenant(): ?ManagedEnvironment
return $this->trustedScopedTenant(); return $this->trustedScopedTenant();
} }
public function mount(ManagedEnvironment|string|null $tenant = null): void public function mount(ManagedEnvironment|string|null $environment = null): void
{ {
$tenant = static::resolveScopedTenant($tenant); $tenant = static::resolveScopedTenant($environment);
if (! $tenant instanceof ManagedEnvironment || ! static::hasScopedTenantAccess($tenant)) { if (! $tenant instanceof ManagedEnvironment || ! static::hasScopedTenantAccess($tenant)) {
abort(404); abort(404);
@ -151,10 +151,10 @@ public function table(Table $table): Table
TextColumn::make('status') TextColumn::make('status')
->label('Status') ->label('Status')
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantPermissionStatus)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::ManagedEnvironmentPermissionStatus))
->color(BadgeRenderer::color(BadgeDomain::TenantPermissionStatus)) ->color(BadgeRenderer::color(BadgeDomain::ManagedEnvironmentPermissionStatus))
->icon(BadgeRenderer::icon(BadgeDomain::TenantPermissionStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::ManagedEnvironmentPermissionStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantPermissionStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::ManagedEnvironmentPermissionStatus))
->sortable(), ->sortable(),
TextColumn::make('features_label') TextColumn::make('features_label')
->label('Features') ->label('Features')
@ -235,7 +235,7 @@ protected static function resolveScopedTenant(ManagedEnvironment|string|null $te
->first(); ->first();
} }
$routeTenant = request()->route('tenant'); $routeTenant = request()->route('environment') ?? request()->route('tenant');
if ($routeTenant instanceof ManagedEnvironment) { if ($routeTenant instanceof ManagedEnvironment) {
return $routeTenant; return $routeTenant;
@ -333,7 +333,7 @@ private function trustedScopedTenant(): ?ManagedEnvironment
*/ */
private function filterState(array $filters = [], ?string $search = null): array private function filterState(array $filters = [], ?string $search = null): array
{ {
return TenantRequiredPermissionsViewModelBuilder::normalizeFilterState([ return ManagedEnvironmentRequiredPermissionsViewModelBuilder::normalizeFilterState([
'status' => $filters['status']['value'] ?? data_get($this->tableFilters, 'status.value'), 'status' => $filters['status']['value'] ?? data_get($this->tableFilters, 'status.value'),
'type' => $filters['type']['value'] ?? data_get($this->tableFilters, 'type.value'), 'type' => $filters['type']['value'] ?? data_get($this->tableFilters, 'type.value'),
'features' => $filters['features']['values'] ?? data_get($this->tableFilters, 'features.values', []), 'features' => $filters['features']['values'] ?? data_get($this->tableFilters, 'features.values', []),
@ -359,7 +359,7 @@ private function viewModelForState(array $state): array
return $this->cachedViewModel; return $this->cachedViewModel;
} }
$builder = app(TenantRequiredPermissionsViewModelBuilder::class); $builder = app(ManagedEnvironmentRequiredPermissionsViewModelBuilder::class);
$this->cachedViewModelStateKey = $stateKey ?: null; $this->cachedViewModelStateKey = $stateKey ?: null;
$this->cachedViewModel = $builder->build($tenant, $state); $this->cachedViewModel = $builder->build($tenant, $state);
@ -514,7 +514,7 @@ private function seedTableStateFromQuery(): void
$queryFeatures = request()->query('features', []); $queryFeatures = request()->query('features', []);
$state = TenantRequiredPermissionsViewModelBuilder::normalizeFilterState([ $state = ManagedEnvironmentRequiredPermissionsViewModelBuilder::normalizeFilterState([
'status' => request()->query('status', 'missing'), 'status' => request()->query('status', 'missing'),
'type' => request()->query('type', 'all'), 'type' => request()->query('type', 'all'),
'features' => is_array($queryFeatures) ? $queryFeatures : [], 'features' => is_array($queryFeatures) ? $queryFeatures : [],

View File

@ -288,10 +288,10 @@ public function emptyState(): array
'title' => 'No visible assigned findings right now', 'title' => 'No visible assigned findings right now',
'body' => 'Nothing currently assigned to you needs attention across the visible environment scope. Choose an environment 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', 'icon' => 'heroicon-o-clipboard-document-check',
'action_name' => 'choose_tenant_empty', 'action_name' => 'choose_environment_empty',
'action_label' => 'Choose an environment', 'action_label' => 'Choose an environment',
'action_kind' => 'url', 'action_kind' => 'url',
'action_url' => route('filament.admin.pages.choose-tenant'), 'action_url' => route('filament.admin.pages.choose-environment'),
]; ];
} }

View File

@ -11,7 +11,7 @@
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Auth\CapabilityResolver; use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver; use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\TenantReviews\TenantReviewRegisterService; use App\Services\EnvironmentReviews\EnvironmentReviewRegisterService;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\GovernanceInbox\GovernanceInboxSectionBuilder; use App\Support\GovernanceInbox\GovernanceInboxSectionBuilder;
use App\Support\Navigation\CanonicalNavigationContext; use App\Support\Navigation\CanonicalNavigationContext;
@ -333,7 +333,7 @@ private function reviewTenants(): array
return $this->reviewTenants = []; return $this->reviewTenants = [];
} }
$service = app(TenantReviewRegisterService::class); $service = app(EnvironmentReviewRegisterService::class);
if (! $service->canAccessWorkspace($user, $workspace)) { if (! $service->canAccessWorkspace($user, $workspace)) {
return $this->reviewTenants = []; return $this->reviewTenants = [];

View File

@ -7,11 +7,11 @@
use App\Filament\Resources\EvidenceSnapshotResource; use App\Filament\Resources\EvidenceSnapshotResource;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Support\Badges\BadgeCatalog; use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain; use App\Support\Badges\BadgeDomain;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -398,13 +398,13 @@ private function latestAccessibleSnapshots(): Collection
*/ */
private function currentReviewTenantIds(Collection $snapshots): array private function currentReviewTenantIds(Collection $snapshots): array
{ {
return TenantReview::query() return EnvironmentReview::query()
->where('workspace_id', $this->workspaceId()) ->where('workspace_id', $this->workspaceId())
->whereIn('managed_environment_id', $snapshots->pluck('managed_environment_id')->map(static fn (mixed $tenantId): int => (int) $tenantId)->all()) ->whereIn('managed_environment_id', $snapshots->pluck('managed_environment_id')->map(static fn (mixed $tenantId): int => (int) $tenantId)->all())
->whereIn('status', [ ->whereIn('status', [
TenantReviewStatus::Draft->value, EnvironmentReviewStatus::Draft->value,
TenantReviewStatus::Ready->value, EnvironmentReviewStatus::Ready->value,
TenantReviewStatus::Published->value, EnvironmentReviewStatus::Published->value,
]) ])
->pluck('managed_environment_id') ->pluck('managed_environment_id')
->mapWithKeys(static fn (mixed $tenantId): array => [(int) $tenantId => true]) ->mapWithKeys(static fn (mixed $tenantId): array => [(int) $tenantId => true])

View File

@ -93,6 +93,6 @@ public function createWorkspace(array $data): void
->success() ->success()
->send(); ->send();
$this->redirect(ChooseTenant::getUrl()); $this->redirect(ChooseEnvironment::getUrl());
} }
} }

View File

@ -4,16 +4,16 @@
namespace App\Filament\Pages\Reviews; namespace App\Filament\Pages\Reviews;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\FindingException; use App\Models\FindingException;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\TenantReviews\TenantReviewRegisterService; use App\Services\EnvironmentReviews\EnvironmentReviewRegisterService;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Findings\FindingOutcomeSemantics; use App\Support\Findings\FindingOutcomeSemantics;
@ -21,7 +21,7 @@
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1; use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use App\Support\Navigation\CanonicalNavigationContext; use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\ReviewPackStatus; use App\Support\ReviewPackStatus;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -236,7 +236,7 @@ public function authorizedTenants(): array
return $this->authorizedTenants = []; return $this->authorizedTenants = [];
} }
return $this->authorizedTenants = app(TenantReviewRegisterService::class)->authorizedTenants($user, $workspace); return $this->authorizedTenants = app(EnvironmentReviewRegisterService::class)->authorizedTenants($user, $workspace);
} }
private function authorizePageAccess(): void private function authorizePageAccess(): void
@ -252,7 +252,7 @@ private function authorizePageAccess(): void
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
$service = app(TenantReviewRegisterService::class); $service = app(EnvironmentReviewRegisterService::class);
if (! $service->canAccessWorkspace($user, $workspace)) { if (! $service->canAccessWorkspace($user, $workspace)) {
throw new NotFoundHttpException; throw new NotFoundHttpException;
@ -300,7 +300,7 @@ private function workspaceQuery(): Builder
return ManagedEnvironment::query()->whereRaw('1 = 0'); return ManagedEnvironment::query()->whereRaw('1 = 0');
} }
return app(TenantReviewRegisterService::class)->customerWorkspaceTenantQuery($user, $workspace); return app(EnvironmentReviewRegisterService::class)->customerWorkspaceTenantQuery($user, $workspace);
} }
/** /**
@ -377,18 +377,18 @@ private function workspace(): ?Workspace
: null; : null;
} }
private function latestPublishedReview(ManagedEnvironment $tenant): ?TenantReview private function latestPublishedReview(ManagedEnvironment $tenant): ?EnvironmentReview
{ {
$review = $tenant->tenantReviews->first(); $review = $tenant->environmentReviews->first();
return $review instanceof TenantReview ? $review : null; return $review instanceof EnvironmentReview ? $review : null;
} }
private function latestReviewUrl(ManagedEnvironment $tenant): ?string private function latestReviewUrl(ManagedEnvironment $tenant): ?string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return null; return null;
} }
@ -404,7 +404,7 @@ private function latestReviewUrl(ManagedEnvironment $tenant): ?string
static fn (mixed $value): bool => $value !== null && $value !== '', static fn (mixed $value): bool => $value !== null && $value !== '',
); );
return $this->appendQuery(TenantReviewResource::tenantScopedUrl('view', ['record' => $review], $tenant), $query); return $this->appendQuery(EnvironmentReviewResource::tenantScopedUrl('view', ['record' => $review], $tenant), $query);
} }
private function latestPublishedAt(ManagedEnvironment $tenant): ?\Illuminate\Support\Carbon private function latestPublishedAt(ManagedEnvironment $tenant): ?\Illuminate\Support\Carbon
@ -416,8 +416,8 @@ private function reviewTruth(ManagedEnvironment $tenant): ?ArtifactTruthEnvelope
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
return $review instanceof TenantReview return $review instanceof EnvironmentReview
? app(ArtifactTruthPresenter::class)->forTenantReview($review) ? app(ArtifactTruthPresenter::class)->forEnvironmentReview($review)
: null; : null;
} }
@ -427,7 +427,7 @@ private function reviewOutcome(ManagedEnvironment $tenant): ?CompressedGovernanc
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
$truth = $this->reviewTruth($tenant); $truth = $this->reviewTruth($tenant);
if (! $review instanceof TenantReview || ! $truth instanceof ArtifactTruthEnvelope) { if (! $review instanceof EnvironmentReview || ! $truth instanceof ArtifactTruthEnvelope) {
return null; return null;
} }
@ -439,7 +439,7 @@ private function latestReviewStateLabel(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review'); return __('localization.review.no_published_review');
} }
@ -452,7 +452,7 @@ private function latestReviewStateColor(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return 'gray'; return 'gray';
} }
@ -481,7 +481,7 @@ private function reviewOutcomeDescription(ManagedEnvironment $tenant): ?string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available'); return __('localization.review.no_published_review_available');
} }
@ -524,7 +524,7 @@ private function governancePackageSummary(ManagedEnvironment $tenant): array
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return []; return [];
} }
@ -541,7 +541,7 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return [ return [
'state' => 'unavailable', 'state' => 'unavailable',
'label' => __('localization.review.governance_package_unavailable'), 'label' => __('localization.review.governance_package_unavailable'),
@ -553,8 +553,8 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
$user = auth()->user(); $user = auth()->user();
$limitations = is_array($review->controlInterpretation()['limitations'] ?? null) ? $review->controlInterpretation()['limitations'] : []; $limitations = is_array($review->controlInterpretation()['limitations'] ?? null) ? $review->controlInterpretation()['limitations'] : [];
$isPartialReview = in_array((string) $review->completeness_state, [ $isPartialReview = in_array((string) $review->completeness_state, [
TenantReviewCompletenessState::Partial->value, EnvironmentReviewCompletenessState::Partial->value,
TenantReviewCompletenessState::Stale->value, EnvironmentReviewCompletenessState::Stale->value,
], true) || $limitations !== []; ], true) || $limitations !== [];
if (! $pack instanceof ReviewPack) { if (! $pack instanceof ReviewPack) {
@ -652,7 +652,7 @@ private function controlReadinessDescription(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available'); return __('localization.review.no_published_review_available');
} }
@ -720,7 +720,7 @@ private function workspaceReviewNeedsAttention(ManagedEnvironment $tenant): bool
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return true; return true;
} }
@ -739,7 +739,7 @@ private function evidenceStatusState(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return 'pending'; return 'pending';
} }
@ -802,7 +802,7 @@ private function primaryControlSummary(ManagedEnvironment $tenant): ?array
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return null; return null;
} }
@ -818,7 +818,7 @@ private function primaryControlSummary(ManagedEnvironment $tenant): ?array
->first(); ->first();
} }
private function controlLimitationSummary(TenantReview $review): ?string private function controlLimitationSummary(EnvironmentReview $review): ?string
{ {
$counts = $review->controlInterpretationLimitationCounts(); $counts = $review->controlInterpretationLimitationCounts();
@ -842,7 +842,7 @@ private function findingSummary(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available'); return __('localization.review.no_published_review_available');
} }
@ -869,7 +869,7 @@ private function acceptedRiskSummary(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available'); return __('localization.review.no_published_review_available');
} }
@ -897,7 +897,7 @@ private function evidenceProofAvailability(ManagedEnvironment $tenant): string
{ {
$review = $this->latestPublishedReview($tenant); $review = $this->latestPublishedReview($tenant);
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
return __('localization.review.no_published_review_available'); return __('localization.review.no_published_review_available');
} }
@ -941,10 +941,10 @@ private function visibleInterpretationVersions(): array
return []; return [];
} }
return app(TenantReviewRegisterService::class) return app(EnvironmentReviewRegisterService::class)
->latestPublishedQuery($user, $workspace) ->latestPublishedQuery($user, $workspace)
->get() ->get()
->map(static fn (TenantReview $review): ?string => $review->controlInterpretationVersion()) ->map(static fn (EnvironmentReview $review): ?string => $review->controlInterpretationVersion())
->filter() ->filter()
->unique() ->unique()
->values() ->values()
@ -965,7 +965,7 @@ private function currentTenantFilterInterpretationVersion(): ?string
return null; return null;
} }
return $tenant->tenantReviews()->published() return $tenant->environmentReviews()->published()
->latest('published_at') ->latest('published_at')
->latest('generated_at') ->latest('generated_at')
->latest('id') ->latest('id')

View File

@ -4,13 +4,13 @@
namespace App\Filament\Pages\Reviews; namespace App\Filament\Pages\Reviews;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\ReviewPackService; use App\Services\ReviewPackService;
use App\Services\TenantReviews\TenantReviewRegisterService; use App\Services\EnvironmentReviews\EnvironmentReviewRegisterService;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeCatalog; use App\Support\Badges\BadgeCatalog;
use App\Support\Badges\BadgeDomain; use App\Support\Badges\BadgeDomain;
@ -19,7 +19,7 @@
use App\Support\Filament\CanonicalAdminTenantFilterState; use App\Support\Filament\CanonicalAdminTenantFilterState;
use App\Support\Filament\FilterPresets; use App\Support\Filament\FilterPresets;
use App\Support\Filament\TablePaginationProfiles; use App\Support\Filament\TablePaginationProfiles;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -112,15 +112,15 @@ public function table(Table $table): Table
->persistFiltersInSession() ->persistFiltersInSession()
->persistSearchInSession() ->persistSearchInSession()
->persistSortInSession() ->persistSortInSession()
->recordUrl(fn (TenantReview $record): string => TenantReviewResource::tenantScopedUrl('view', ['record' => $record], $record->tenant)) ->recordUrl(fn (EnvironmentReview $record): string => EnvironmentReviewResource::tenantScopedUrl('view', ['record' => $record], $record->tenant))
->columns([ ->columns([
TextColumn::make('tenant.name')->label('ManagedEnvironment')->searchable(), TextColumn::make('tenant.name')->label('ManagedEnvironment')->searchable(),
TextColumn::make('status') TextColumn::make('status')
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantReviewStatus)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EnvironmentReviewStatus))
->color(BadgeRenderer::color(BadgeDomain::TenantReviewStatus)) ->color(BadgeRenderer::color(BadgeDomain::EnvironmentReviewStatus))
->icon(BadgeRenderer::icon(BadgeDomain::TenantReviewStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantReviewStatus)), ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewStatus)),
TextColumn::make('outcome') TextColumn::make('outcome')
->label('Outcome') ->label('Outcome')
->badge() ->badge()
@ -154,7 +154,7 @@ public function table(Table $table): Table
]), ]),
SelectFilter::make('completeness_state') SelectFilter::make('completeness_state')
->label('Completeness') ->label('Completeness')
->options(BadgeCatalog::options(BadgeDomain::TenantReviewCompleteness, TenantReviewCompletenessState::values())), ->options(BadgeCatalog::options(BadgeDomain::EnvironmentReviewCompleteness, EnvironmentReviewCompletenessState::values())),
SelectFilter::make('published_state') SelectFilter::make('published_state')
->label('Published state') ->label('Published state')
->options([ ->options([
@ -174,11 +174,11 @@ public function table(Table $table): Table
Action::make('export_executive_pack') Action::make('export_executive_pack')
->label('Export executive pack') ->label('Export executive pack')
->icon('heroicon-o-arrow-down-tray') ->icon('heroicon-o-arrow-down-tray')
->visible(fn (TenantReview $record): bool => auth()->user() instanceof User ->visible(fn (EnvironmentReview $record): bool => auth()->user() instanceof User
&& auth()->user()->can(Capabilities::TENANT_REVIEW_MANAGE, $record->tenant) && auth()->user()->can(Capabilities::ENVIRONMENT_REVIEW_MANAGE, $record->tenant)
&& in_array($record->status, ['ready', 'published'], true)) && in_array($record->status, ['ready', 'published'], true))
->disabled(fn (TenantReview $record): bool => (bool) (app(ReviewPackService::class)->reviewPackGenerationDecisionForTenant($record->tenant)['is_blocked'] ?? false)) ->disabled(fn (EnvironmentReview $record): bool => (bool) (app(ReviewPackService::class)->reviewPackGenerationDecisionForTenant($record->tenant)['is_blocked'] ?? false))
->tooltip(function (TenantReview $record): ?string { ->tooltip(function (EnvironmentReview $record): ?string {
$decision = app(ReviewPackService::class)->reviewPackGenerationDecisionForTenant($record->tenant); $decision = app(ReviewPackService::class)->reviewPackGenerationDecisionForTenant($record->tenant);
if ((bool) ($decision['is_blocked'] ?? false)) { if ((bool) ($decision['is_blocked'] ?? false)) {
@ -195,7 +195,7 @@ public function table(Table $table): Table
return null; return null;
}) })
->action(fn (TenantReview $record): mixed => TenantReviewResource::executeExport($record)), ->action(fn (EnvironmentReview $record): mixed => EnvironmentReviewResource::executeExport($record)),
]) ])
->bulkActions([]) ->bulkActions([])
->emptyStateHeading('No review records match this view') ->emptyStateHeading('No review records match this view')
@ -225,7 +225,7 @@ public function authorizedTenants(): array
return $this->authorizedTenants = []; return $this->authorizedTenants = [];
} }
return $this->authorizedTenants = app(TenantReviewRegisterService::class)->authorizedTenants($user, $workspace); return $this->authorizedTenants = app(EnvironmentReviewRegisterService::class)->authorizedTenants($user, $workspace);
} }
private function authorizePageAccess(): void private function authorizePageAccess(): void
@ -241,7 +241,7 @@ private function authorizePageAccess(): void
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
$service = app(TenantReviewRegisterService::class); $service = app(EnvironmentReviewRegisterService::class);
if (! $service->canAccessWorkspace($user, $workspace)) { if (! $service->canAccessWorkspace($user, $workspace)) {
throw new NotFoundHttpException; throw new NotFoundHttpException;
@ -258,10 +258,10 @@ private function registerQuery(): Builder
$workspace = $this->workspace(); $workspace = $this->workspace();
if (! $user instanceof User || ! $workspace instanceof Workspace) { if (! $user instanceof User || ! $workspace instanceof Workspace) {
return TenantReview::query()->whereRaw('1 = 0'); return EnvironmentReview::query()->whereRaw('1 = 0');
} }
return app(TenantReviewRegisterService::class)->query($user, $workspace); return app(EnvironmentReviewRegisterService::class)->query($user, $workspace);
} }
/** /**
@ -341,36 +341,36 @@ private function workspace(): ?Workspace
: null; : null;
} }
private function reviewTruth(TenantReview $record, bool $fresh = false): ArtifactTruthEnvelope private function reviewTruth(EnvironmentReview $record, bool $fresh = false): ArtifactTruthEnvelope
{ {
$presenter = app(ArtifactTruthPresenter::class); $presenter = app(ArtifactTruthPresenter::class);
return $fresh return $fresh
? $presenter->forTenantReviewFresh($record) ? $presenter->forEnvironmentReviewFresh($record)
: $presenter->forTenantReview($record); : $presenter->forEnvironmentReview($record);
} }
private function reviewOutcomeLabel(TenantReview $record): string private function reviewOutcomeLabel(EnvironmentReview $record): string
{ {
return $this->reviewOutcome($record)->primaryLabel; return $this->reviewOutcome($record)->primaryLabel;
} }
private function reviewOutcomeBadgeColor(TenantReview $record): string private function reviewOutcomeBadgeColor(EnvironmentReview $record): string
{ {
return $this->reviewOutcome($record)->primaryBadge->color; return $this->reviewOutcome($record)->primaryBadge->color;
} }
private function reviewOutcomeBadgeIcon(TenantReview $record): ?string private function reviewOutcomeBadgeIcon(EnvironmentReview $record): ?string
{ {
return $this->reviewOutcome($record)->primaryBadge->icon; return $this->reviewOutcome($record)->primaryBadge->icon;
} }
private function reviewOutcomeBadgeIconColor(TenantReview $record): ?string private function reviewOutcomeBadgeIconColor(EnvironmentReview $record): ?string
{ {
return $this->reviewOutcome($record)->primaryBadge->iconColor; return $this->reviewOutcome($record)->primaryBadge->iconColor;
} }
private function reviewOutcomeDescription(TenantReview $record): ?string private function reviewOutcomeDescription(EnvironmentReview $record): ?string
{ {
$primaryReason = $this->reviewOutcome($record)->primaryReason; $primaryReason = $this->reviewOutcome($record)->primaryReason;
$findingOutcomeSummary = $this->findingOutcomeSummary($record); $findingOutcomeSummary = $this->findingOutcomeSummary($record);
@ -382,12 +382,12 @@ private function reviewOutcomeDescription(TenantReview $record): ?string
return trim($primaryReason.' Terminal outcomes: '.$findingOutcomeSummary.'.'); return trim($primaryReason.' Terminal outcomes: '.$findingOutcomeSummary.'.');
} }
private function reviewOutcomeNextStep(TenantReview $record): string private function reviewOutcomeNextStep(EnvironmentReview $record): string
{ {
return $this->reviewOutcome($record)->nextActionText; return $this->reviewOutcome($record)->nextActionText;
} }
private function reviewOutcome(TenantReview $record, bool $fresh = false): CompressedGovernanceOutcome private function reviewOutcome(EnvironmentReview $record, bool $fresh = false): CompressedGovernanceOutcome
{ {
$presenter = app(ArtifactTruthPresenter::class); $presenter = app(ArtifactTruthPresenter::class);
$truth = $fresh $truth = $fresh
@ -401,7 +401,7 @@ private function reviewOutcome(TenantReview $record, bool $fresh = false): Compr
); );
} }
private function findingOutcomeSummary(TenantReview $record): ?string private function findingOutcomeSummary(EnvironmentReview $record): ?string
{ {
$summary = is_array($record->summary) ? $record->summary : []; $summary = is_array($record->summary) ? $record->summary : [];
$outcomeCounts = $summary['finding_outcomes'] ?? []; $outcomeCounts = $summary['finding_outcomes'] ?? [];

View File

@ -6,7 +6,7 @@
use App\Models\User; use App\Models\User;
use App\Models\WorkspaceMembership; use App\Models\WorkspaceMembership;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver; use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Services\Auth\TenantMembershipManager; use App\Services\Auth\ManagedEnvironmentMembershipManager;
use App\Support\Workspaces\WorkspaceContext; use App\Support\Workspaces\WorkspaceContext;
use Filament\Forms; use Filament\Forms;
use Filament\Pages\Tenancy\RegisterTenant as BaseRegisterTenant; use Filament\Pages\Tenancy\RegisterTenant as BaseRegisterTenant;
@ -105,7 +105,7 @@ protected function handleRegistration(array $data): Model
->allowedManagedEnvironmentIdsForWorkspace($user, $workspaceId); ->allowedManagedEnvironmentIdsForWorkspace($user, $workspaceId);
if (is_array($explicitScopes)) { if (is_array($explicitScopes)) {
app(TenantMembershipManager::class)->grantScope( app(ManagedEnvironmentMembershipManager::class)->grantScope(
tenant: $tenant, tenant: $tenant,
actor: $user, actor: $user,
member: $user, member: $user,

View File

@ -4,7 +4,7 @@
namespace App\Filament\Pages\Workspaces; namespace App\Filament\Pages\Workspaces;
use App\Filament\Pages\ChooseTenant; use App\Filament\Pages\ChooseEnvironment;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
@ -17,7 +17,7 @@
use Filament\Pages\Page; use Filament\Pages\Page;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
class ManagedTenantsLanding extends Page class ManagedEnvironmentsLanding extends Page
{ {
protected static bool $shouldRegisterNavigation = false; protected static bool $shouldRegisterNavigation = false;
@ -25,7 +25,7 @@ class ManagedTenantsLanding extends Page
protected static string $layout = 'filament-panels::components.layout.simple'; protected static string $layout = 'filament-panels::components.layout.simple';
protected string $view = 'filament.pages.workspaces.managed-tenants-landing'; protected string $view = 'filament.pages.workspaces.managed-environments-landing';
public Workspace $workspace; public Workspace $workspace;
@ -90,9 +90,9 @@ public function getTenants(): Collection
->values(); ->values();
} }
public function goToChooseTenant(): void public function goToChooseEnvironment(): void
{ {
$this->redirect(route('admin.workspace.managed-tenants.index', ['workspace' => $this->workspace])); $this->redirect(route('admin.workspace.managed-environments.index', ['workspace' => $this->workspace]));
} }
public function openTenant(int $tenantId): void public function openTenant(int $tenantId): void

View File

@ -64,8 +64,8 @@ private function captureAction(): Action
: 'Capture baseline'; : 'Capture baseline';
$modalDescription = $captureMode === BaselineCaptureMode::FullContent $modalDescription = $captureMode === BaselineCaptureMode::FullContent
? 'Select the source tenant. This will capture content evidence on demand (redacted) and may take longer depending on scope.' ? 'Select the source environment. This will capture content evidence on demand (redacted) and may take longer depending on scope.'
: 'Select the source tenant whose current inventory will be captured as the baseline snapshot.'; : 'Select the source environment whose current inventory will be captured as the baseline snapshot.';
$action = Action::make('capture') $action = Action::make('capture')
->label($label) ->label($label)
@ -76,8 +76,8 @@ private function captureAction(): Action
->modalHeading($label) ->modalHeading($label)
->modalDescription($modalDescription) ->modalDescription($modalDescription)
->form([ ->form([
Select::make('source_tenant_id') Select::make('source_environment_id')
->label('Source ManagedEnvironment') ->label('Source managed environment')
->options(fn (): array => $this->getWorkspaceTenantOptions()) ->options(fn (): array => $this->getWorkspaceTenantOptions())
->required() ->required()
->searchable(), ->searchable(),
@ -91,11 +91,11 @@ private function captureAction(): Action
/** @var BaselineProfile $profile */ /** @var BaselineProfile $profile */
$profile = $this->getRecord(); $profile = $this->getRecord();
$sourceTenant = ManagedEnvironment::query()->find((int) $data['source_tenant_id']); $sourceTenant = ManagedEnvironment::query()->find((int) $data['source_environment_id']);
if (! $sourceTenant instanceof ManagedEnvironment) { if (! $sourceTenant instanceof ManagedEnvironment) {
Notification::make() Notification::make()
->title('Source tenant not found') ->title('Source environment not found')
->danger() ->danger()
->send(); ->send();
@ -175,8 +175,8 @@ private function compareNowAction(): Action
: 'Compare now'; : 'Compare now';
$modalDescription = $captureMode === BaselineCaptureMode::FullContent $modalDescription = $captureMode === BaselineCaptureMode::FullContent
? 'Select the target tenant. This will refresh content evidence on demand (redacted) before comparing.' ? 'Select the target environment. This will refresh content evidence on demand (redacted) before comparing.'
: 'Select the target tenant to compare its current inventory against the effective current baseline snapshot.'; : 'Select the target environment to compare its current inventory against the effective current baseline snapshot.';
return Action::make('compareNow') return Action::make('compareNow')
->label($label) ->label($label)
@ -187,8 +187,8 @@ private function compareNowAction(): Action
->modalHeading($label) ->modalHeading($label)
->modalDescription($modalDescription) ->modalDescription($modalDescription)
->form([ ->form([
Select::make('target_tenant_id') Select::make('target_environment_id')
->label('Target ManagedEnvironment') ->label('Target managed environment')
->options(fn (): array => $this->getEligibleCompareTenantOptions()) ->options(fn (): array => $this->getEligibleCompareTenantOptions())
->required() ->required()
->searchable(), ->searchable(),
@ -204,11 +204,11 @@ private function compareNowAction(): Action
/** @var BaselineProfile $profile */ /** @var BaselineProfile $profile */
$profile = $this->getRecord(); $profile = $this->getRecord();
$targetTenant = ManagedEnvironment::query()->find((int) $data['target_tenant_id']); $targetTenant = ManagedEnvironment::query()->find((int) $data['target_environment_id']);
if (! $targetTenant instanceof ManagedEnvironment || (int) $targetTenant->workspace_id !== (int) $profile->workspace_id) { if (! $targetTenant instanceof ManagedEnvironment || (int) $targetTenant->workspace_id !== (int) $profile->workspace_id) {
Notification::make() Notification::make()
->title('Target tenant not found') ->title('Target environment not found')
->danger() ->danger()
->send(); ->send();

View File

@ -8,16 +8,16 @@
use App\Filament\Concerns\ResolvesPanelTenantContext; use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Filament\Concerns\WorkspaceScopedTenantRoutes; use App\Filament\Concerns\WorkspaceScopedTenantRoutes;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\TenantReviewResource\Pages; use App\Filament\Resources\EnvironmentReviewResource\Pages;
use App\Exceptions\Entitlements\WorkspaceEntitlementBlockedException; use App\Exceptions\Entitlements\WorkspaceEntitlementBlockedException;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\TenantReviewSection; use App\Models\EnvironmentReviewSection;
use App\Models\User; use App\Models\User;
use App\Services\ReviewPackService; use App\Services\ReviewPackService;
use App\Services\TenantReviews\TenantReviewService; use App\Services\EnvironmentReviews\EnvironmentReviewService;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Auth\UiTooltips as AuthUiTooltips; use App\Support\Auth\UiTooltips as AuthUiTooltips;
use App\Support\Badges\BadgeCatalog; use App\Support\Badges\BadgeCatalog;
@ -31,8 +31,8 @@
use App\Support\ReasonTranslation\ReasonPresenter; use App\Support\ReasonTranslation\ReasonPresenter;
use App\Support\ReviewPackStatus; use App\Support\ReviewPackStatus;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile; use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -62,7 +62,7 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
use UnitEnum; use UnitEnum;
class TenantReviewResource extends Resource class EnvironmentReviewResource extends Resource
{ {
use InteractsWithTenantOwnedRecords; use InteractsWithTenantOwnedRecords;
use ResolvesPanelTenantContext; use ResolvesPanelTenantContext;
@ -70,7 +70,7 @@ class TenantReviewResource extends Resource
protected static bool $isDiscovered = false; protected static bool $isDiscovered = false;
protected static ?string $model = TenantReview::class; protected static ?string $model = EnvironmentReview::class;
protected static ?string $slug = 'reviews'; protected static ?string $slug = 'reviews';
@ -95,7 +95,7 @@ public static function shouldRegisterNavigation(): bool
public static function getSlug(?Panel $panel = null): string public static function getSlug(?Panel $panel = null): string
{ {
$slug = $panel?->getId() === 'admin' $slug = $panel?->getId() === 'admin'
? 'tenant-reviews' ? 'environment-reviews'
: parent::getSlug($panel); : parent::getSlug($panel);
return static::workspaceScopedSlug($slug, $panel); return static::workspaceScopedSlug($slug, $panel);
@ -134,7 +134,7 @@ public static function canViewAny(): bool
return false; return false;
} }
return $user->can(Capabilities::TENANT_REVIEW_VIEW, $tenant); return $user->can(Capabilities::ENVIRONMENT_REVIEW_VIEW, $tenant);
} }
public static function canView(Model $record): bool public static function canView(Model $record): bool
@ -142,7 +142,7 @@ public static function canView(Model $record): bool
$tenant = static::resolveTenantContextForCurrentPanel(); $tenant = static::resolveTenantContextForCurrentPanel();
$user = auth()->user(); $user = auth()->user();
if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User || ! $record instanceof TenantReview) { if (! $tenant instanceof ManagedEnvironment || ! $user instanceof User || ! $record instanceof EnvironmentReview) {
return false; return false;
} }
@ -194,7 +194,7 @@ public static function infolist(Schema $schema): Schema
ViewEntry::make('artifact_truth') ViewEntry::make('artifact_truth')
->hiddenLabel() ->hiddenLabel()
->view('filament.infolists.entries.governance-artifact-truth') ->view('filament.infolists.entries.governance-artifact-truth')
->state(fn (TenantReview $record): array => static::truthState($record)) ->state(fn (EnvironmentReview $record): array => static::truthState($record))
->columnSpanFull(), ->columnSpanFull(),
]) ])
->columnSpanFull(), ->columnSpanFull(),
@ -202,30 +202,30 @@ public static function infolist(Schema $schema): Schema
->schema([ ->schema([
TextEntry::make('status') TextEntry::make('status')
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantReviewStatus)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EnvironmentReviewStatus))
->color(BadgeRenderer::color(BadgeDomain::TenantReviewStatus)) ->color(BadgeRenderer::color(BadgeDomain::EnvironmentReviewStatus))
->icon(BadgeRenderer::icon(BadgeDomain::TenantReviewStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantReviewStatus)), ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewStatus)),
TextEntry::make('completeness_state') TextEntry::make('completeness_state')
->label(__('localization.review.completeness')) ->label(__('localization.review.completeness'))
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantReviewCompleteness)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EnvironmentReviewCompleteness))
->color(BadgeRenderer::color(BadgeDomain::TenantReviewCompleteness)) ->color(BadgeRenderer::color(BadgeDomain::EnvironmentReviewCompleteness))
->icon(BadgeRenderer::icon(BadgeDomain::TenantReviewCompleteness)) ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewCompleteness))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantReviewCompleteness)), ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewCompleteness)),
TextEntry::make('tenant.name')->label(__('localization.review.tenant')), TextEntry::make('tenant.name')->label(__('localization.review.tenant')),
TextEntry::make('generated_at')->dateTime()->placeholder('—'), TextEntry::make('generated_at')->dateTime()->placeholder('—'),
TextEntry::make('published_at')->dateTime()->placeholder('—'), TextEntry::make('published_at')->dateTime()->placeholder('—'),
TextEntry::make('evidenceSnapshot.id') TextEntry::make('evidenceSnapshot.id')
->label(__('localization.review.evidence_snapshot')) ->label(__('localization.review.evidence_snapshot'))
->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—') ->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—')
->url(fn (TenantReview $record): ?string => $record->evidenceSnapshot ->url(fn (EnvironmentReview $record): ?string => $record->evidenceSnapshot
? EvidenceSnapshotResource::getUrl('view', ['record' => $record->evidenceSnapshot], tenant: $record->tenant) ? EvidenceSnapshotResource::getUrl('view', ['record' => $record->evidenceSnapshot], tenant: $record->tenant)
: null), : null),
TextEntry::make('currentExportReviewPack.id') TextEntry::make('currentExportReviewPack.id')
->label(__('localization.review.current_export')) ->label(__('localization.review.current_export'))
->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—') ->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—')
->url(fn (TenantReview $record): ?string => $record->currentExportReviewPack ->url(fn (EnvironmentReview $record): ?string => $record->currentExportReviewPack
? ReviewPackResource::getUrl('view', ['record' => $record->currentExportReviewPack], tenant: $record->tenant) ? ReviewPackResource::getUrl('view', ['record' => $record->currentExportReviewPack], tenant: $record->tenant)
: null), : null),
TextEntry::make('fingerprint') TextEntry::make('fingerprint')
@ -242,32 +242,32 @@ public static function infolist(Schema $schema): Schema
->schema([ ->schema([
ViewEntry::make('review_summary') ViewEntry::make('review_summary')
->hiddenLabel() ->hiddenLabel()
->view('filament.infolists.entries.tenant-review-summary') ->view('filament.infolists.entries.environment-review-summary')
->state(fn (TenantReview $record): array => static::summaryPresentation($record)) ->state(fn (EnvironmentReview $record): array => static::summaryPresentation($record))
->columnSpanFull(), ->columnSpanFull(),
]) ])
->columnSpanFull(), ->columnSpanFull(),
Section::make(__('localization.review.sections')) Section::make(__('localization.review.sections'))
->schema([ ->schema([
RepeatableEntry::make('sections') RepeatableEntry::make('sections')
->state(fn (TenantReview $record): array => static::visibleSections($record)) ->state(fn (EnvironmentReview $record): array => static::visibleSections($record))
->hiddenLabel() ->hiddenLabel()
->schema([ ->schema([
TextEntry::make('title'), TextEntry::make('title'),
TextEntry::make('completeness_state') TextEntry::make('completeness_state')
->label(__('localization.review.completeness')) ->label(__('localization.review.completeness'))
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantReviewCompleteness)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EnvironmentReviewCompleteness))
->color(BadgeRenderer::color(BadgeDomain::TenantReviewCompleteness)) ->color(BadgeRenderer::color(BadgeDomain::EnvironmentReviewCompleteness))
->icon(BadgeRenderer::icon(BadgeDomain::TenantReviewCompleteness)) ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewCompleteness))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantReviewCompleteness)), ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewCompleteness)),
TextEntry::make('measured_at')->dateTime()->placeholder('—'), TextEntry::make('measured_at')->dateTime()->placeholder('—'),
Section::make(__('localization.review.details')) Section::make(__('localization.review.details'))
->schema([ ->schema([
ViewEntry::make('section_payload') ViewEntry::make('section_payload')
->hiddenLabel() ->hiddenLabel()
->view('filament.infolists.entries.tenant-review-section') ->view('filament.infolists.entries.environment-review-section')
->state(fn (TenantReviewSection $record): array => static::sectionPresentation($record)) ->state(fn (EnvironmentReviewSection $record): array => static::sectionPresentation($record))
->columnSpanFull(), ->columnSpanFull(),
]) ])
->collapsible() ->collapsible()
@ -281,12 +281,12 @@ public static function infolist(Schema $schema): Schema
} }
/** /**
* @return array<int, TenantReviewSection> * @return array<int, EnvironmentReviewSection>
*/ */
private static function visibleSections(TenantReview $record): array private static function visibleSections(EnvironmentReview $record): array
{ {
return $record->sections return $record->sections
->reject(fn (TenantReviewSection $section): bool => static::isCustomerWorkspaceMode() && $section->isControlInterpretation()) ->reject(fn (EnvironmentReviewSection $section): bool => static::isCustomerWorkspaceMode() && $section->isControlInterpretation())
->values() ->values()
->all(); ->all();
} }
@ -297,43 +297,43 @@ public static function table(Table $table): Table
Actions\Action::make('export_executive_pack') Actions\Action::make('export_executive_pack')
->label(__('localization.review.export_executive_pack')) ->label(__('localization.review.export_executive_pack'))
->icon('heroicon-o-arrow-down-tray') ->icon('heroicon-o-arrow-down-tray')
->visible(fn (TenantReview $record): bool => in_array($record->status, [ ->visible(fn (EnvironmentReview $record): bool => in_array($record->status, [
TenantReviewStatus::Ready->value, EnvironmentReviewStatus::Ready->value,
TenantReviewStatus::Published->value, EnvironmentReviewStatus::Published->value,
], true)) ], true))
->disabled(fn (TenantReview $record): bool => static::reviewPackGenerationBlocked($record->tenant)) ->disabled(fn (EnvironmentReview $record): bool => static::reviewPackGenerationBlocked($record->tenant))
->action(fn (TenantReview $record): mixed => static::executeExport($record)), ->action(fn (EnvironmentReview $record): mixed => static::executeExport($record)),
fn (TenantReview $record): TenantReview => $record, fn (EnvironmentReview $record): EnvironmentReview => $record,
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->preserveVisibility() ->preserveVisibility()
->preserveDisabled() ->preserveDisabled()
->apply(); ->apply();
$exportExecutivePackAction->tooltip(fn (TenantReview $record): ?string => static::reviewPackGenerationActionTooltip($record->tenant)); $exportExecutivePackAction->tooltip(fn (EnvironmentReview $record): ?string => static::reviewPackGenerationActionTooltip($record->tenant));
return $table return $table
->defaultSort('generated_at', 'desc') ->defaultSort('generated_at', 'desc')
->persistFiltersInSession() ->persistFiltersInSession()
->persistSearchInSession() ->persistSearchInSession()
->persistSortInSession() ->persistSortInSession()
->recordUrl(fn (TenantReview $record): string => static::tenantScopedUrl('view', ['record' => $record], $record->tenant)) ->recordUrl(fn (EnvironmentReview $record): string => static::tenantScopedUrl('view', ['record' => $record], $record->tenant))
->columns([ ->columns([
Tables\Columns\TextColumn::make('status') Tables\Columns\TextColumn::make('status')
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantReviewStatus)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EnvironmentReviewStatus))
->color(BadgeRenderer::color(BadgeDomain::TenantReviewStatus)) ->color(BadgeRenderer::color(BadgeDomain::EnvironmentReviewStatus))
->icon(BadgeRenderer::icon(BadgeDomain::TenantReviewStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantReviewStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewStatus))
->sortable(), ->sortable(),
Tables\Columns\TextColumn::make('outcome') Tables\Columns\TextColumn::make('outcome')
->label(__('localization.review.outcome')) ->label(__('localization.review.outcome'))
->badge() ->badge()
->getStateUsing(fn (TenantReview $record): string => static::compressedOutcome($record)->primaryLabel) ->getStateUsing(fn (EnvironmentReview $record): string => static::compressedOutcome($record)->primaryLabel)
->color(fn (TenantReview $record): string => static::compressedOutcome($record)->primaryBadge->color) ->color(fn (EnvironmentReview $record): string => static::compressedOutcome($record)->primaryBadge->color)
->icon(fn (TenantReview $record): ?string => static::compressedOutcome($record)->primaryBadge->icon) ->icon(fn (EnvironmentReview $record): ?string => static::compressedOutcome($record)->primaryBadge->icon)
->iconColor(fn (TenantReview $record): ?string => static::compressedOutcome($record)->primaryBadge->iconColor) ->iconColor(fn (EnvironmentReview $record): ?string => static::compressedOutcome($record)->primaryBadge->iconColor)
->description(fn (TenantReview $record): ?string => static::compressedOutcome($record)->primaryReason) ->description(fn (EnvironmentReview $record): ?string => static::compressedOutcome($record)->primaryReason)
->wrap(), ->wrap(),
Tables\Columns\TextColumn::make('generated_at')->dateTime()->placeholder('—')->sortable(), Tables\Columns\TextColumn::make('generated_at')->dateTime()->placeholder('—')->sortable(),
Tables\Columns\TextColumn::make('published_at')->dateTime()->placeholder('—')->sortable(), Tables\Columns\TextColumn::make('published_at')->dateTime()->placeholder('—')->sortable(),
@ -342,7 +342,7 @@ public static function table(Table $table): Table
->boolean(), ->boolean(),
Tables\Columns\TextColumn::make('next_step') Tables\Columns\TextColumn::make('next_step')
->label(__('localization.review.next_step')) ->label(__('localization.review.next_step'))
->getStateUsing(fn (TenantReview $record): string => static::compressedOutcome($record)->nextActionText) ->getStateUsing(fn (EnvironmentReview $record): string => static::compressedOutcome($record)->nextActionText)
->wrap(), ->wrap(),
Tables\Columns\TextColumn::make('fingerprint') Tables\Columns\TextColumn::make('fingerprint')
->toggleable(isToggledHiddenByDefault: true) ->toggleable(isToggledHiddenByDefault: true)
@ -350,18 +350,18 @@ public static function table(Table $table): Table
]) ])
->filters([ ->filters([
Tables\Filters\SelectFilter::make('status') Tables\Filters\SelectFilter::make('status')
->options(collect(TenantReviewStatus::cases()) ->options(collect(EnvironmentReviewStatus::cases())
->mapWithKeys(fn (TenantReviewStatus $status): array => [$status->value => Str::headline($status->value)]) ->mapWithKeys(fn (EnvironmentReviewStatus $status): array => [$status->value => Str::headline($status->value)])
->all()), ->all()),
Tables\Filters\SelectFilter::make('completeness_state') Tables\Filters\SelectFilter::make('completeness_state')
->options(BadgeCatalog::options(BadgeDomain::TenantReviewCompleteness, TenantReviewCompletenessState::values())), ->options(BadgeCatalog::options(BadgeDomain::EnvironmentReviewCompleteness, EnvironmentReviewCompletenessState::values())),
\App\Support\Filament\FilterPresets::dateRange('review_date', __('localization.review.review_date'), 'generated_at'), \App\Support\Filament\FilterPresets::dateRange('review_date', __('localization.review.review_date'), 'generated_at'),
]) ])
->actions([ ->actions([
$exportExecutivePackAction, $exportExecutivePackAction,
]) ])
->bulkActions([]) ->bulkActions([])
->emptyStateHeading(__('localization.review.no_tenant_reviews_yet')) ->emptyStateHeading(__('localization.review.no_environment_reviews_yet'))
->emptyStateDescription(__('localization.review.create_first_review_description')) ->emptyStateDescription(__('localization.review.create_first_review_description'))
->emptyStateActions([ ->emptyStateActions([
static::makeCreateReviewAction( static::makeCreateReviewAction(
@ -375,8 +375,8 @@ public static function table(Table $table): Table
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListTenantReviews::route('/'), 'index' => Pages\ListEnvironmentReviews::route('/'),
'view' => Pages\ViewTenantReview::route('/{record}'), 'view' => Pages\ViewEnvironmentReview::route('/{record}'),
]; ];
} }
@ -406,7 +406,7 @@ public static function makeCreateReviewAction(
]) ])
->action(fn (array $data): mixed => static::executeCreateReview($data)), ->action(fn (array $data): mixed => static::executeCreateReview($data)),
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->apply(); ->apply();
} }
@ -428,7 +428,7 @@ public static function executeCreateReview(array $data): void
abort(404); abort(404);
} }
if (! $user->can(Capabilities::TENANT_REVIEW_MANAGE, $tenant)) { if (! $user->can(Capabilities::ENVIRONMENT_REVIEW_MANAGE, $tenant)) {
abort(403); abort(403);
} }
@ -447,7 +447,7 @@ public static function executeCreateReview(array $data): void
} }
try { try {
$review = app(TenantReviewService::class)->create($tenant, $snapshot, $user); $review = app(EnvironmentReviewService::class)->create($tenant, $snapshot, $user);
} catch (\Throwable $throwable) { } catch (\Throwable $throwable) {
Notification::make()->danger()->title(__('localization.review.unable_create_review'))->body($throwable->getMessage())->send(); Notification::make()->danger()->title(__('localization.review.unable_create_review'))->body($throwable->getMessage())->send();
@ -471,7 +471,7 @@ public static function executeCreateReview(array $data): void
return; return;
} }
$toast = OperationUxPresenter::queuedToast(OperationRunType::TenantReviewCompose->value) $toast = OperationUxPresenter::queuedToast(OperationRunType::EnvironmentReviewCompose->value)
->body(__('localization.review.review_composing_background')); ->body(__('localization.review.review_composing_background'));
if ($review->operation_run_id) { if ($review->operation_run_id) {
@ -535,7 +535,7 @@ public static function reviewPackGenerationActionTooltip(?ManagedEnvironment $te
$tenant ??= static::panelTenantContext(); $tenant ??= static::panelTenantContext();
$user = auth()->user(); $user = auth()->user();
if ($tenant instanceof ManagedEnvironment && $user instanceof User && ! $user->can(Capabilities::TENANT_REVIEW_MANAGE, $tenant)) { if ($tenant instanceof ManagedEnvironment && $user instanceof User && ! $user->can(Capabilities::ENVIRONMENT_REVIEW_MANAGE, $tenant)) {
return AuthUiTooltips::insufficientPermission(); return AuthUiTooltips::insufficientPermission();
} }
@ -543,7 +543,7 @@ public static function reviewPackGenerationActionTooltip(?ManagedEnvironment $te
?? static::reviewPackGenerationWarningReason($tenant); ?? static::reviewPackGenerationWarningReason($tenant);
} }
public static function executeExport(TenantReview $review): void public static function executeExport(EnvironmentReview $review): void
{ {
$review->loadMissing(['tenant', 'currentExportReviewPack']); $review->loadMissing(['tenant', 'currentExportReviewPack']);
$user = auth()->user(); $user = auth()->user();
@ -654,13 +654,13 @@ private static function evidenceSnapshotOptions(): array
private static function reviewCompletenessCountLabel(string $state): string private static function reviewCompletenessCountLabel(string $state): string
{ {
return BadgeCatalog::spec(BadgeDomain::TenantReviewCompleteness, $state)->label; return BadgeCatalog::spec(BadgeDomain::EnvironmentReviewCompleteness, $state)->label;
} }
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private static function summaryPresentation(TenantReview $record): array private static function summaryPresentation(EnvironmentReview $record): array
{ {
$summary = is_array($record->summary) ? $record->summary : []; $summary = is_array($record->summary) ? $record->summary : [];
$truthEnvelope = static::truthEnvelope($record); $truthEnvelope = static::truthEnvelope($record);
@ -706,7 +706,7 @@ private static function summaryPresentation(TenantReview $record): array
* @param array<string, mixed> $packagePresentation * @param array<string, mixed> $packagePresentation
* @return array<int, array{label:string,value:string}> * @return array<int, array{label:string,value:string}>
*/ */
private static function customerWorkspaceMetrics(TenantReview $record, array $summary, array $packagePresentation): array private static function customerWorkspaceMetrics(EnvironmentReview $record, array $summary, array $packagePresentation): array
{ {
$acceptedRisk = is_array($summary['risk_acceptance'] ?? null) ? $summary['risk_acceptance'] : []; $acceptedRisk = is_array($summary['risk_acceptance'] ?? null) ? $summary['risk_acceptance'] : [];
@ -719,9 +719,9 @@ private static function customerWorkspaceMetrics(TenantReview $record, array $su
]; ];
} }
private static function customerReviewStatusLabel(TenantReview $record): string private static function customerReviewStatusLabel(EnvironmentReview $record): string
{ {
if ($record->isPublished() && (string) $record->completeness_state === TenantReviewCompletenessState::Complete->value) { if ($record->isPublished() && (string) $record->completeness_state === EnvironmentReviewCompletenessState::Complete->value) {
return __('localization.review.review_completed'); return __('localization.review.review_completed');
} }
@ -732,7 +732,7 @@ private static function customerReviewStatusLabel(TenantReview $record): string
return Str::headline((string) $record->status); return Str::headline((string) $record->status);
} }
private static function customerEvidenceStatusLabel(TenantReview $record): string private static function customerEvidenceStatusLabel(EnvironmentReview $record): string
{ {
$snapshot = $record->evidenceSnapshot; $snapshot = $record->evidenceSnapshot;
$tenant = $record->tenant; $tenant = $record->tenant;
@ -775,7 +775,7 @@ private static function customerAcceptedRiskStatusLabel(array $acceptedRisk): st
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private static function governancePackagePresentation(TenantReview $record): array private static function governancePackagePresentation(EnvironmentReview $record): array
{ {
$summary = is_array($record->summary) ? $record->summary : []; $summary = is_array($record->summary) ? $record->summary : [];
$package = is_array($summary['governance_package'] ?? null) ? $summary['governance_package'] : []; $package = is_array($summary['governance_package'] ?? null) ? $summary['governance_package'] : [];
@ -793,7 +793,7 @@ private static function governancePackagePresentation(TenantReview $record): arr
/** /**
* @return array{state:string,label:string,description:string} * @return array{state:string,label:string,description:string}
*/ */
private static function governancePackageAvailability(TenantReview $record): array private static function governancePackageAvailability(EnvironmentReview $record): array
{ {
$pack = $record->currentExportReviewPack; $pack = $record->currentExportReviewPack;
$tenant = $record->tenant; $tenant = $record->tenant;
@ -801,8 +801,8 @@ private static function governancePackageAvailability(TenantReview $record): arr
$controlInterpretation = $record->controlInterpretation(); $controlInterpretation = $record->controlInterpretation();
$limitations = is_array($controlInterpretation['limitations'] ?? null) ? $controlInterpretation['limitations'] : []; $limitations = is_array($controlInterpretation['limitations'] ?? null) ? $controlInterpretation['limitations'] : [];
$isPartialReview = in_array((string) $record->completeness_state, [ $isPartialReview = in_array((string) $record->completeness_state, [
TenantReviewCompletenessState::Partial->value, EnvironmentReviewCompletenessState::Partial->value,
TenantReviewCompletenessState::Stale->value, EnvironmentReviewCompletenessState::Stale->value,
], true) || $limitations !== []; ], true) || $limitations !== [];
if (! $pack instanceof ReviewPack) { if (! $pack instanceof ReviewPack) {
@ -855,7 +855,7 @@ private static function governancePackageAvailability(TenantReview $record): arr
/** /**
* @return array<int, array{title:string,label:string,url:?string,description:string}> * @return array<int, array{title:string,label:string,url:?string,description:string}>
*/ */
private static function summaryContextLinks(TenantReview $record, bool $customerWorkspaceMode = false): array private static function summaryContextLinks(EnvironmentReview $record, bool $customerWorkspaceMode = false): array
{ {
$links = []; $links = [];
@ -913,15 +913,15 @@ private static function summaryContextLinks(TenantReview $record, bool $customer
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private static function sectionPresentation(TenantReviewSection $section): array private static function sectionPresentation(EnvironmentReviewSection $section): array
{ {
$summary = is_array($section->summary_payload) ? $section->summary_payload : []; $summary = is_array($section->summary_payload) ? $section->summary_payload : [];
$render = is_array($section->render_payload) ? $section->render_payload : []; $render = is_array($section->render_payload) ? $section->render_payload : [];
$review = $section->tenantReview; $review = $section->environmentReview;
$tenant = $section->tenant; $tenant = $section->tenant;
$links = []; $links = [];
if ($section->isControlInterpretation() && $review instanceof TenantReview && $tenant instanceof ManagedEnvironment && $review->evidenceSnapshot instanceof EvidenceSnapshot) { if ($section->isControlInterpretation() && $review instanceof EnvironmentReview && $tenant instanceof ManagedEnvironment && $review->evidenceSnapshot instanceof EvidenceSnapshot) {
$user = auth()->user(); $user = auth()->user();
if ($user instanceof User && $user->can(Capabilities::EVIDENCE_VIEW, $tenant)) { if ($user instanceof User && $user->can(Capabilities::EVIDENCE_VIEW, $tenant)) {
@ -960,34 +960,34 @@ private static function sectionPresentation(TenantReviewSection $section): array
]; ];
} }
private static function truthEnvelope(TenantReview $record, bool $fresh = false): ArtifactTruthEnvelope private static function truthEnvelope(EnvironmentReview $record, bool $fresh = false): ArtifactTruthEnvelope
{ {
$presenter = app(ArtifactTruthPresenter::class); $presenter = app(ArtifactTruthPresenter::class);
return $fresh return $fresh
? $presenter->forTenantReviewFresh($record) ? $presenter->forEnvironmentReviewFresh($record)
: $presenter->forTenantReview($record); : $presenter->forEnvironmentReview($record);
} }
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private static function truthState(TenantReview $record, bool $fresh = false): array private static function truthState(EnvironmentReview $record, bool $fresh = false): array
{ {
$presenter = app(ArtifactTruthPresenter::class); $presenter = app(ArtifactTruthPresenter::class);
return $presenter->surfaceStateFor($record, SurfaceCompressionContext::tenantReview(), $fresh) return $presenter->surfaceStateFor($record, SurfaceCompressionContext::environmentReview(), $fresh)
?? static::truthEnvelope($record, $fresh)->toArray(static::compressedOutcome($record, $fresh)); ?? static::truthEnvelope($record, $fresh)->toArray(static::compressedOutcome($record, $fresh));
} }
private static function compressedOutcome(TenantReview $record, bool $fresh = false): CompressedGovernanceOutcome private static function compressedOutcome(EnvironmentReview $record, bool $fresh = false): CompressedGovernanceOutcome
{ {
$presenter = app(ArtifactTruthPresenter::class); $presenter = app(ArtifactTruthPresenter::class);
return $presenter->compressedOutcomeFor($record, SurfaceCompressionContext::tenantReview(), $fresh) return $presenter->compressedOutcomeFor($record, SurfaceCompressionContext::environmentReview(), $fresh)
?? $presenter->compressedOutcomeFromEnvelope( ?? $presenter->compressedOutcomeFromEnvelope(
static::truthEnvelope($record, $fresh), static::truthEnvelope($record, $fresh),
SurfaceCompressionContext::tenantReview(), SurfaceCompressionContext::environmentReview(),
); );
} }
@ -1013,7 +1013,7 @@ private static function isCustomerWorkspaceMode(): bool
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private static function customerWorkspaceEvidenceQuery(TenantReview $record): array private static function customerWorkspaceEvidenceQuery(EnvironmentReview $record): array
{ {
return array_filter([ return array_filter([
'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE, 'source_surface' => CustomerReviewWorkspace::SOURCE_SURFACE,

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\EnvironmentReviewResource\Pages;
use App\Filament\Resources\EnvironmentReviewResource;
use Filament\Resources\Pages\ListRecords;
class ListEnvironmentReviews extends ListRecords
{
protected static string $resource = EnvironmentReviewResource::class;
protected function getHeaderActions(): array
{
return [
EnvironmentReviewResource::makeCreateReviewAction(),
];
}
}

View File

@ -2,23 +2,23 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Resources\TenantReviewResource\Pages; namespace App\Filament\Resources\EnvironmentReviewResource\Pages;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Services\ReviewPackService; use App\Services\ReviewPackService;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\TenantReviews\TenantReviewLifecycleService; use App\Services\EnvironmentReviews\EnvironmentReviewLifecycleService;
use App\Services\TenantReviews\TenantReviewService; use App\Services\EnvironmentReviews\EnvironmentReviewService;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\ReviewPackStatus; use App\Support\ReviewPackStatus;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use App\Support\Ui\GovernanceActions\GovernanceActionCatalog; use App\Support\Ui\GovernanceActions\GovernanceActionCatalog;
use Filament\Actions; use Filament\Actions;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
@ -26,9 +26,9 @@
use Filament\Resources\Pages\ViewRecord; use Filament\Resources\Pages\ViewRecord;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class ViewTenantReview extends ViewRecord class ViewEnvironmentReview extends ViewRecord
{ {
protected static string $resource = TenantReviewResource::class; protected static string $resource = EnvironmentReviewResource::class;
public function mount(int|string $record): void public function mount(int|string $record): void
{ {
@ -39,16 +39,16 @@ public function mount(int|string $record): void
protected function resolveRecord(int|string $key): Model protected function resolveRecord(int|string $key): Model
{ {
return TenantReviewResource::resolveScopedRecordOrFail($key); return EnvironmentReviewResource::resolveScopedRecordOrFail($key);
} }
protected function authorizeAccess(): void protected function authorizeAccess(): void
{ {
$tenant = TenantReviewResource::panelTenantContext(); $tenant = EnvironmentReviewResource::panelTenantContext();
$record = $this->getRecord(); $record = $this->getRecord();
$user = auth()->user(); $user = auth()->user();
if (! $user instanceof User || ! $tenant instanceof ManagedEnvironment || ! $record instanceof TenantReview) { if (! $user instanceof User || ! $tenant instanceof ManagedEnvironment || ! $record instanceof EnvironmentReview) {
abort(404); abort(404);
} }
@ -108,11 +108,11 @@ private function primaryLifecycleActionName(): ?string
return null; return null;
} }
if ((string) $this->record->status === TenantReviewStatus::Published->value) { if ((string) $this->record->status === EnvironmentReviewStatus::Published->value) {
return 'export_executive_pack'; return 'export_executive_pack';
} }
if ((string) $this->record->status === TenantReviewStatus::Ready->value) { if ((string) $this->record->status === EnvironmentReviewStatus::Ready->value) {
return 'publish_review'; return 'publish_review';
} }
@ -157,8 +157,8 @@ private function secondaryLifecycleActionNames(): array
} }
if (in_array((string) $this->record->status, [ if (in_array((string) $this->record->status, [
TenantReviewStatus::Ready->value, EnvironmentReviewStatus::Ready->value,
TenantReviewStatus::Published->value, EnvironmentReviewStatus::Published->value,
], true)) { ], true)) {
$names[] = 'export_executive_pack'; $names[] = 'export_executive_pack';
} }
@ -194,7 +194,7 @@ private function refreshReviewAction(): Actions\Action
} }
try { try {
app(TenantReviewService::class)->refresh($this->record, $user); app(EnvironmentReviewService::class)->refresh($this->record, $user);
} catch (\Throwable $throwable) { } catch (\Throwable $throwable) {
Notification::make()->danger()->title('Unable to refresh review')->body($throwable->getMessage())->send(); Notification::make()->danger()->title('Unable to refresh review')->body($throwable->getMessage())->send();
@ -204,7 +204,7 @@ private function refreshReviewAction(): Actions\Action
Notification::make()->success()->title($rule->successTitle)->send(); Notification::make()->success()->title($rule->successTitle)->send();
}), }),
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->apply(); ->apply();
} }
@ -236,7 +236,7 @@ private function publishReviewAction(): Actions\Action
} }
try { try {
app(TenantReviewLifecycleService::class)->publish( app(EnvironmentReviewLifecycleService::class)->publish(
$this->record, $this->record,
$user, $user,
(string) ($data['publish_reason'] ?? ''), (string) ($data['publish_reason'] ?? ''),
@ -251,7 +251,7 @@ private function publishReviewAction(): Actions\Action
Notification::make()->success()->title($rule->successTitle)->send(); Notification::make()->success()->title($rule->successTitle)->send();
}), }),
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->preserveVisibility() ->preserveVisibility()
->apply(); ->apply();
} }
@ -264,18 +264,18 @@ private function exportExecutivePackAction(): Actions\Action
->icon('heroicon-o-arrow-down-tray') ->icon('heroicon-o-arrow-down-tray')
->color('primary') ->color('primary')
->hidden(fn (): bool => ! in_array((string) $this->record->status, [ ->hidden(fn (): bool => ! in_array((string) $this->record->status, [
TenantReviewStatus::Ready->value, EnvironmentReviewStatus::Ready->value,
TenantReviewStatus::Published->value, EnvironmentReviewStatus::Published->value,
], true)) ], true))
->disabled(fn (): bool => TenantReviewResource::reviewPackGenerationBlocked($this->record->tenant)) ->disabled(fn (): bool => EnvironmentReviewResource::reviewPackGenerationBlocked($this->record->tenant))
->action(fn (): mixed => TenantReviewResource::executeExport($this->record)), ->action(fn (): mixed => EnvironmentReviewResource::executeExport($this->record)),
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->preserveVisibility() ->preserveVisibility()
->preserveDisabled() ->preserveDisabled()
->apply(); ->apply();
$action->tooltip(fn (): ?string => TenantReviewResource::reviewPackGenerationActionTooltip($this->record->tenant)); $action->tooltip(fn (): ?string => EnvironmentReviewResource::reviewPackGenerationActionTooltip($this->record->tenant));
return $action; return $action;
} }
@ -295,17 +295,17 @@ private function createNextReviewAction(): Actions\Action
} }
try { try {
$nextReview = app(TenantReviewLifecycleService::class)->createNextReview($this->record, $user); $nextReview = app(EnvironmentReviewLifecycleService::class)->createNextReview($this->record, $user);
} catch (\Throwable $throwable) { } catch (\Throwable $throwable) {
Notification::make()->danger()->title('Unable to create next review')->body($throwable->getMessage())->send(); Notification::make()->danger()->title('Unable to create next review')->body($throwable->getMessage())->send();
return; return;
} }
$this->redirect(TenantReviewResource::tenantScopedUrl('view', ['record' => $nextReview], $nextReview->tenant)); $this->redirect(EnvironmentReviewResource::tenantScopedUrl('view', ['record' => $nextReview], $nextReview->tenant));
}), }),
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->preserveVisibility() ->preserveVisibility()
->apply(); ->apply();
} }
@ -337,7 +337,7 @@ private function archiveReviewAction(): Actions\Action
abort(403); abort(403);
} }
app(TenantReviewLifecycleService::class)->archive( app(EnvironmentReviewLifecycleService::class)->archive(
$this->record, $this->record,
$user, $user,
(string) ($data['archive_reason'] ?? ''), (string) ($data['archive_reason'] ?? ''),
@ -347,7 +347,7 @@ private function archiveReviewAction(): Actions\Action
Notification::make()->success()->title($rule->successTitle)->send(); Notification::make()->success()->title($rule->successTitle)->send();
}), }),
) )
->requireCapability(Capabilities::TENANT_REVIEW_MANAGE) ->requireCapability(Capabilities::ENVIRONMENT_REVIEW_MANAGE)
->preserveVisibility() ->preserveVisibility()
->apply(); ->apply();
} }
@ -443,7 +443,7 @@ private function auditCustomerWorkspaceOpen(): void
app(WorkspaceAuditLogger::class)->log( app(WorkspaceAuditLogger::class)->log(
workspace: $tenant->workspace, workspace: $tenant->workspace,
action: AuditActionId::TenantReviewOpened, action: AuditActionId::EnvironmentReviewOpened,
context: [ context: [
'metadata' => [ 'metadata' => [
'review_id' => (int) $this->record->getKey(), 'review_id' => (int) $this->record->getKey(),
@ -453,7 +453,7 @@ private function auditCustomerWorkspaceOpen(): void
], ],
], ],
actor: $user, actor: $user,
resourceType: 'tenant_review', resourceType: 'environment_review',
resourceId: (string) $this->record->getKey(), resourceId: (string) $this->record->getKey(),
targetLabel: sprintf('ManagedEnvironment review #%d', (int) $this->record->getKey()), targetLabel: sprintf('ManagedEnvironment review #%d', (int) $this->record->getKey()),
tenant: $tenant, tenant: $tenant,

View File

@ -681,7 +681,7 @@ public static function approvalQueueUrl(?ManagedEnvironment $tenant = null): ?st
} }
return route('admin.finding-exceptions.open-queue', [ return route('admin.finding-exceptions.open-queue', [
'tenant' => (string) $tenant->external_id, 'environment' => (string) $tenant->external_id,
]); ]);
} }
} }

View File

@ -6,7 +6,7 @@
use App\Filament\Resources\FindingExceptionResource; use App\Filament\Resources\FindingExceptionResource;
use App\Filament\Resources\FindingResource; use App\Filament\Resources\FindingResource;
use App\Filament\Widgets\Tenant\FindingExceptionStatsOverview; use App\Filament\Widgets\ManagedEnvironment\FindingExceptionStatsOverview;
use App\Models\FindingException; use App\Models\FindingException;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;

View File

@ -4,8 +4,8 @@
use App\Filament\Concerns\ResolvesPanelTenantContext; use App\Filament\Concerns\ResolvesPanelTenantContext;
use App\Filament\Resources\FindingResource; use App\Filament\Resources\FindingResource;
use App\Filament\Widgets\Tenant\BaselineCompareCoverageBanner; use App\Filament\Widgets\ManagedEnvironment\BaselineCompareCoverageBanner;
use App\Filament\Widgets\Tenant\FindingStatsOverview; use App\Filament\Widgets\ManagedEnvironment\FindingStatsOverview;
use App\Models\Finding; use App\Models\Finding;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\User; use App\Models\User;

View File

@ -2,9 +2,9 @@
namespace App\Filament\Resources; namespace App\Filament\Resources;
use App\Filament\Pages\CrossTenantComparePage; use App\Filament\Pages\CrossEnvironmentComparePage;
use App\Filament\Resources\TenantResource\Pages; use App\Filament\Resources\ManagedEnvironmentResource\Pages;
use App\Filament\Resources\TenantResource\RelationManagers; use App\Filament\Resources\ManagedEnvironmentResource\RelationManagers;
use App\Http\Controllers\RbacDelegatedAuthController; use App\Http\Controllers\RbacDelegatedAuthController;
use App\Jobs\BulkTenantSyncJob; use App\Jobs\BulkTenantSyncJob;
use App\Jobs\SyncPoliciesJob; use App\Jobs\SyncPoliciesJob;
@ -12,8 +12,8 @@
use App\Models\EntraRoleDefinition; use App\Models\EntraRoleDefinition;
use App\Models\ProviderConnection; use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\TenantTriageReview; use App\Models\ManagedEnvironmentTriageReview;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
@ -26,7 +26,7 @@
use App\Services\Intune\RbacOnboardingService; use App\Services\Intune\RbacOnboardingService;
use App\Services\OperationRunService; use App\Services\OperationRunService;
use App\Services\Operations\BulkSelectionIdentity; use App\Services\Operations\BulkSelectionIdentity;
use App\Services\PortfolioTriage\TenantTriageReviewService; use App\Services\PortfolioTriage\ManagedEnvironmentTriageReviewService;
use App\Services\Providers\AdminConsentUrlFactory; use App\Services\Providers\AdminConsentUrlFactory;
use App\Services\Tenants\TenantActionPolicySurface; use App\Services\Tenants\TenantActionPolicySurface;
use App\Services\Tenants\TenantOperabilityService; use App\Services\Tenants\TenantOperabilityService;
@ -49,7 +49,7 @@
use App\Support\OpsUx\OpsUxBrowserEvents; use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\Navigation\CanonicalNavigationContext; use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\PortfolioTriage\PortfolioArrivalContextToken; use App\Support\PortfolioTriage\PortfolioArrivalContextToken;
use App\Support\PortfolioTriage\TenantTriageReviewStateResolver; use App\Support\PortfolioTriage\ManagedEnvironmentTriageReviewStateResolver;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\Navigation\RelatedContextEntry; use App\Support\Navigation\RelatedContextEntry;
use App\Support\Navigation\UnavailableRelationState; use App\Support\Navigation\UnavailableRelationState;
@ -93,7 +93,7 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
use UnitEnum; use UnitEnum;
class TenantResource extends Resource class ManagedEnvironmentResource extends Resource
{ {
// ... [Properties Omitted for Brevity] ... // ... [Properties Omitted for Brevity] ...
protected static ?string $model = ManagedEnvironment::class; protected static ?string $model = ManagedEnvironment::class;
@ -119,7 +119,7 @@ class TenantResource extends Resource
private const string POSTURE_SNAPSHOT_REQUEST_KEY = 'tenant_resource.posture_snapshot'; private const string POSTURE_SNAPSHOT_REQUEST_KEY = 'tenant_resource.posture_snapshot';
private const string TRIAGE_REVIEW_SNAPSHOT_REQUEST_KEY = 'tenant_resource.triage_review_snapshot'; private const string TRIAGE_REVIEW_SNAPSHOT_REQUEST_KEY = 'managed_environment_resource.triage_review_snapshot';
/** /**
* @var array<string, true> * @var array<string, true>
@ -297,7 +297,7 @@ public static function makeTenantViewMarkReviewedAction(): Actions\Action
->modalDescription(fn (ManagedEnvironment $record): string => static::triageReviewActionModalDescription( ->modalDescription(fn (ManagedEnvironment $record): string => static::triageReviewActionModalDescription(
$record, $record,
static::tenantViewTriageState(), static::tenantViewTriageState(),
TenantTriageReview::STATE_REVIEWED, ManagedEnvironmentTriageReview::STATE_REVIEWED,
)) ))
->visible(fn (ManagedEnvironment $record): bool => static::selectedActionTriageReviewRowForTenant( ->visible(fn (ManagedEnvironment $record): bool => static::selectedActionTriageReviewRowForTenant(
$record, $record,
@ -308,11 +308,11 @@ public static function makeTenantViewMarkReviewedAction(): Actions\Action
->before(function (ManagedEnvironment $record): void { ->before(function (ManagedEnvironment $record): void {
static::authorizeTriageReviewAction($record); static::authorizeTriageReviewAction($record);
}) })
->action(function (ManagedEnvironment $record, TenantTriageReviewService $service): void { ->action(function (ManagedEnvironment $record, ManagedEnvironmentTriageReviewService $service): void {
static::handleTriageReviewMutation( static::handleTriageReviewMutation(
tenant: $record, tenant: $record,
triageState: static::tenantViewTriageState(), triageState: static::tenantViewTriageState(),
targetManualState: TenantTriageReview::STATE_REVIEWED, targetManualState: ManagedEnvironmentTriageReview::STATE_REVIEWED,
service: $service, service: $service,
); );
}); });
@ -329,7 +329,7 @@ public static function makeTenantViewMarkFollowUpNeededAction(): Actions\Action
->modalDescription(fn (ManagedEnvironment $record): string => static::triageReviewActionModalDescription( ->modalDescription(fn (ManagedEnvironment $record): string => static::triageReviewActionModalDescription(
$record, $record,
static::tenantViewTriageState(), static::tenantViewTriageState(),
TenantTriageReview::STATE_FOLLOW_UP_NEEDED, ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED,
)) ))
->visible(fn (ManagedEnvironment $record): bool => static::selectedActionTriageReviewRowForTenant( ->visible(fn (ManagedEnvironment $record): bool => static::selectedActionTriageReviewRowForTenant(
$record, $record,
@ -340,11 +340,11 @@ public static function makeTenantViewMarkFollowUpNeededAction(): Actions\Action
->before(function (ManagedEnvironment $record): void { ->before(function (ManagedEnvironment $record): void {
static::authorizeTriageReviewAction($record); static::authorizeTriageReviewAction($record);
}) })
->action(function (ManagedEnvironment $record, TenantTriageReviewService $service): void { ->action(function (ManagedEnvironment $record, ManagedEnvironmentTriageReviewService $service): void {
static::handleTriageReviewMutation( static::handleTriageReviewMutation(
tenant: $record, tenant: $record,
triageState: static::tenantViewTriageState(), triageState: static::tenantViewTriageState(),
targetManualState: TenantTriageReview::STATE_FOLLOW_UP_NEEDED, targetManualState: ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED,
service: $service, service: $service,
); );
}); });
@ -826,10 +826,10 @@ public static function table(Table $table): Table
$record, $record,
static::currentPortfolioTriageState($livewire), static::currentPortfolioTriageState($livewire),
)['derived_state'] ?? null) )['derived_state'] ?? null)
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantTriageReviewState)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::ManagedEnvironmentTriageReviewState))
->color(BadgeRenderer::color(BadgeDomain::TenantTriageReviewState)) ->color(BadgeRenderer::color(BadgeDomain::ManagedEnvironmentTriageReviewState))
->icon(BadgeRenderer::icon(BadgeDomain::TenantTriageReviewState)) ->icon(BadgeRenderer::icon(BadgeDomain::ManagedEnvironmentTriageReviewState))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantTriageReviewState)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::ManagedEnvironmentTriageReviewState))
->description(fn (ManagedEnvironment $record, mixed $livewire): ?string => static::triageReviewDescriptionForTenant( ->description(fn (ManagedEnvironment $record, mixed $livewire): ?string => static::triageReviewDescriptionForTenant(
$record, $record,
static::currentPortfolioTriageState($livewire), static::currentPortfolioTriageState($livewire),
@ -953,7 +953,7 @@ public static function table(Table $table): Table
return '#'; return '#';
} }
$triageState = $livewire instanceof Pages\ListTenants $triageState = $livewire instanceof Pages\ListManagedEnvironments
? static::currentPortfolioTriageState($livewire) ? static::currentPortfolioTriageState($livewire)
: []; : [];
@ -976,12 +976,12 @@ public static function table(Table $table): Table
->url(fn (ManagedEnvironment $record): string => static::relatedOnboardingDraftUrl($record) ?? route('admin.onboarding')) ->url(fn (ManagedEnvironment $record): string => static::relatedOnboardingDraftUrl($record) ?? route('admin.onboarding'))
->visible(fn (ManagedEnvironment $record): bool => static::relatedOnboardingDraftAction($record, TenantActionSurface::TenantIndexRow) instanceof TenantActionDescriptor ->visible(fn (ManagedEnvironment $record): bool => static::relatedOnboardingDraftAction($record, TenantActionSurface::TenantIndexRow) instanceof TenantActionDescriptor
&& static::tenantIndexPrimaryAction($record)?->key !== 'related_onboarding'), && static::tenantIndexPrimaryAction($record)?->key !== 'related_onboarding'),
Actions\Action::make('compareTenants') Actions\Action::make('compareEnvironments')
->label('Compare tenants') ->label('Compare tenants')
->icon('heroicon-o-scale') ->icon('heroicon-o-scale')
->color('gray') ->color('gray')
->url(function (ManagedEnvironment $record, mixed $livewire): string { ->url(function (ManagedEnvironment $record, mixed $livewire): string {
$triageState = $livewire instanceof Pages\ListTenants $triageState = $livewire instanceof Pages\ListManagedEnvironments
? static::currentPortfolioTriageState($livewire) ? static::currentPortfolioTriageState($livewire)
: []; : [];
@ -994,9 +994,9 @@ public static function table(Table $table): Table
$triageState = static::portfolioReturnFiltersFromRequest(request()->query()); $triageState = static::portfolioReturnFiltersFromRequest(request()->query());
} }
return static::crossTenantCompareOpenUrl($record, $triageState); return static::crossEnvironmentCompareOpenUrl($record, $triageState);
}) })
->visible(fn (ManagedEnvironment $record): bool => static::crossTenantCompareActionVisible($record)), ->visible(fn (ManagedEnvironment $record): bool => static::crossEnvironmentCompareActionVisible($record)),
UiEnforcement::forAction( UiEnforcement::forAction(
Actions\Action::make('edit') Actions\Action::make('edit')
->label('Edit') ->label('Edit')
@ -1019,7 +1019,7 @@ public static function table(Table $table): Table
->modalDescription(fn (ManagedEnvironment $record, mixed $livewire): string => static::triageReviewActionModalDescription( ->modalDescription(fn (ManagedEnvironment $record, mixed $livewire): string => static::triageReviewActionModalDescription(
$record, $record,
static::currentPortfolioTriageState($livewire), static::currentPortfolioTriageState($livewire),
TenantTriageReview::STATE_REVIEWED, ManagedEnvironmentTriageReview::STATE_REVIEWED,
)) ))
->visible(fn (ManagedEnvironment $record, mixed $livewire): bool => static::selectedTriageReviewRowForTenant( ->visible(fn (ManagedEnvironment $record, mixed $livewire): bool => static::selectedTriageReviewRowForTenant(
$record, $record,
@ -1033,12 +1033,12 @@ public static function table(Table $table): Table
->action(function ( ->action(function (
ManagedEnvironment $record, ManagedEnvironment $record,
mixed $livewire, mixed $livewire,
TenantTriageReviewService $service, ManagedEnvironmentTriageReviewService $service,
): void { ): void {
static::handleTriageReviewMutation( static::handleTriageReviewMutation(
tenant: $record, tenant: $record,
triageState: static::currentPortfolioTriageState($livewire), triageState: static::currentPortfolioTriageState($livewire),
targetManualState: TenantTriageReview::STATE_REVIEWED, targetManualState: ManagedEnvironmentTriageReview::STATE_REVIEWED,
service: $service, service: $service,
); );
}), }),
@ -1051,7 +1051,7 @@ public static function table(Table $table): Table
->modalDescription(fn (ManagedEnvironment $record, mixed $livewire): string => static::triageReviewActionModalDescription( ->modalDescription(fn (ManagedEnvironment $record, mixed $livewire): string => static::triageReviewActionModalDescription(
$record, $record,
static::currentPortfolioTriageState($livewire), static::currentPortfolioTriageState($livewire),
TenantTriageReview::STATE_FOLLOW_UP_NEEDED, ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED,
)) ))
->visible(fn (ManagedEnvironment $record, mixed $livewire): bool => static::selectedTriageReviewRowForTenant( ->visible(fn (ManagedEnvironment $record, mixed $livewire): bool => static::selectedTriageReviewRowForTenant(
$record, $record,
@ -1065,12 +1065,12 @@ public static function table(Table $table): Table
->action(function ( ->action(function (
ManagedEnvironment $record, ManagedEnvironment $record,
mixed $livewire, mixed $livewire,
TenantTriageReviewService $service, ManagedEnvironmentTriageReviewService $service,
): void { ): void {
static::handleTriageReviewMutation( static::handleTriageReviewMutation(
tenant: $record, tenant: $record,
triageState: static::currentPortfolioTriageState($livewire), triageState: static::currentPortfolioTriageState($livewire),
targetManualState: TenantTriageReview::STATE_FOLLOW_UP_NEEDED, targetManualState: ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED,
service: $service, service: $service,
); );
}), }),
@ -1149,13 +1149,13 @@ public static function table(Table $table): Table
->visible(fn (): bool => auth()->user() instanceof User) ->visible(fn (): bool => auth()->user() instanceof User)
->authorize(fn (): bool => auth()->user() instanceof User) ->authorize(fn (): bool => auth()->user() instanceof User)
->extraAttributes(fn (mixed $livewire): array => [ ->extraAttributes(fn (mixed $livewire): array => [
'x-bind:aria-disabled' => static::crossTenantCompareBulkClientDisabledExpression($livewire).' ? true : null', 'x-bind:aria-disabled' => static::crossEnvironmentCompareBulkClientDisabledExpression($livewire).' ? true : null',
'x-bind:disabled' => static::crossTenantCompareBulkClientDisabledExpression($livewire), 'x-bind:disabled' => static::crossEnvironmentCompareBulkClientDisabledExpression($livewire),
'x-bind:title' => static::crossTenantCompareBulkClientTooltipExpression($livewire), 'x-bind:title' => static::crossEnvironmentCompareBulkClientTooltipExpression($livewire),
'x-bind:class' => "{ 'fi-disabled': ".static::crossTenantCompareBulkClientDisabledExpression($livewire).' }', 'x-bind:class' => "{ 'fi-disabled': ".static::crossEnvironmentCompareBulkClientDisabledExpression($livewire).' }',
]) ])
->action(function (Collection $records, mixed $livewire): void { ->action(function (Collection $records, mixed $livewire): void {
$disabledReason = static::crossTenantCompareBulkDisabledReason($records); $disabledReason = static::crossEnvironmentCompareBulkDisabledReason($records);
if ($disabledReason !== null) { if ($disabledReason !== null) {
Notification::make() Notification::make()
@ -1167,7 +1167,7 @@ public static function table(Table $table): Table
} }
if (method_exists($livewire, 'redirect')) { if (method_exists($livewire, 'redirect')) {
$livewire->redirect(static::crossTenantCompareBulkOpenUrl($records, $livewire), navigate: true); $livewire->redirect(static::crossEnvironmentCompareBulkOpenUrl($records, $livewire), navigate: true);
} }
}), }),
Actions\BulkAction::make('syncSelected') Actions\BulkAction::make('syncSelected')
@ -1324,7 +1324,7 @@ public static function sanitizeReviewStates(mixed $value): array
{ {
return static::sanitizeRequestedValues( return static::sanitizeRequestedValues(
$value, $value,
TenantTriageReview::DERIVED_STATES, ManagedEnvironmentTriageReview::DERIVED_STATES,
); );
} }
@ -1366,10 +1366,10 @@ public static function tenantDashboardOpenUrl(ManagedEnvironment $record, array
* triage_sort?: string|null * triage_sort?: string|null
* } $triageState * } $triageState
*/ */
public static function crossTenantCompareOpenUrl(ManagedEnvironment $record, array $triageState = []): string public static function crossEnvironmentCompareOpenUrl(ManagedEnvironment $record, array $triageState = []): string
{ {
return static::crossTenantCompareOpenUrlForSelection( return static::crossEnvironmentCompareOpenUrlForSelection(
targetTenant: $record, targetEnvironment: $record,
triageState: $triageState, triageState: $triageState,
); );
} }
@ -1382,10 +1382,10 @@ public static function crossTenantCompareOpenUrl(ManagedEnvironment $record, arr
* triage_sort?: string|null * triage_sort?: string|null
* } $triageState * } $triageState
*/ */
public static function crossTenantCompareOpenUrlForSelection( public static function crossEnvironmentCompareOpenUrlForSelection(
ManagedEnvironment $targetTenant, ManagedEnvironment $targetEnvironment,
array $triageState = [], array $triageState = [],
?ManagedEnvironment $sourceTenant = null, ?ManagedEnvironment $sourceEnvironment = null,
): string { ): string {
$normalizedState = static::portfolioReturnFilters( $normalizedState = static::portfolioReturnFilters(
static::sanitizeBackupPostures($triageState['backup_posture'] ?? []), static::sanitizeBackupPostures($triageState['backup_posture'] ?? []),
@ -1394,12 +1394,12 @@ public static function crossTenantCompareOpenUrlForSelection(
static::sanitizeTriageSort($triageState['triage_sort'] ?? null), static::sanitizeTriageSort($triageState['triage_sort'] ?? null),
); );
return CrossTenantComparePage::launchUrl( return CrossEnvironmentComparePage::launchUrl(
sourceTenant: $sourceTenant, sourceEnvironment: $sourceEnvironment,
targetTenant: $targetTenant, targetEnvironment: $targetEnvironment,
navigationContext: CanonicalNavigationContext::forTenantRegistry( navigationContext: CanonicalNavigationContext::forTenantRegistry(
backLinkUrl: static::getUrl(panel: 'admin', parameters: $normalizedState), backLinkUrl: static::getUrl(panel: 'admin', parameters: $normalizedState),
tenantId: $sourceTenant instanceof ManagedEnvironment ? null : (int) $targetTenant->getKey(), tenantId: $sourceEnvironment instanceof ManagedEnvironment ? null : (int) $targetEnvironment->getKey(),
), ),
); );
} }
@ -1494,7 +1494,7 @@ private static function portfolioReturnFiltersFromRequest(array $query): array
); );
} }
private static function crossTenantCompareActionVisible(ManagedEnvironment $record): bool private static function crossEnvironmentCompareActionVisible(ManagedEnvironment $record): bool
{ {
if (! $record->isActive()) { if (! $record->isActive()) {
return false; return false;
@ -1533,7 +1533,7 @@ private static function crossTenantCompareActionVisible(ManagedEnvironment $reco
&& $tenantResolver->can($user, $record, Capabilities::TENANT_VIEW); && $tenantResolver->can($user, $record, Capabilities::TENANT_VIEW);
} }
private static function crossTenantCompareBulkDisabledReason(Collection $records): ?string private static function crossEnvironmentCompareBulkDisabledReason(Collection $records): ?string
{ {
$user = auth()->user(); $user = auth()->user();
@ -1541,20 +1541,20 @@ private static function crossTenantCompareBulkDisabledReason(Collection $records
return UiTooltips::insufficientPermission(); return UiTooltips::insufficientPermission();
} }
$tenants = $records $environments = $records
->filter(fn ($record): bool => $record instanceof ManagedEnvironment) ->filter(fn ($record): bool => $record instanceof ManagedEnvironment)
->values(); ->values();
if ($records->count() !== 2 || $tenants->count() !== 2) { if ($records->count() !== 2 || $environments->count() !== 2) {
return 'Select exactly two tenants to compare.'; return 'Select exactly two environments to compare.';
} }
if ($tenants->contains(fn (ManagedEnvironment $tenant): bool => ! $tenant->isActive())) { if ($environments->contains(fn (ManagedEnvironment $environment): bool => ! $environment->isActive())) {
return 'Only active tenants can be compared.'; return 'Only active environments can be compared.';
} }
$workspaceIds = $tenants $workspaceIds = $environments
->map(fn (ManagedEnvironment $tenant): int => (int) $tenant->workspace_id) ->map(fn (ManagedEnvironment $environment): int => (int) $environment->workspace_id)
->unique() ->unique()
->values(); ->values();
@ -1578,32 +1578,32 @@ private static function crossTenantCompareBulkDisabledReason(Collection $records
return UiTooltips::insufficientPermission(); return UiTooltips::insufficientPermission();
} }
/** @var CapabilityResolver $tenantResolver */ /** @var CapabilityResolver $environmentResolver */
$tenantResolver = app(CapabilityResolver::class); $environmentResolver = app(CapabilityResolver::class);
$isDenied = $tenants->contains(fn (ManagedEnvironment $tenant): bool => ! $user->canAccessTenant($tenant) $isDenied = $environments->contains(fn (ManagedEnvironment $environment): bool => ! $user->canAccessTenant($environment)
|| ! $tenantResolver->can($user, $tenant, Capabilities::TENANT_VIEW)); || ! $environmentResolver->can($user, $environment, Capabilities::TENANT_VIEW));
return $isDenied ? UiTooltips::insufficientPermission() : null; return $isDenied ? UiTooltips::insufficientPermission() : null;
} }
private static function crossTenantCompareBulkClientDisabledExpression(mixed $livewire): string private static function crossEnvironmentCompareBulkClientDisabledExpression(mixed $livewire): string
{ {
$containsInactiveSelection = static::crossTenantCompareBulkContainsInactiveSelectionExpression($livewire); $containsInactiveSelection = static::crossEnvironmentCompareBulkContainsInactiveSelectionExpression($livewire);
return "getSelectedRecordsCount() !== 2 || {$containsInactiveSelection}"; return "getSelectedRecordsCount() !== 2 || {$containsInactiveSelection}";
} }
private static function crossTenantCompareBulkClientTooltipExpression(mixed $livewire): string private static function crossEnvironmentCompareBulkClientTooltipExpression(mixed $livewire): string
{ {
$containsInactiveSelection = static::crossTenantCompareBulkContainsInactiveSelectionExpression($livewire); $containsInactiveSelection = static::crossEnvironmentCompareBulkContainsInactiveSelectionExpression($livewire);
return "getSelectedRecordsCount() !== 2 ? 'Select exactly two tenants to compare.' : ({$containsInactiveSelection} ? 'Only active tenants can be compared.' : null)"; return "getSelectedRecordsCount() !== 2 ? 'Select exactly two environments to compare.' : ({$containsInactiveSelection} ? 'Only active environments can be compared.' : null)";
} }
private static function crossTenantCompareBulkContainsInactiveSelectionExpression(mixed $livewire): string private static function crossEnvironmentCompareBulkContainsInactiveSelectionExpression(mixed $livewire): string
{ {
$inactiveRecordKeys = \Illuminate\Support\Js::from(static::crossTenantCompareInactiveSelectionRecordKeys($livewire)); $inactiveRecordKeys = \Illuminate\Support\Js::from(static::crossEnvironmentCompareInactiveSelectionRecordKeys($livewire));
return "[...selectedRecords].some((key) => {$inactiveRecordKeys}.includes(key))"; return "[...selectedRecords].some((key) => {$inactiveRecordKeys}.includes(key))";
} }
@ -1611,7 +1611,7 @@ private static function crossTenantCompareBulkContainsInactiveSelectionExpressio
/** /**
* @return list<string> * @return list<string>
*/ */
private static function crossTenantCompareInactiveSelectionRecordKeys(mixed $livewire): array private static function crossEnvironmentCompareInactiveSelectionRecordKeys(mixed $livewire): array
{ {
if (! $livewire instanceof HasTable || ! method_exists($livewire, 'getTableRecordKey')) { if (! $livewire instanceof HasTable || ! method_exists($livewire, 'getTableRecordKey')) {
return []; return [];
@ -1630,9 +1630,9 @@ private static function crossTenantCompareInactiveSelectionRecordKeys(mixed $liv
->all(); ->all();
} }
private static function crossTenantCompareBulkOpenUrl(Collection $records, mixed $livewire): string private static function crossEnvironmentCompareBulkOpenUrl(Collection $records, mixed $livewire): string
{ {
$triageState = $livewire instanceof Pages\ListTenants $triageState = $livewire instanceof Pages\ListManagedEnvironments
? static::currentPortfolioTriageState($livewire) ? static::currentPortfolioTriageState($livewire)
: []; : [];
@ -1649,10 +1649,10 @@ private static function crossTenantCompareBulkOpenUrl(Collection $records, mixed
->filter(fn ($record): bool => $record instanceof ManagedEnvironment) ->filter(fn ($record): bool => $record instanceof ManagedEnvironment)
->values(); ->values();
return static::crossTenantCompareOpenUrlForSelection( return static::crossEnvironmentCompareOpenUrlForSelection(
targetTenant: $tenants->get(1), targetEnvironment: $tenants->get(1),
triageState: $triageState, triageState: $triageState,
sourceTenant: $tenants->get(0), sourceEnvironment: $tenants->get(0),
); );
} }
@ -1808,9 +1808,9 @@ private static function recoveryEvidenceForTenant(ManagedEnvironment $tenant): ?
*/ */
private static function reviewStateOptions(): array private static function reviewStateOptions(): array
{ {
return collect(TenantTriageReview::DERIVED_STATES) return collect(ManagedEnvironmentTriageReview::DERIVED_STATES)
->mapWithKeys(static fn (string $state): array => [ ->mapWithKeys(static fn (string $state): array => [
$state => BadgeRenderer::spec(BadgeDomain::TenantTriageReviewState, $state)->label, $state => BadgeRenderer::spec(BadgeDomain::ManagedEnvironmentTriageReviewState, $state)->label,
]) ])
->all(); ->all();
} }
@ -1854,7 +1854,7 @@ private static function triageReviewSnapshot(): array
return $resolved; return $resolved;
} }
$resolved = app(TenantTriageReviewStateResolver::class)->resolveMany( $resolved = app(ManagedEnvironmentTriageReviewStateResolver::class)->resolveMany(
workspaceId: $workspaceId, workspaceId: $workspaceId,
tenantIds: $snapshot['tenant_ids'], tenantIds: $snapshot['tenant_ids'],
backupHealthByTenant: $snapshot['backup_health'], backupHealthByTenant: $snapshot['backup_health'],
@ -2035,8 +2035,8 @@ private static function triageReviewActionModalDescription(
return 'This triage slice no longer points at a current visible concern.'; return 'This triage slice no longer points at a current visible concern.';
} }
$currentLabel = BadgeRenderer::spec(BadgeDomain::TenantTriageReviewState, $row['derived_state'])->label; $currentLabel = BadgeRenderer::spec(BadgeDomain::ManagedEnvironmentTriageReviewState, $row['derived_state'])->label;
$targetLabel = BadgeRenderer::spec(BadgeDomain::TenantTriageReviewState, $targetManualState)->label; $targetLabel = BadgeRenderer::spec(BadgeDomain::ManagedEnvironmentTriageReviewState, $targetManualState)->label;
return implode("\n\n", [ return implode("\n\n", [
'Concern family: '.$row['concern_family_label'], 'Concern family: '.$row['concern_family_label'],
@ -2071,7 +2071,7 @@ private static function triageReviewActionIsDisabled(ManagedEnvironment $tenant)
return true; return true;
} }
return ! $resolver->can($user, $tenant, Capabilities::TENANT_TRIAGE_REVIEW_MANAGE); return ! $resolver->can($user, $tenant, Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE);
} }
private static function triageReviewActionTooltip(ManagedEnvironment $tenant): ?string private static function triageReviewActionTooltip(ManagedEnvironment $tenant): ?string
@ -2084,7 +2084,7 @@ private static function triageReviewActionTooltip(ManagedEnvironment $tenant): ?
$resolver = app(CapabilityResolver::class); $resolver = app(CapabilityResolver::class);
if ($resolver->isMember($user, $tenant) && ! $resolver->can($user, $tenant, Capabilities::TENANT_TRIAGE_REVIEW_MANAGE)) { if ($resolver->isMember($user, $tenant) && ! $resolver->can($user, $tenant, Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE)) {
return UiTooltips::insufficientPermission(); return UiTooltips::insufficientPermission();
} }
@ -2105,7 +2105,7 @@ private static function authorizeTriageReviewAction(ManagedEnvironment $tenant):
abort(404); abort(404);
} }
if (! $resolver->can($user, $tenant, Capabilities::TENANT_TRIAGE_REVIEW_MANAGE)) { if (! $resolver->can($user, $tenant, Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE)) {
abort(403); abort(403);
} }
} }
@ -2122,7 +2122,7 @@ private static function handleTriageReviewMutation(
ManagedEnvironment $tenant, ManagedEnvironment $tenant,
array $triageState, array $triageState,
string $targetManualState, string $targetManualState,
TenantTriageReviewService $service, ManagedEnvironmentTriageReviewService $service,
): void { ): void {
$row = static::selectedActionTriageReviewRowForTenant($tenant, $triageState); $row = static::selectedActionTriageReviewRowForTenant($tenant, $triageState);
@ -2141,14 +2141,14 @@ private static function handleTriageReviewMutation(
$recoveryEvidence = static::recoveryEvidenceForTenant($tenant); $recoveryEvidence = static::recoveryEvidenceForTenant($tenant);
$review = match ($targetManualState) { $review = match ($targetManualState) {
TenantTriageReview::STATE_REVIEWED => $service->markReviewed( ManagedEnvironmentTriageReview::STATE_REVIEWED => $service->markReviewed(
tenant: $tenant, tenant: $tenant,
concernFamily: (string) $row['concern_family'], concernFamily: (string) $row['concern_family'],
backupHealth: $backupHealth, backupHealth: $backupHealth,
recoveryEvidence: $recoveryEvidence, recoveryEvidence: $recoveryEvidence,
actor: $actor instanceof User ? $actor : null, actor: $actor instanceof User ? $actor : null,
), ),
TenantTriageReview::STATE_FOLLOW_UP_NEEDED => $service->markFollowUpNeeded( ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED => $service->markFollowUpNeeded(
tenant: $tenant, tenant: $tenant,
concernFamily: (string) $row['concern_family'], concernFamily: (string) $row['concern_family'],
backupHealth: $backupHealth, backupHealth: $backupHealth,
@ -2158,7 +2158,7 @@ private static function handleTriageReviewMutation(
default => null, default => null,
}; };
if (! $review instanceof TenantTriageReview) { if (! $review instanceof ManagedEnvironmentTriageReview) {
return; return;
} }
@ -2169,7 +2169,7 @@ private static function handleTriageReviewMutation(
->body(sprintf( ->body(sprintf(
'%s is now %s for %s.', '%s is now %s for %s.',
$tenant->name, $tenant->name,
BadgeRenderer::spec(BadgeDomain::TenantTriageReviewState, $review->current_state)->label, BadgeRenderer::spec(BadgeDomain::ManagedEnvironmentTriageReviewState, $review->current_state)->label,
static::triageConcernFamilyLabel((string) $review->concern_family), static::triageConcernFamilyLabel((string) $review->concern_family),
)) ))
->success() ->success()
@ -2197,7 +2197,7 @@ private static function selectedActionTriageReviewRowForTenant(ManagedEnvironmen
$backupHealth = app(TenantBackupHealthResolver::class)->assess($tenant); $backupHealth = app(TenantBackupHealthResolver::class)->assess($tenant);
$recoveryEvidence = app(RestoreSafetyResolver::class)->dashboardRecoveryEvidence($tenant); $recoveryEvidence = app(RestoreSafetyResolver::class)->dashboardRecoveryEvidence($tenant);
$rows = app(TenantTriageReviewStateResolver::class)->resolveMany( $rows = app(ManagedEnvironmentTriageReviewStateResolver::class)->resolveMany(
workspaceId: $workspaceId, workspaceId: $workspaceId,
tenantIds: [$tenantId], tenantIds: [$tenantId],
backupHealthByTenant: [$tenantId => $backupHealth], backupHealthByTenant: [$tenantId => $backupHealth],
@ -2640,10 +2640,10 @@ public static function infolist(Schema $schema): Schema
->formatStateUsing(fn ($state) => is_array($state) ? implode(', ', $state) : (string) $state), ->formatStateUsing(fn ($state) => is_array($state) ? implode(', ', $state) : (string) $state),
Infolists\Components\TextEntry::make('status') Infolists\Components\TextEntry::make('status')
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantPermissionStatus)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::ManagedEnvironmentPermissionStatus))
->color(BadgeRenderer::color(BadgeDomain::TenantPermissionStatus)) ->color(BadgeRenderer::color(BadgeDomain::ManagedEnvironmentPermissionStatus))
->icon(BadgeRenderer::icon(BadgeDomain::TenantPermissionStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::ManagedEnvironmentPermissionStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantPermissionStatus)), ->iconColor(BadgeRenderer::iconColor(BadgeDomain::ManagedEnvironmentPermissionStatus)),
]) ])
->columnSpanFull(), ->columnSpanFull(),
]) ])
@ -2730,7 +2730,7 @@ public static function tenantIndexPrimaryAction(ManagedEnvironment $tenant): ?Te
return $catalog[1] ?? null; return $catalog[1] ?? null;
} }
public static function relatedOnboardingDraft(ManagedEnvironment $tenant): ?TenantOnboardingSession public static function relatedOnboardingDraft(ManagedEnvironment $tenant): ?ManagedEnvironmentOnboardingSession
{ {
return static::tenantActionPolicy()->relatedOnboardingDraft($tenant); return static::tenantActionPolicy()->relatedOnboardingDraft($tenant);
} }
@ -2749,7 +2749,7 @@ public static function relatedOnboardingDraftUrl(ManagedEnvironment $tenant): ?s
{ {
$draft = static::relatedOnboardingDraft($tenant); $draft = static::relatedOnboardingDraft($tenant);
if (! $draft instanceof TenantOnboardingSession) { if (! $draft instanceof ManagedEnvironmentOnboardingSession) {
return null; return null;
} }
@ -3119,17 +3119,17 @@ private static function validatedLifecycleReason(string $reason, string $field):
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListTenants::route('/'), 'index' => Pages\ListManagedEnvironments::route('/'),
'view' => Pages\ViewTenant::route('/{record}'), 'view' => Pages\ViewManagedEnvironment::route('/{record}'),
'edit' => Pages\EditTenant::route('/{record}/edit'), 'edit' => Pages\EditManagedEnvironment::route('/{record}/edit'),
'memberships' => Pages\ManageTenantMemberships::route('/{record}/memberships'), 'memberships' => Pages\ManageEnvironmentAccessScopes::route('/{record}/memberships'),
]; ];
} }
public static function getRelations(): array public static function getRelations(): array
{ {
return [ return [
RelationManagers\TenantMembershipsRelationManager::class, RelationManagers\ManagedEnvironmentMembershipsRelationManager::class,
]; ];
} }

View File

@ -1,26 +1,26 @@
<?php <?php
namespace App\Filament\Resources\TenantResource\Pages; namespace App\Filament\Resources\ManagedEnvironmentResource\Pages;
use App\Filament\Resources\TenantResource; use App\Filament\Resources\ManagedEnvironmentResource;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Support\Tenants\TenantActionSurface; use App\Support\Tenants\TenantActionSurface;
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
class EditTenant extends EditRecord class EditManagedEnvironment extends EditRecord
{ {
protected static string $resource = TenantResource::class; protected static string $resource = ManagedEnvironmentResource::class;
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return array_values(array_filter([ return array_values(array_filter([
Actions\ActionGroup::make([ Actions\ActionGroup::make([
TenantResource::makeRestoreTenantAction( ManagedEnvironmentResource::makeRestoreTenantAction(
TenantActionSurface::TenantEditHeader, TenantActionSurface::TenantEditHeader,
'You do not have permission to restore tenants.', 'You do not have permission to restore tenants.',
), ),
TenantResource::makeArchiveTenantAction( ManagedEnvironmentResource::makeArchiveTenantAction(
TenantActionSurface::TenantEditHeader, TenantActionSurface::TenantEditHeader,
'You do not have permission to archive tenants.', 'You do not have permission to archive tenants.',
), ),
@ -30,7 +30,7 @@ protected function getHeaderActions(): array
->color('gray') ->color('gray')
->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment ->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment
&& in_array( && in_array(
TenantResource::lifecycleActionDescriptor($this->getRecord(), TenantActionSurface::TenantEditHeader)?->key, ManagedEnvironmentResource::lifecycleActionDescriptor($this->getRecord(), TenantActionSurface::TenantEditHeader)?->key,
['archive', 'restore'], ['archive', 'restore'],
true, true,
)), )),

View File

@ -2,9 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Resources\TenantResource\Pages; namespace App\Filament\Resources\ManagedEnvironmentResource\Pages;
use App\Filament\Resources\TenantResource; use App\Filament\Resources\ManagedEnvironmentResource;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Onboarding\OnboardingDraftResolver; use App\Services\Onboarding\OnboardingDraftResolver;
@ -13,9 +13,9 @@
use Filament\Actions; use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
class ListTenants extends ListRecords class ListManagedEnvironments extends ListRecords
{ {
protected static string $resource = TenantResource::class; protected static string $resource = ManagedEnvironmentResource::class;
public function mount(): void public function mount(): void
{ {
@ -64,7 +64,7 @@ protected function getTableEmptyStateDescription(): ?string
private function makeOnboardingEntryAction(): Actions\Action private function makeOnboardingEntryAction(): Actions\Action
{ {
$descriptor = TenantResource::tenantActionPolicy()->onboardingEntryDescriptor($this->accessibleResumableDraftCount()); $descriptor = ManagedEnvironmentResource::tenantActionPolicy()->onboardingEntryDescriptor($this->accessibleResumableDraftCount());
return Actions\Action::make('add_tenant') return Actions\Action::make('add_tenant')
->label($descriptor->label) ->label($descriptor->label)
@ -92,10 +92,10 @@ private function applyRequestedTriageIntent(): void
return; return;
} }
$backupPostures = TenantResource::sanitizeBackupPostures(request()->query('backup_posture')); $backupPostures = ManagedEnvironmentResource::sanitizeBackupPostures(request()->query('backup_posture'));
$recoveryEvidence = TenantResource::sanitizeRecoveryEvidenceStates(request()->query('recovery_evidence')); $recoveryEvidence = ManagedEnvironmentResource::sanitizeRecoveryEvidenceStates(request()->query('recovery_evidence'));
$reviewStates = TenantResource::sanitizeReviewStates(request()->query('review_state')); $reviewStates = ManagedEnvironmentResource::sanitizeReviewStates(request()->query('review_state'));
$triageSort = TenantResource::sanitizeTriageSort(request()->query('triage_sort')); $triageSort = ManagedEnvironmentResource::sanitizeTriageSort(request()->query('triage_sort'));
foreach (['backup_posture', 'recovery_evidence', 'review_state', 'triage_sort'] as $filterName) { foreach (['backup_posture', 'recovery_evidence', 'review_state', 'triage_sort'] as $filterName) {
data_forget($this->tableFilters, $filterName); data_forget($this->tableFilters, $filterName);
@ -139,10 +139,10 @@ private function hasActiveTriageEmptyState(): bool
public function currentPortfolioTriageReturnState(): array public function currentPortfolioTriageReturnState(): array
{ {
return [ return [
'backup_posture' => TenantResource::sanitizeBackupPostures(data_get($this->tableFilters, 'backup_posture.values', [])), 'backup_posture' => ManagedEnvironmentResource::sanitizeBackupPostures(data_get($this->tableFilters, 'backup_posture.values', [])),
'recovery_evidence' => TenantResource::sanitizeRecoveryEvidenceStates(data_get($this->tableFilters, 'recovery_evidence.values', [])), 'recovery_evidence' => ManagedEnvironmentResource::sanitizeRecoveryEvidenceStates(data_get($this->tableFilters, 'recovery_evidence.values', [])),
'review_state' => TenantResource::sanitizeReviewStates(data_get($this->tableFilters, 'review_state.values', [])), 'review_state' => ManagedEnvironmentResource::sanitizeReviewStates(data_get($this->tableFilters, 'review_state.values', [])),
'triage_sort' => TenantResource::sanitizeTriageSort(data_get($this->tableFilters, 'triage_sort.value')), 'triage_sort' => ManagedEnvironmentResource::sanitizeTriageSort(data_get($this->tableFilters, 'triage_sort.value')),
]; ];
} }

View File

@ -1,18 +1,18 @@
<?php <?php
namespace App\Filament\Resources\TenantResource\Pages; namespace App\Filament\Resources\ManagedEnvironmentResource\Pages;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Support\ManagedEnvironmentLinks; use App\Support\ManagedEnvironmentLinks;
use Filament\Actions\Action; use Filament\Actions\Action;
class ManageTenantMemberships extends ViewTenant class ManageEnvironmentAccessScopes extends ViewManagedEnvironment
{ {
protected static ?string $title = 'Manage environment access scope'; protected static ?string $title = 'Manage environment access scope';
public function mount(int|string|ManagedEnvironment $tenant): void public function mount(int|string|ManagedEnvironment $environment): void
{ {
parent::mount($tenant instanceof ManagedEnvironment ? (string) $tenant->getRouteKey() : $tenant); parent::mount($environment instanceof ManagedEnvironment ? (string) $environment->getRouteKey() : $environment);
} }
public function getSubheading(): ?string public function getSubheading(): ?string

View File

@ -1,12 +1,12 @@
<?php <?php
namespace App\Filament\Resources\TenantResource\Pages; namespace App\Filament\Resources\ManagedEnvironmentResource\Pages;
use App\Filament\Resources\TenantResource; use App\Filament\Resources\ManagedEnvironmentResource;
use App\Filament\Widgets\Tenant\AdminRolesSummaryWidget; use App\Filament\Widgets\ManagedEnvironment\AdminRolesSummaryWidget;
use App\Filament\Widgets\Tenant\RecentOperationsSummary; use App\Filament\Widgets\ManagedEnvironment\RecentOperationsSummary;
use App\Filament\Widgets\Tenant\TenantArchivedBanner; use App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentArchivedBanner;
use App\Filament\Widgets\Tenant\TenantVerificationReport; use App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentVerificationReport;
use App\Jobs\RefreshTenantRbacHealthJob; use App\Jobs\RefreshTenantRbacHealthJob;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\User; use App\Models\User;
@ -22,9 +22,9 @@
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Pages\ViewRecord; use Filament\Resources\Pages\ViewRecord;
class ViewTenant extends ViewRecord class ViewManagedEnvironment extends ViewRecord
{ {
protected static string $resource = TenantResource::class; protected static string $resource = ManagedEnvironmentResource::class;
public static function verificationHeaderActionLabel(): string public static function verificationHeaderActionLabel(): string
{ {
@ -44,9 +44,9 @@ public function getHeaderWidgetsColumns(): int|array
protected function getHeaderWidgets(): array protected function getHeaderWidgets(): array
{ {
return [ return [
TenantArchivedBanner::class, ManagedEnvironmentArchivedBanner::class,
RecentOperationsSummary::class, RecentOperationsSummary::class,
TenantVerificationReport::class, ManagedEnvironmentVerificationReport::class,
AdminRolesSummaryWidget::class, AdminRolesSummaryWidget::class,
]; ];
} }
@ -54,27 +54,27 @@ protected function getHeaderWidgets(): array
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return array_values(array_filter([ return array_values(array_filter([
TenantResource::makeMembershipsAction(), ManagedEnvironmentResource::makeMembershipsAction(),
Actions\ActionGroup::make([ Actions\ActionGroup::make([
TenantResource::makeAdminConsentAction(), ManagedEnvironmentResource::makeAdminConsentAction(),
TenantResource::makeOpenInEntraAction(), ManagedEnvironmentResource::makeOpenInEntraAction(),
]) ])
->label('External links') ->label('External links')
->icon('heroicon-o-arrow-top-right-on-square') ->icon('heroicon-o-arrow-top-right-on-square')
->color('gray') ->color('gray')
->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment ->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment
&& TenantResource::tenantViewExternalGroupVisible($this->getRecord())), && ManagedEnvironmentResource::tenantViewExternalGroupVisible($this->getRecord())),
Actions\ActionGroup::make([ Actions\ActionGroup::make([
TenantResource::makeSyncTenantAction(), ManagedEnvironmentResource::makeSyncTenantAction(),
TenantResource::makeVerifyConfigurationAction('tenant_view_header'), ManagedEnvironmentResource::makeVerifyConfigurationAction('tenant_view_header'),
TenantResource::rbacAction(), ManagedEnvironmentResource::rbacAction(),
UiEnforcement::forAction( UiEnforcement::forAction(
Actions\Action::make('refresh_rbac') Actions\Action::make('refresh_rbac')
->label('Refresh RBAC status') ->label('Refresh RBAC status')
->icon('heroicon-o-arrow-path') ->icon('heroicon-o-arrow-path')
->color('primary') ->color('primary')
->requiresConfirmation() ->requiresConfirmation()
->visible(fn (ManagedEnvironment $record): bool => TenantResource::tenantSetupMutationVisible($record)) ->visible(fn (ManagedEnvironment $record): bool => ManagedEnvironmentResource::tenantSetupMutationVisible($record))
->action(function (ManagedEnvironment $record): void { ->action(function (ManagedEnvironment $record): void {
$user = auth()->user(); $user = auth()->user();
@ -140,27 +140,27 @@ protected function getHeaderActions(): array
->icon('heroicon-o-wrench-screwdriver') ->icon('heroicon-o-wrench-screwdriver')
->color('gray') ->color('gray')
->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment ->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment
&& TenantResource::tenantViewSetupGroupVisible($this->getRecord())), && ManagedEnvironmentResource::tenantViewSetupGroupVisible($this->getRecord())),
Actions\ActionGroup::make([ Actions\ActionGroup::make([
TenantResource::makeTenantViewMarkReviewedAction(), ManagedEnvironmentResource::makeTenantViewMarkReviewedAction(),
TenantResource::makeTenantViewMarkFollowUpNeededAction(), ManagedEnvironmentResource::makeTenantViewMarkFollowUpNeededAction(),
]) ])
->label('Triage') ->label('Triage')
->icon('heroicon-o-check-circle') ->icon('heroicon-o-check-circle')
->color('gray') ->color('gray')
->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment ->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment
&& TenantResource::tenantViewTriageGroupVisible($this->getRecord())), && ManagedEnvironmentResource::tenantViewTriageGroupVisible($this->getRecord())),
Actions\ActionGroup::make([ Actions\ActionGroup::make([
TenantResource::makeRestoreTenantAction(TenantActionSurface::TenantViewHeader), ManagedEnvironmentResource::makeRestoreTenantAction(TenantActionSurface::TenantViewHeader),
TenantResource::makeRestoreTenantToWorkspaceAction(), ManagedEnvironmentResource::makeRestoreTenantToWorkspaceAction(),
TenantResource::makeRemoveTenantFromWorkspaceAction(), ManagedEnvironmentResource::makeRemoveTenantFromWorkspaceAction(),
TenantResource::makeArchiveTenantAction(TenantActionSurface::TenantViewHeader), ManagedEnvironmentResource::makeArchiveTenantAction(TenantActionSurface::TenantViewHeader),
]) ])
->label('Lifecycle') ->label('Lifecycle')
->icon('heroicon-o-archive-box') ->icon('heroicon-o-archive-box')
->color('gray') ->color('gray')
->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment ->visible(fn (): bool => $this->getRecord() instanceof ManagedEnvironment
&& TenantResource::tenantViewLifecycleGroupVisible($this->getRecord())), && ManagedEnvironmentResource::tenantViewLifecycleGroupVisible($this->getRecord())),
])); ]));
} }
} }

View File

@ -1,13 +1,13 @@
<?php <?php
namespace App\Filament\Resources\TenantResource\RelationManagers; namespace App\Filament\Resources\ManagedEnvironmentResource\RelationManagers;
use App\Filament\Resources\TenantResource\Pages\ManageTenantMemberships; use App\Filament\Resources\ManagedEnvironmentResource\Pages\ManageEnvironmentAccessScopes;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership; use App\Models\ManagedEnvironmentMembership;
use App\Models\User; use App\Models\User;
use App\Services\Auth\CapabilityResolver; use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\TenantMembershipManager; use App\Services\Auth\ManagedEnvironmentMembershipManager;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration; use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
@ -22,7 +22,7 @@
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class TenantMembershipsRelationManager extends RelationManager class ManagedEnvironmentMembershipsRelationManager extends RelationManager
{ {
protected static string $relationship = 'memberships'; protected static string $relationship = 'memberships';
@ -42,7 +42,7 @@ public static function canViewForRecord(Model $ownerRecord, string $pageClass):
return false; return false;
} }
if ($pageClass !== ManageTenantMemberships::class) { if ($pageClass !== ManageEnvironmentAccessScopes::class) {
return false; return false;
} }
@ -103,7 +103,7 @@ public function table(Table $table): Table
->searchable() ->searchable()
->options(fn (): array => $this->workspaceMemberOptions()), ->options(fn (): array => $this->workspaceMemberOptions()),
]) ])
->action(function (array $data, TenantMembershipManager $manager): void { ->action(function (array $data, ManagedEnvironmentMembershipManager $manager): void {
$tenant = $this->getOwnerRecord(); $tenant = $this->getOwnerRecord();
if (! $tenant instanceof ManagedEnvironment) { if (! $tenant instanceof ManagedEnvironment) {
@ -155,7 +155,7 @@ public function table(Table $table): Table
->color('danger') ->color('danger')
->icon('heroicon-o-x-mark') ->icon('heroicon-o-x-mark')
->requiresConfirmation() ->requiresConfirmation()
->action(function (ManagedEnvironmentMembership $record, TenantMembershipManager $manager): void { ->action(function (ManagedEnvironmentMembership $record, ManagedEnvironmentMembershipManager $manager): void {
$tenant = $this->getOwnerRecord(); $tenant = $this->getOwnerRecord();
if (! $tenant instanceof ManagedEnvironment) { if (! $tenant instanceof ManagedEnvironment) {

View File

@ -200,11 +200,11 @@ public static function infolist(Schema $schema): Schema
Section::make('Metadata') Section::make('Metadata')
->schema([ ->schema([
TextEntry::make('initiator.name')->label('Initiated by')->placeholder('—'), TextEntry::make('initiator.name')->label('Initiated by')->placeholder('—'),
TextEntry::make('tenantReview.id') TextEntry::make('environmentReview.id')
->label('ManagedEnvironment review') ->label('ManagedEnvironment review')
->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—') ->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—')
->url(fn (ReviewPack $record): ?string => $record->tenantReview && $record->tenant ->url(fn (ReviewPack $record): ?string => $record->environmentReview && $record->tenant
? TenantReviewResource::tenantScopedUrl('view', ['record' => $record->tenantReview], $record->tenant) ? EnvironmentReviewResource::tenantScopedUrl('view', ['record' => $record->environmentReview], $record->tenant)
: null) : null)
->placeholder('—'), ->placeholder('—'),
TextEntry::make('customer_workspace') TextEntry::make('customer_workspace')
@ -217,10 +217,10 @@ public static function infolist(Schema $schema): Schema
TextEntry::make('summary.review_status') TextEntry::make('summary.review_status')
->label('Review status') ->label('Review status')
->badge() ->badge()
->formatStateUsing(BadgeRenderer::label(BadgeDomain::TenantReviewStatus)) ->formatStateUsing(BadgeRenderer::label(BadgeDomain::EnvironmentReviewStatus))
->color(BadgeRenderer::color(BadgeDomain::TenantReviewStatus)) ->color(BadgeRenderer::color(BadgeDomain::EnvironmentReviewStatus))
->icon(BadgeRenderer::icon(BadgeDomain::TenantReviewStatus)) ->icon(BadgeRenderer::icon(BadgeDomain::EnvironmentReviewStatus))
->iconColor(BadgeRenderer::iconColor(BadgeDomain::TenantReviewStatus)) ->iconColor(BadgeRenderer::iconColor(BadgeDomain::EnvironmentReviewStatus))
->placeholder('—'), ->placeholder('—'),
TextEntry::make('operationRun.id') TextEntry::make('operationRun.id')
->label('Operation') ->label('Operation')
@ -306,7 +306,7 @@ public static function table(Table $table): Table
->dateTime() ->dateTime()
->sortable() ->sortable()
->placeholder('—'), ->placeholder('—'),
Tables\Columns\TextColumn::make('tenantReview.id') Tables\Columns\TextColumn::make('environmentReview.id')
->label('Review') ->label('Review')
->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—') ->formatStateUsing(fn (?int $state): string => $state ? '#'.$state : '—')
->toggleable(isToggledHiddenByDefault: true), ->toggleable(isToggledHiddenByDefault: true),
@ -429,7 +429,7 @@ public static function getEloquentQuery(): Builder
} }
return parent::getEloquentQuery() return parent::getEloquentQuery()
->with(['tenant', 'operationRun', 'evidenceSnapshot', 'tenantReview']) ->with(['tenant', 'operationRun', 'evidenceSnapshot', 'environmentReview'])
->where('managed_environment_id', (int) $tenant->getKey()); ->where('managed_environment_id', (int) $tenant->getKey());
} }
@ -575,7 +575,7 @@ public static function executeGeneration(array $data): void
return; return;
} }
OperationUxPresenter::queuedToast('tenant.review_pack.generate')->send(); OperationUxPresenter::queuedToast('environment.review_pack.generate')->send();
} }
/** /**

View File

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\TenantReviewResource\Pages;
use App\Filament\Resources\TenantReviewResource;
use Filament\Resources\Pages\ListRecords;
class ListTenantReviews extends ListRecords
{
protected static string $resource = TenantReviewResource::class;
protected function getHeaderActions(): array
{
return [
TenantReviewResource::makeCreateReviewAction(),
];
}
}

View File

@ -9,7 +9,7 @@
use App\Models\PlatformUser; use App\Models\PlatformUser;
use App\Models\ProviderConnection; use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantPermission; use App\Models\ManagedEnvironmentPermission;
use App\Support\Auth\PlatformCapabilities; use App\Support\Auth\PlatformCapabilities;
use App\Support\CustomerHealth\WorkspaceHealthSummaryQuery; use App\Support\CustomerHealth\WorkspaceHealthSummaryQuery;
use App\Support\OperationCatalog; use App\Support\OperationCatalog;
@ -69,11 +69,11 @@ public function providerConnections(): Collection
} }
/** /**
* @return Collection<int, TenantPermission> * @return Collection<int, ManagedEnvironmentPermission>
*/ */
public function tenantPermissions(): Collection public function managedEnvironmentPermissions(): Collection
{ {
return TenantPermission::query() return ManagedEnvironmentPermission::query()
->where('managed_environment_id', (int) $this->tenant->getKey()) ->where('managed_environment_id', (int) $this->tenant->getKey())
->orderBy('permission_key') ->orderBy('permission_key')
->limit(20) ->limit(20)

View File

@ -6,7 +6,7 @@
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Support\OpsUx\ActiveRuns; use App\Support\OpsUx\ActiveRuns;
use App\Support\TenantDashboard\TenantDashboardSummaryBuilder; use App\Support\EnvironmentDashboard\EnvironmentDashboardSummaryBuilder;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Widgets\StatsOverviewWidget; use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat; use Filament\Widgets\StatsOverviewWidget\Stat;
@ -32,7 +32,7 @@ protected function getStats(): array
return []; return [];
} }
$summary = app(TenantDashboardSummaryBuilder::class)->build($tenant, auth()->user()); $summary = app(EnvironmentDashboardSummaryBuilder::class)->build($tenant, auth()->user());
$stats = []; $stats = [];

View File

@ -5,17 +5,17 @@
namespace App\Filament\Widgets\Dashboard; namespace App\Filament\Widgets\Dashboard;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Support\TenantDashboard\TenantDashboardSummaryBuilder; use App\Support\EnvironmentDashboard\EnvironmentDashboardSummaryBuilder;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Widgets\Widget; use Filament\Widgets\Widget;
class TenantDashboardContextChips extends Widget class EnvironmentDashboardContextChips extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected int|string|array $columnSpan = 'full'; protected int|string|array $columnSpan = 'full';
protected string $view = 'filament.widgets.dashboard.tenant-dashboard-context-chips'; protected string $view = 'filament.widgets.dashboard.environment-dashboard-context-chips';
/** /**
* @return array<string, mixed> * @return array<string, mixed>
@ -37,7 +37,7 @@ protected function getViewData(): array
]; ];
} }
$summary = app(TenantDashboardSummaryBuilder::class)->build($tenant, auth()->user()); $summary = app(EnvironmentDashboardSummaryBuilder::class)->build($tenant, auth()->user());
return [ return [
'context' => $summary->context, 'context' => $summary->context,

View File

@ -5,17 +5,17 @@
namespace App\Filament\Widgets\Dashboard; namespace App\Filament\Widgets\Dashboard;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Support\TenantDashboard\TenantDashboardSummaryBuilder; use App\Support\EnvironmentDashboard\EnvironmentDashboardSummaryBuilder;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Widgets\Widget; use Filament\Widgets\Widget;
class TenantDashboardOverview extends Widget class EnvironmentDashboardOverview extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected int|string|array $columnSpan = 'full'; protected int|string|array $columnSpan = 'full';
protected string $view = 'filament.widgets.dashboard.tenant-dashboard-overview'; protected string $view = 'filament.widgets.dashboard.environment-dashboard-overview';
/** /**
* @return array<string, mixed> * @return array<string, mixed>
@ -49,7 +49,7 @@ protected function getViewData(): array
]; ];
} }
return app(TenantDashboardSummaryBuilder::class) return app(EnvironmentDashboardSummaryBuilder::class)
->build($tenant) ->build($tenant)
->toArray(); ->toArray();
} }

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Filament\Resources\StoredReportResource; use App\Filament\Resources\StoredReportResource;
use App\Jobs\ScanEntraAdminRolesJob; use App\Jobs\ScanEntraAdminRolesJob;
@ -22,7 +22,7 @@ class AdminRolesSummaryWidget extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected string $view = 'filament.widgets.tenant.admin-roles-summary'; protected string $view = 'filament.widgets.managed-environment.admin-roles-summary';
public ?ManagedEnvironment $record = null; public ?ManagedEnvironment $record = null;

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Filament\Pages\BaselineCompareLanding; use App\Filament\Pages\BaselineCompareLanding;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
@ -16,7 +16,7 @@ class BaselineCompareCoverageBanner extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected string $view = 'filament.widgets.tenant.baseline-compare-coverage-banner'; protected string $view = 'filament.widgets.managed-environment.baseline-compare-coverage-banner';
/** /**
* @return array<string, mixed> * @return array<string, mixed>

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Filament\Resources\FindingExceptionResource; use App\Filament\Resources\FindingExceptionResource;
use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget as BaseWidget;

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Filament\Resources\FindingResource; use App\Filament\Resources\FindingResource;
use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget as BaseWidget;

View File

@ -2,18 +2,18 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Support\Tenants\TenantLifecyclePresentation; use App\Support\Tenants\TenantLifecyclePresentation;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Widgets\Widget; use Filament\Widgets\Widget;
class TenantArchivedBanner extends Widget class ManagedEnvironmentArchivedBanner extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected string $view = 'filament.widgets.tenant.tenant-archived-banner'; protected string $view = 'filament.widgets.managed-environment.managed-environment-archived-banner';
/** /**
* @return array<string, mixed> * @return array<string, mixed>

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Exceptions\Entitlements\WorkspaceEntitlementBlockedException; use App\Exceptions\Entitlements\WorkspaceEntitlementBlockedException;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
@ -23,13 +23,13 @@
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Widgets\Widget; use Filament\Widgets\Widget;
class TenantReviewPackCard extends Widget class ManagedEnvironmentReviewPackCard extends Widget
{ {
private const string ACTIVE_POLLING_INTERVAL = '10s'; private const string ACTIVE_POLLING_INTERVAL = '10s';
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected string $view = 'filament.widgets.tenant.tenant-review-pack-card'; protected string $view = 'filament.widgets.managed-environment.managed-environment-review-pack-card';
public ?ManagedEnvironment $record = null; public ?ManagedEnvironment $record = null;
@ -176,7 +176,7 @@ protected function getViewData(): array
: null; : null;
$latestPack = ReviewPack::query() $latestPack = ReviewPack::query()
->with(['tenantReview', 'operationRun']) ->with(['environmentReview', 'operationRun'])
->where('managed_environment_id', (int) $tenant->getKey()) ->where('managed_environment_id', (int) $tenant->getKey())
->orderByDesc('created_at') ->orderByDesc('created_at')
->orderByDesc('id') ->orderByDesc('id')
@ -210,8 +210,8 @@ protected function getViewData(): array
} }
$reviewUrl = null; $reviewUrl = null;
if ($latestPack->tenantReview && $canView) { if ($latestPack->environmentReview && $canView) {
$reviewUrl = \App\Filament\Resources\TenantReviewResource::tenantScopedUrl('view', ['record' => $latestPack->tenantReview], $tenant); $reviewUrl = \App\Filament\Resources\EnvironmentReviewResource::tenantScopedUrl('view', ['record' => $latestPack->environmentReview], $tenant);
} }
$failedReason = null; $failedReason = null;

View File

@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantTriageReview; use App\Models\ManagedEnvironmentTriageReview;
use App\Models\User; use App\Models\User;
use App\Services\PortfolioTriage\TenantTriageReviewService; use App\Services\PortfolioTriage\ManagedEnvironmentTriageReviewService;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use App\Support\BackupHealth\TenantBackupHealthResolver; use App\Support\BackupHealth\TenantBackupHealthResolver;
use App\Support\Badges\BadgeDomain; use App\Support\Badges\BadgeDomain;
@ -15,7 +15,7 @@
use App\Support\PortfolioTriage\PortfolioArrivalContext; use App\Support\PortfolioTriage\PortfolioArrivalContext;
use App\Support\PortfolioTriage\PortfolioArrivalContextResolver; use App\Support\PortfolioTriage\PortfolioArrivalContextResolver;
use App\Support\PortfolioTriage\PortfolioArrivalContextToken; use App\Support\PortfolioTriage\PortfolioArrivalContextToken;
use App\Support\PortfolioTriage\TenantTriageReviewStateResolver; use App\Support\PortfolioTriage\ManagedEnvironmentTriageReviewStateResolver;
use App\Support\Rbac\UiEnforcement; use App\Support\Rbac\UiEnforcement;
use App\Support\RestoreSafety\RestoreSafetyResolver; use App\Support\RestoreSafety\RestoreSafetyResolver;
use Filament\Actions\Action; use Filament\Actions\Action;
@ -27,7 +27,7 @@
use Filament\Schemas\Contracts\HasSchemas; use Filament\Schemas\Contracts\HasSchemas;
use Filament\Widgets\Widget; use Filament\Widgets\Widget;
class TenantTriageArrivalContinuity extends Widget implements HasActions, HasSchemas class ManagedEnvironmentTriageArrivalContinuity extends Widget implements HasActions, HasSchemas
{ {
use InteractsWithActions; use InteractsWithActions;
use InteractsWithSchemas; use InteractsWithSchemas;
@ -59,7 +59,7 @@ class TenantTriageArrivalContinuity extends Widget implements HasActions, HasSch
protected int|string|array $columnSpan = 'full'; protected int|string|array $columnSpan = 'full';
protected string $view = 'filament.widgets.tenant.triage-arrival-continuity'; protected string $view = 'filament.widgets.managed-environment.triage-arrival-continuity';
public function mount(): void public function mount(): void
{ {
@ -100,14 +100,14 @@ public function markReviewedAction(): Action
->color('success') ->color('success')
->requiresConfirmation() ->requiresConfirmation()
->modalHeading('Mark reviewed') ->modalHeading('Mark reviewed')
->modalDescription($this->reviewModalDescription(TenantTriageReview::STATE_REVIEWED)) ->modalDescription($this->reviewModalDescription(ManagedEnvironmentTriageReview::STATE_REVIEWED))
->visible(fn (): bool => $this->canShowReviewActions()) ->visible(fn (): bool => $this->canShowReviewActions())
->action(function (TenantTriageReviewService $service): void { ->action(function (ManagedEnvironmentTriageReviewService $service): void {
$this->handleReviewMutation(TenantTriageReview::STATE_REVIEWED, $service); $this->handleReviewMutation(ManagedEnvironmentTriageReview::STATE_REVIEWED, $service);
}), }),
) )
->preserveVisibility() ->preserveVisibility()
->requireCapability(Capabilities::TENANT_TRIAGE_REVIEW_MANAGE) ->requireCapability(Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE)
->apply(); ->apply();
} }
@ -120,14 +120,14 @@ public function markFollowUpNeededAction(): Action
->color('warning') ->color('warning')
->requiresConfirmation() ->requiresConfirmation()
->modalHeading('Mark follow-up needed') ->modalHeading('Mark follow-up needed')
->modalDescription($this->reviewModalDescription(TenantTriageReview::STATE_FOLLOW_UP_NEEDED)) ->modalDescription($this->reviewModalDescription(ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED))
->visible(fn (): bool => $this->canShowReviewActions()) ->visible(fn (): bool => $this->canShowReviewActions())
->action(function (TenantTriageReviewService $service): void { ->action(function (ManagedEnvironmentTriageReviewService $service): void {
$this->handleReviewMutation(TenantTriageReview::STATE_FOLLOW_UP_NEEDED, $service); $this->handleReviewMutation(ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED, $service);
}), }),
) )
->preserveVisibility() ->preserveVisibility()
->requireCapability(Capabilities::TENANT_TRIAGE_REVIEW_MANAGE) ->requireCapability(Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE)
->apply(); ->apply();
} }
@ -170,10 +170,10 @@ private function reviewModalDescription(string $targetManualState): \Closure
} }
$currentLabel = BadgeRenderer::spec( $currentLabel = BadgeRenderer::spec(
BadgeDomain::TenantTriageReviewState, BadgeDomain::ManagedEnvironmentTriageReviewState,
(string) ($reviewState['derived_state'] ?? TenantTriageReview::DERIVED_STATE_NOT_REVIEWED), (string) ($reviewState['derived_state'] ?? ManagedEnvironmentTriageReview::DERIVED_STATE_NOT_REVIEWED),
)->label; )->label;
$targetLabel = BadgeRenderer::spec(BadgeDomain::TenantTriageReviewState, $targetManualState)->label; $targetLabel = BadgeRenderer::spec(BadgeDomain::ManagedEnvironmentTriageReviewState, $targetManualState)->label;
return implode("\n\n", [ return implode("\n\n", [
'Concern family: '.$this->concernFamilyLabel($context->concernFamily), 'Concern family: '.$this->concernFamilyLabel($context->concernFamily),
@ -184,7 +184,7 @@ private function reviewModalDescription(string $targetManualState): \Closure
}; };
} }
private function handleReviewMutation(string $targetManualState, TenantTriageReviewService $service): void private function handleReviewMutation(string $targetManualState, ManagedEnvironmentTriageReviewService $service): void
{ {
$tenant = Filament::getTenant(); $tenant = Filament::getTenant();
@ -219,14 +219,14 @@ private function handleReviewMutation(string $targetManualState, TenantTriageRev
$actor = auth()->user(); $actor = auth()->user();
$review = match ($targetManualState) { $review = match ($targetManualState) {
TenantTriageReview::STATE_REVIEWED => $service->markReviewed( ManagedEnvironmentTriageReview::STATE_REVIEWED => $service->markReviewed(
tenant: $tenant, tenant: $tenant,
concernFamily: $context->concernFamily, concernFamily: $context->concernFamily,
backupHealth: $concernTruth['backupHealth'], backupHealth: $concernTruth['backupHealth'],
recoveryEvidence: $concernTruth['recoveryEvidence'], recoveryEvidence: $concernTruth['recoveryEvidence'],
actor: $actor instanceof User ? $actor : null, actor: $actor instanceof User ? $actor : null,
), ),
TenantTriageReview::STATE_FOLLOW_UP_NEEDED => $service->markFollowUpNeeded( ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED => $service->markFollowUpNeeded(
tenant: $tenant, tenant: $tenant,
concernFamily: $context->concernFamily, concernFamily: $context->concernFamily,
backupHealth: $concernTruth['backupHealth'], backupHealth: $concernTruth['backupHealth'],
@ -236,7 +236,7 @@ private function handleReviewMutation(string $targetManualState, TenantTriageRev
default => null, default => null,
}; };
if (! $review instanceof TenantTriageReview) { if (! $review instanceof ManagedEnvironmentTriageReview) {
return; return;
} }
@ -247,7 +247,7 @@ private function handleReviewMutation(string $targetManualState, TenantTriageRev
->body(sprintf( ->body(sprintf(
'%s is now %s for %s.', '%s is now %s for %s.',
$tenant->name, $tenant->name,
BadgeRenderer::spec(BadgeDomain::TenantTriageReviewState, $review->current_state)->label, BadgeRenderer::spec(BadgeDomain::ManagedEnvironmentTriageReviewState, $review->current_state)->label,
$this->concernFamilyLabel($context->concernFamily), $this->concernFamilyLabel($context->concernFamily),
)) ))
->success() ->success()
@ -268,7 +268,7 @@ private function currentReviewStateFor(ManagedEnvironment $tenant, string $conce
$concernTruth = $this->concernTruthFor($tenant); $concernTruth = $this->concernTruthFor($tenant);
$reviewState = app(TenantTriageReviewStateResolver::class)->resolveMany( $reviewState = app(ManagedEnvironmentTriageReviewStateResolver::class)->resolveMany(
workspaceId: (int) $tenant->workspace_id, workspaceId: (int) $tenant->workspace_id,
tenantIds: [$tenantId], tenantIds: [$tenantId],
backupHealthByTenant: [$tenantId => $concernTruth['backupHealth']], backupHealthByTenant: [$tenantId => $concernTruth['backupHealth']],

View File

@ -2,9 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Filament\Resources\TenantResource\Pages\ViewTenant; use App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment;
use App\Filament\Support\VerificationReportChangeIndicator; use App\Filament\Support\VerificationReportChangeIndicator;
use App\Filament\Support\VerificationReportViewer; use App\Filament\Support\VerificationReportViewer;
use App\Models\OperationRun; use App\Models\OperationRun;
@ -24,11 +24,11 @@
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Widgets\Widget; use Filament\Widgets\Widget;
class TenantVerificationReport extends Widget class ManagedEnvironmentVerificationReport extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected string $view = 'filament.widgets.tenant.tenant-verification-report'; protected string $view = 'filament.widgets.managed-environment.managed-environment-verification-report';
public ?ManagedEnvironment $record = null; public ?ManagedEnvironment $record = null;
@ -203,7 +203,7 @@ protected function getViewData(): array
'startTooltip' => $isTenantMember && $canOperate && ! $canStart ? UiTooltips::insufficientPermission() : null, 'startTooltip' => $isTenantMember && $canOperate && ! $canStart ? UiTooltips::insufficientPermission() : null,
'lifecycleNotice' => $lifecycleNotice, 'lifecycleNotice' => $lifecycleNotice,
'rerunHint' => $run instanceof OperationRun && $canStart 'rerunHint' => $run instanceof OperationRun && $canStart
? ViewTenant::verificationHeaderActionHint() ? ViewManagedEnvironment::verificationHeaderActionHint()
: null, : null,
]; ];
} }

View File

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Filament\Widgets\Tenant; namespace App\Filament\Widgets\ManagedEnvironment;
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
@ -15,7 +15,7 @@ class RecentOperationsSummary extends Widget
{ {
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected string $view = 'filament.widgets.tenant.recent-operations-summary'; protected string $view = 'filament.widgets.managed-environment.recent-operations-summary';
public ?ManagedEnvironment $record = null; public ?ManagedEnvironment $record = null;

View File

@ -4,7 +4,7 @@
use App\Models\ProviderConnection; use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Services\Intune\AuditLogger; use App\Services\Intune\AuditLogger;
use App\Support\Providers\ProviderConnectionType; use App\Support\Providers\ProviderConnectionType;
use App\Support\Providers\ProviderConsentStatus; use App\Support\Providers\ProviderConsentStatus;
@ -23,8 +23,8 @@ public function __invoke(
Request $request, Request $request,
AuditLogger $auditLogger, AuditLogger $auditLogger,
): View { ): View {
$expectedState = $request->session()->pull('tenant_onboard_state'); $expectedState = $request->session()->pull('environment_onboard_state');
$workspaceId = $request->session()->pull('tenant_onboard_workspace_id'); $workspaceId = $request->session()->pull('environment_onboard_workspace_id');
$tenantKey = $request->string('tenant')->toString(); $tenantKey = $request->string('tenant')->toString();
$state = $request->string('state')->toString(); $state = $request->string('state')->toString();
$tenantIdentifier = $tenantKey ?: $this->parseState($state); $tenantIdentifier = $tenantKey ?: $this->parseState($state);
@ -219,10 +219,10 @@ private function verificationStateLabel(ProviderConnection $connection): string
private function invalidateResumableOnboardingVerificationState(ManagedEnvironment $tenant, ProviderConnection $connection): void private function invalidateResumableOnboardingVerificationState(ManagedEnvironment $tenant, ProviderConnection $connection): void
{ {
TenantOnboardingSession::query() ManagedEnvironmentOnboardingSession::query()
->where('managed_environment_id', (int) $tenant->getKey()) ->where('managed_environment_id', (int) $tenant->getKey())
->resumable() ->resumable()
->each(function (TenantOnboardingSession $draft) use ($connection): void { ->each(function (ManagedEnvironmentOnboardingSession $draft) use ($connection): void {
$state = is_array($draft->state) ? $draft->state : []; $state = is_array($draft->state) ? $draft->state : [];
$providerConnectionId = $state['provider_connection_id'] ?? null; $providerConnectionId = $state['provider_connection_id'] ?? null;
$providerConnectionId = is_numeric($providerConnectionId) ? (int) $providerConnectionId : null; $providerConnectionId = is_numeric($providerConnectionId) ? (int) $providerConnectionId : null;

View File

@ -11,7 +11,7 @@
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
final class ClearTenantContextController final class ClearEnvironmentContextController
{ {
public function __invoke(Request $request): RedirectResponse public function __invoke(Request $request): RedirectResponse
{ {
@ -38,13 +38,13 @@ public function __invoke(Request $request): RedirectResponse
$workspace = $workspaceContext->currentWorkspace($request); $workspace = $workspaceContext->currentWorkspace($request);
if ($workspace !== null) { if ($workspace !== null) {
return redirect()->route('admin.workspace.managed-tenants.index', ['workspace' => $workspace]); return redirect()->route('admin.workspace.managed-environments.index', ['workspace' => $workspace]);
} }
return redirect()->route('admin.home'); return redirect()->route('admin.home');
} }
if ($previousPath === '' || $previousPath === '/admin/clear-tenant-context') { if ($previousPath === '' || $previousPath === '/admin/clear-environment-context') {
return redirect()->to(OperationRunLinks::index()); return redirect()->to(OperationRunLinks::index());
} }

View File

@ -16,7 +16,7 @@
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response as ResponseAlias; use Symfony\Component\HttpFoundation\Response as ResponseAlias;
class TenantOnboardingController extends Controller class ManagedEnvironmentOnboardingController extends Controller
{ {
public function __invoke( public function __invoke(
Request $request, Request $request,
@ -27,12 +27,12 @@ public function __invoke(
abort_if($tenantIdentifier === '', ResponseAlias::HTTP_NOT_FOUND); abort_if($tenantIdentifier === '', ResponseAlias::HTTP_NOT_FOUND);
$state = Str::uuid()->toString(); $state = Str::uuid()->toString();
$request->session()->put('tenant_onboard_state', $state); $request->session()->put('environment_onboard_state', $state);
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId($request); $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId($request);
if ($workspaceId !== null) { if ($workspaceId !== null) {
$request->session()->put('tenant_onboard_workspace_id', (int) $workspaceId); $request->session()->put('environment_onboard_workspace_id', (int) $workspaceId);
} }
$tenant = $this->resolveTenant($tenantIdentifier, is_numeric($workspaceId) ? (int) $workspaceId : null); $tenant = $this->resolveTenant($tenantIdentifier, is_numeric($workspaceId) ? (int) $workspaceId : null);

View File

@ -16,7 +16,7 @@
final class OpenFindingExceptionsQueueController extends Controller final class OpenFindingExceptionsQueueController extends Controller
{ {
public function __invoke(Request $request, ManagedEnvironment $tenant): RedirectResponse public function __invoke(Request $request, ManagedEnvironment $environment): RedirectResponse
{ {
$user = auth()->user(); $user = auth()->user();
@ -24,13 +24,13 @@ public function __invoke(Request $request, ManagedEnvironment $tenant): Redirect
abort(403); abort(403);
} }
$workspace = Workspace::query()->whereKey($tenant->workspace_id)->first(); $workspace = Workspace::query()->whereKey($environment->workspace_id)->first();
if (! $workspace instanceof Workspace) { if (! $workspace instanceof Workspace) {
abort(404); abort(404);
} }
if (! $user->canAccessTenant($tenant)) { if (! $user->canAccessTenant($environment)) {
abort(404); abort(404);
} }
@ -49,12 +49,12 @@ public function __invoke(Request $request, ManagedEnvironment $tenant): Redirect
$workspaceContext->setCurrentWorkspace($workspace, $user, $request); $workspaceContext->setCurrentWorkspace($workspace, $user, $request);
if (! $workspaceContext->rememberTenantContext($tenant, $request)) { if (! $workspaceContext->rememberTenantContext($environment, $request)) {
abort(404); abort(404);
} }
return redirect()->to(FindingExceptionsQueue::getUrl([ return redirect()->to(FindingExceptionsQueue::getUrl([
'tenant' => (string) $tenant->external_id, 'tenant' => (string) $environment->external_id,
], panel: 'admin')); ], panel: 'admin'));
} }
} }

View File

@ -55,8 +55,8 @@ public function __invoke(Request $request, ReviewPack $reviewPack): StreamedResp
context: [ context: [
'metadata' => [ 'metadata' => [
'review_pack_id' => (int) $reviewPack->getKey(), 'review_pack_id' => (int) $reviewPack->getKey(),
'tenant_review_id' => $reviewPack->tenant_review_id !== null 'environment_review_id' => $reviewPack->environment_review_id !== null
? (int) $reviewPack->tenant_review_id ? (int) $reviewPack->environment_review_id
: null, : null,
'source_surface' => (string) $request->query('source_surface', 'review_pack'), 'source_surface' => (string) $request->query('source_surface', 'review_pack'),
'review_id' => $request->query('review_id'), 'review_id' => $request->query('review_id'),

View File

@ -16,7 +16,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
final class SelectTenantController final class SelectEnvironmentController
{ {
public function __invoke(Request $request): RedirectResponse public function __invoke(Request $request): RedirectResponse
{ {

View File

@ -204,7 +204,7 @@ private function isLivewireUpdatePath(string $path): bool
private function isChooserFirstPath(string $path): bool private function isChooserFirstPath(string $path): bool
{ {
return in_array($path, ['/admin', '/admin/choose-tenant'], true); return in_array($path, ['/admin', '/admin/choose-environment'], true);
} }
private function requestHasExplicitTenantContext(Request $request): bool private function requestHasExplicitTenantContext(Request $request): bool

View File

@ -69,9 +69,9 @@ public function handle(OperationRunService $runs): void
$chunkSize = max(1, $chunkSize); $chunkSize = max(1, $chunkSize);
foreach (array_chunk($ids, $chunkSize) as $chunk) { foreach (array_chunk($ids, $chunkSize) as $chunk) {
foreach ($chunk as $targetTenantId) { foreach ($chunk as $targetEnvironmentId) {
dispatch(new TenantSyncWorkerJob( dispatch(new TenantSyncWorkerJob(
tenantId: $targetTenantId, tenantId: $targetEnvironmentId,
userId: $this->userId, userId: $this->userId,
operationRun: $this->operationRun, operationRun: $this->operationRun,
context: $this->context, context: $this->context,

View File

@ -100,7 +100,7 @@ public function handle(
$context = is_array($this->operationRun->context) ? $this->operationRun->context : []; $context = is_array($this->operationRun->context) ? $this->operationRun->context : [];
$profileId = (int) ($context['baseline_profile_id'] ?? 0); $profileId = (int) ($context['baseline_profile_id'] ?? 0);
$sourceTenantId = (int) ($context['source_tenant_id'] ?? 0); $sourceEnvironmentId = (int) ($context['source_environment_id'] ?? 0);
$profile = BaselineProfile::query()->find($profileId); $profile = BaselineProfile::query()->find($profileId);
@ -108,10 +108,10 @@ public function handle(
throw new RuntimeException("BaselineProfile #{$profileId} not found."); throw new RuntimeException("BaselineProfile #{$profileId} not found.");
} }
$sourceTenant = ManagedEnvironment::query()->find($sourceTenantId); $sourceTenant = ManagedEnvironment::query()->find($sourceEnvironmentId);
if (! $sourceTenant instanceof ManagedEnvironment) { if (! $sourceTenant instanceof ManagedEnvironment) {
throw new RuntimeException("Source ManagedEnvironment #{$sourceTenantId} not found."); throw new RuntimeException("Source ManagedEnvironment #{$sourceEnvironmentId} not found.");
} }
$initiator = $this->operationRun->user_id $initiator = $this->operationRun->user_id

View File

@ -6,17 +6,17 @@
use App\Jobs\Concerns\BridgesFailedOperationRun; use App\Jobs\Concerns\BridgesFailedOperationRun;
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Services\OperationRunService; use App\Services\OperationRunService;
use App\Services\TenantReviews\TenantReviewService; use App\Services\EnvironmentReviews\EnvironmentReviewService;
use App\Support\OperationRunOutcome; use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus; use App\Support\OperationRunStatus;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable; use Illuminate\Foundation\Queue\Queueable;
use Throwable; use Throwable;
class ComposeTenantReviewJob implements ShouldQueue class ComposeEnvironmentReviewJob implements ShouldQueue
{ {
use BridgesFailedOperationRun; use BridgesFailedOperationRun;
use Queueable; use Queueable;
@ -26,21 +26,21 @@ class ComposeTenantReviewJob implements ShouldQueue
public bool $failOnTimeout = true; public bool $failOnTimeout = true;
public function __construct( public function __construct(
public int $tenantReviewId, public int $environmentReviewId,
public int $operationRunId, public int $operationRunId,
) {} ) {}
public function handle(TenantReviewService $service, OperationRunService $operationRuns): void public function handle(EnvironmentReviewService $service, OperationRunService $operationRuns): void
{ {
$review = TenantReview::query()->with(['tenant', 'evidenceSnapshot.items'])->find($this->tenantReviewId); $review = EnvironmentReview::query()->with(['tenant', 'evidenceSnapshot.items'])->find($this->environmentReviewId);
$operationRun = OperationRun::query()->find($this->operationRunId); $operationRun = OperationRun::query()->find($this->operationRunId);
if (! $review instanceof TenantReview || ! $operationRun instanceof OperationRun || ! $review->tenant) { if (! $review instanceof EnvironmentReview || ! $operationRun instanceof OperationRun || ! $review->tenant) {
return; return;
} }
$operationRuns->updateRun($operationRun, OperationRunStatus::Running->value, OperationRunOutcome::Pending->value); $operationRuns->updateRun($operationRun, OperationRunStatus::Running->value, OperationRunOutcome::Pending->value);
$review->update(['status' => TenantReviewStatus::Draft->value]); $review->update(['status' => EnvironmentReviewStatus::Draft->value]);
try { try {
$review = $service->compose($review); $review = $service->compose($review);
@ -61,7 +61,7 @@ public function handle(TenantReviewService $service, OperationRunService $operat
); );
} catch (Throwable $throwable) { } catch (Throwable $throwable) {
$review->update([ $review->update([
'status' => TenantReviewStatus::Failed->value, 'status' => EnvironmentReviewStatus::Failed->value,
'summary' => array_merge(is_array($review->summary) ? $review->summary : [], [ 'summary' => array_merge(is_array($review->summary) ? $review->summary : [], [
'error' => $throwable->getMessage(), 'error' => $throwable->getMessage(),
]), ]),
@ -73,7 +73,7 @@ public function handle(TenantReviewService $service, OperationRunService $operat
outcome: OperationRunOutcome::Failed->value, outcome: OperationRunOutcome::Failed->value,
failures: [ failures: [
[ [
'code' => 'tenant_review_compose.failed', 'code' => 'environment_review_compose.failed',
'message' => $throwable->getMessage(), 'message' => $throwable->getMessage(),
], ],
], ],

View File

@ -9,7 +9,7 @@
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Services\Intune\SecretClassificationService; use App\Services\Intune\SecretClassificationService;
use App\Services\OperationRunService; use App\Services\OperationRunService;
use App\Services\ReviewPackService; use App\Services\ReviewPackService;
@ -39,7 +39,7 @@ public function __construct(
public function handle(OperationRunService $operationRunService): void public function handle(OperationRunService $operationRunService): void
{ {
$reviewPack = ReviewPack::query()->with(['tenant', 'evidenceSnapshot.items', 'tenantReview.sections'])->find($this->reviewPackId); $reviewPack = ReviewPack::query()->with(['tenant', 'evidenceSnapshot.items', 'environmentReview.sections'])->find($this->reviewPackId);
$operationRun = OperationRun::query()->find($this->operationRunId); $operationRun = OperationRun::query()->find($this->operationRunId);
if (! $reviewPack instanceof ReviewPack || ! $operationRun instanceof OperationRun) { if (! $reviewPack instanceof ReviewPack || ! $operationRun instanceof OperationRun) {
@ -82,9 +82,9 @@ public function handle(OperationRunService $operationRunService): void
private function executeGeneration(ReviewPack $reviewPack, OperationRun $operationRun, ManagedEnvironment $tenant, EvidenceSnapshot $snapshot, OperationRunService $operationRunService): void private function executeGeneration(ReviewPack $reviewPack, OperationRun $operationRun, ManagedEnvironment $tenant, EvidenceSnapshot $snapshot, OperationRunService $operationRunService): void
{ {
$review = $reviewPack->tenantReview; $review = $reviewPack->environmentReview;
if ($review instanceof TenantReview) { if ($review instanceof EnvironmentReview) {
$this->executeReviewDerivedGeneration($reviewPack, $review, $operationRun, $tenant, $snapshot, $operationRunService); $this->executeReviewDerivedGeneration($reviewPack, $review, $operationRun, $tenant, $snapshot, $operationRunService);
return; return;
@ -216,7 +216,7 @@ private function executeGeneration(ReviewPack $reviewPack, OperationRun $operati
private function executeReviewDerivedGeneration( private function executeReviewDerivedGeneration(
ReviewPack $reviewPack, ReviewPack $reviewPack,
TenantReview $review, EnvironmentReview $review,
OperationRun $operationRun, OperationRun $operationRun,
ManagedEnvironment $tenant, ManagedEnvironment $tenant,
EvidenceSnapshot $snapshot, EvidenceSnapshot $snapshot,
@ -280,7 +280,7 @@ private function executeReviewDerivedGeneration(
$fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options); $fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options);
$reviewSummary = is_array($review->summary) ? $review->summary : []; $reviewSummary = is_array($review->summary) ? $review->summary : [];
$summary = [ $summary = [
'tenant_review_id' => (int) $review->getKey(), 'environment_review_id' => (int) $review->getKey(),
'review_status' => (string) $review->status, 'review_status' => (string) $review->status,
'review_completeness_state' => (string) $review->completeness_state, 'review_completeness_state' => (string) $review->completeness_state,
'section_count' => $review->sections->count(), 'section_count' => $review->sections->count(),
@ -636,7 +636,7 @@ private function assembleZip(string $tempFile, array $fileMap, ?callable $afterW
*/ */
private function buildReviewDerivedFileMap( private function buildReviewDerivedFileMap(
ReviewPack $reviewPack, ReviewPack $reviewPack,
TenantReview $review, EnvironmentReview $review,
ManagedEnvironment $tenant, ManagedEnvironment $tenant,
EvidenceSnapshot $snapshot, EvidenceSnapshot $snapshot,
bool $includePii, bool $includePii,
@ -662,7 +662,7 @@ private function buildReviewDerivedFileMap(
'tenant_name' => $includePii ? $tenant->name : '[REDACTED]', 'tenant_name' => $includePii ? $tenant->name : '[REDACTED]',
'generated_at' => $generatedAt->toIso8601String(), 'generated_at' => $generatedAt->toIso8601String(),
'delivery_bundle' => $deliveryMetadata, 'delivery_bundle' => $deliveryMetadata,
'tenant_review' => [ 'environment_review' => [
'id' => (int) $review->getKey(), 'id' => (int) $review->getKey(),
'status' => (string) $review->status, 'status' => (string) $review->status,
'completeness_state' => (string) $review->completeness_state, 'completeness_state' => (string) $review->completeness_state,
@ -686,7 +686,7 @@ private function buildReviewDerivedFileMap(
], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR), ], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR),
'summary.json' => json_encode($this->redactReportPayload(array_merge( 'summary.json' => json_encode($this->redactReportPayload(array_merge(
[ [
'tenant_review_id' => (int) $review->getKey(), 'environment_review_id' => (int) $review->getKey(),
'review_status' => (string) $review->status, 'review_status' => (string) $review->status,
'review_completeness_state' => (string) $review->completeness_state, 'review_completeness_state' => (string) $review->completeness_state,
], ],
@ -738,7 +738,7 @@ private function buildReviewDerivedFileMap(
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
private function deliveryBundleSummary(TenantReview $review): array private function deliveryBundleSummary(EnvironmentReview $review): array
{ {
return [ return [
'contract' => ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT, 'contract' => ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT,
@ -753,7 +753,7 @@ private function deliveryBundleSummary(TenantReview $review): array
*/ */
private function deliveryBundleMetadata( private function deliveryBundleMetadata(
ReviewPack $reviewPack, ReviewPack $reviewPack,
TenantReview $review, EnvironmentReview $review,
EvidenceSnapshot $snapshot, EvidenceSnapshot $snapshot,
\Carbon\CarbonInterface $generatedAt, \Carbon\CarbonInterface $generatedAt,
): array { ): array {
@ -805,7 +805,7 @@ private function deliveryBundleMetadata(
* @param array<string, mixed> $reviewSummary * @param array<string, mixed> $reviewSummary
*/ */
private function buildExecutiveEntrypoint( private function buildExecutiveEntrypoint(
TenantReview $review, EnvironmentReview $review,
ManagedEnvironment $tenant, ManagedEnvironment $tenant,
EvidenceSnapshot $snapshot, EvidenceSnapshot $snapshot,
array $reviewSummary, array $reviewSummary,

View File

@ -28,7 +28,7 @@
use RuntimeException; use RuntimeException;
use Throwable; use Throwable;
final class CrossTenantPromotionExecutionJob implements ShouldQueue final class CrossEnvironmentPromotionExecutionJob implements ShouldQueue
{ {
use Dispatchable; use Dispatchable;
use InteractsWithQueue; use InteractsWithQueue;
@ -73,16 +73,16 @@ public function handle(
return; return;
} }
$tenant = $this->operationRun->tenant; $targetEnvironment = $this->operationRun->tenant;
if (! $tenant instanceof ManagedEnvironment) { if (! $targetEnvironment instanceof ManagedEnvironment) {
throw new RuntimeException('Promotion execution target tenant is missing.'); throw new RuntimeException('Promotion execution target environment is missing.');
} }
$context = is_array($this->operationRun->context) ? $this->operationRun->context : []; $context = is_array($this->operationRun->context) ? $this->operationRun->context : [];
$targetScope = is_array($context['target_scope'] ?? null) ? $context['target_scope'] : []; $targetScope = is_array($context['target_scope'] ?? null) ? $context['target_scope'] : [];
$lock = $limiter->acquireSlot((int) $tenant->getKey(), $targetScope); $lock = $limiter->acquireSlot((int) $targetEnvironment->getKey(), $targetScope);
if (! $lock) { if (! $lock) {
$this->release(max(1, (int) config('tenantpilot.bulk_operations.poll_interval_seconds', 3))); $this->release(max(1, (int) config('tenantpilot.bulk_operations.poll_interval_seconds', 3)));
@ -114,7 +114,7 @@ public function handle(
$summary['total'] = count($items); $summary['total'] = count($items);
[$backupSet, $selectedItemIds, $preRestoreSummary, $preRestoreFailures] = $this->buildRestoreInputs( [$backupSet, $selectedItemIds, $preRestoreSummary, $preRestoreFailures] = $this->buildRestoreInputs(
tenant: $tenant, targetEnvironment: $targetEnvironment,
operationRun: $this->operationRun, operationRun: $this->operationRun,
items: $items, items: $items,
); );
@ -126,7 +126,7 @@ public function handle(
if ($selectedItemIds !== []) { if ($selectedItemIds !== []) {
$restoreRun = RestoreRun::withoutEvents(fn (): RestoreRun => $restoreService->execute( $restoreRun = RestoreRun::withoutEvents(fn (): RestoreRun => $restoreService->execute(
tenant: $tenant, tenant: $targetEnvironment,
backupSet: $backupSet, backupSet: $backupSet,
selectedItemIds: $selectedItemIds, selectedItemIds: $selectedItemIds,
dryRun: false, dryRun: false,
@ -160,10 +160,10 @@ public function handle(
failures: $failures, failures: $failures,
); );
$auditLogger->logCrossTenantPromotionExecutionCompleted( $auditLogger->logCrossEnvironmentPromotionExecutionCompleted(
operationRun: $updated, operationRun: $updated,
sourceTenantId: is_numeric($context['source_tenant_id'] ?? null) ? (int) $context['source_tenant_id'] : null, sourceEnvironmentId: is_numeric($context['source_environment_id'] ?? null) ? (int) $context['source_environment_id'] : null,
targetTenant: $tenant, targetEnvironment: $targetEnvironment,
summaryCounts: $summary, summaryCounts: $summary,
restoreRun: $restoreRun, restoreRun: $restoreRun,
); );
@ -183,7 +183,7 @@ public function getOperationRun(): ?OperationRun
* @param list<array<string, mixed>> $items * @param list<array<string, mixed>> $items
* @return array{0: ?BackupSet, 1: list<int>, 2: array<string, int>, 3: list<array{code: string, message: string}>} * @return array{0: ?BackupSet, 1: list<int>, 2: array<string, int>, 3: list<array{code: string, message: string}>}
*/ */
private function buildRestoreInputs(ManagedEnvironment $tenant, OperationRun $operationRun, array $items): array private function buildRestoreInputs(ManagedEnvironment $targetEnvironment, OperationRun $operationRun, array $items): array
{ {
$summary = [ $summary = [
'processed' => 0, 'processed' => 0,
@ -195,14 +195,14 @@ private function buildRestoreInputs(ManagedEnvironment $tenant, OperationRun $op
]; ];
$failures = []; $failures = [];
$backupSet = BackupSet::query()->create([ $backupSet = BackupSet::query()->create([
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $targetEnvironment->getKey(),
'name' => 'Cross-tenant promotion • Operation #'.$operationRun->getKey(), 'name' => 'Cross-environment promotion • Operation #'.$operationRun->getKey(),
'created_by' => $operationRun->user?->email, 'created_by' => $operationRun->user?->email,
'status' => 'completed', 'status' => 'completed',
'item_count' => 0, 'item_count' => 0,
'completed_at' => CarbonImmutable::now(), 'completed_at' => CarbonImmutable::now(),
'metadata' => [ 'metadata' => [
'source' => 'cross_tenant_promotion', 'source' => 'cross_environment_promotion',
'operation_run_id' => (int) $operationRun->getKey(), 'operation_run_id' => (int) $operationRun->getKey(),
], ],
]); ]);
@ -219,13 +219,13 @@ private function buildRestoreInputs(ManagedEnvironment $tenant, OperationRun $op
} }
$versionId = data_get($item, 'source.policy_version_id'); $versionId = data_get($item, 'source.policy_version_id');
$sourceTenantId = data_get($item, 'source.managed_environment_id'); $sourceEnvironmentId = data_get($item, 'source.managed_environment_id');
$version = is_numeric($versionId) && is_numeric($sourceTenantId) $version = is_numeric($versionId) && is_numeric($sourceEnvironmentId)
? PolicyVersion::query() ? PolicyVersion::query()
->with('policy') ->with('policy')
->whereKey((int) $versionId) ->whereKey((int) $versionId)
->where('managed_environment_id', (int) $sourceTenantId) ->where('managed_environment_id', (int) $sourceEnvironmentId)
->first() ->first()
: null; : null;
@ -248,13 +248,13 @@ private function buildRestoreInputs(ManagedEnvironment $tenant, OperationRun $op
: (is_string($sourceExternalId) && trim($sourceExternalId) !== '' ? trim($sourceExternalId) : (string) $sourcePolicy->external_id); : (is_string($sourceExternalId) && trim($sourceExternalId) !== '' ? trim($sourceExternalId) : (string) $sourcePolicy->external_id);
$targetPolicy = Policy::query() $targetPolicy = Policy::query()
->where('managed_environment_id', (int) $tenant->getKey()) ->where('managed_environment_id', (int) $targetEnvironment->getKey())
->where('policy_type', (string) $sourcePolicy->policy_type) ->where('policy_type', (string) $sourcePolicy->policy_type)
->where('external_id', $policyIdentifier) ->where('external_id', $policyIdentifier)
->first(); ->first();
$backupItem = BackupItem::query()->create([ $backupItem = BackupItem::query()->create([
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $targetEnvironment->getKey(),
'backup_set_id' => (int) $backupSet->getKey(), 'backup_set_id' => (int) $backupSet->getKey(),
'policy_id' => $targetPolicy?->getKey(), 'policy_id' => $targetPolicy?->getKey(),
'policy_identifier' => $policyIdentifier, 'policy_identifier' => $policyIdentifier,
@ -263,10 +263,10 @@ private function buildRestoreInputs(ManagedEnvironment $tenant, OperationRun $op
'captured_at' => $version->captured_at ?? CarbonImmutable::now(), 'captured_at' => $version->captured_at ?? CarbonImmutable::now(),
'payload' => is_array($version->snapshot) ? $version->snapshot : [], 'payload' => is_array($version->snapshot) ? $version->snapshot : [],
'metadata' => [ 'metadata' => [
'source' => 'cross_tenant_promotion', 'source' => 'cross_environment_promotion',
'display_name' => (string) $sourcePolicy->display_name, 'display_name' => (string) $sourcePolicy->display_name,
'operation_run_id' => (int) $operationRun->getKey(), 'operation_run_id' => (int) $operationRun->getKey(),
'source_tenant_id' => (int) $sourcePolicy->managed_environment_id, 'source_environment_id' => (int) $sourcePolicy->managed_environment_id,
'source_policy_id' => (int) $sourcePolicy->getKey(), 'source_policy_id' => (int) $sourcePolicy->getKey(),
'source_policy_version_id' => (int) $version->getKey(), 'source_policy_version_id' => (int) $version->getKey(),
'source_subject_key' => (string) ($item['subject_key'] ?? ''), 'source_subject_key' => (string) ($item['subject_key'] ?? ''),

View File

@ -10,7 +10,7 @@
use App\Models\User; use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Intune\AuditLogger as TenantAuditLogger; use App\Services\Intune\AuditLogger as TenantAuditLogger;
use App\Services\Intune\TenantPermissionService; use App\Services\Intune\ManagedEnvironmentPermissionService;
use App\Services\OperationRunService; use App\Services\OperationRunService;
use App\Services\Providers\Contracts\HealthResult; use App\Services\Providers\Contracts\HealthResult;
use App\Services\Providers\MicrosoftProviderHealthCheck; use App\Services\Providers\MicrosoftProviderHealthCheck;
@ -24,7 +24,7 @@
use App\Support\Providers\ProviderNextStepsRegistry; use App\Support\Providers\ProviderNextStepsRegistry;
use App\Support\Providers\ProviderReasonCodes; use App\Support\Providers\ProviderReasonCodes;
use App\Support\Providers\TargetScope\ProviderConnectionTargetScopeNormalizer; use App\Support\Providers\TargetScope\ProviderConnectionTargetScopeNormalizer;
use App\Support\Verification\TenantPermissionCheckClusters; use App\Support\Verification\ManagedEnvironmentPermissionCheckClusters;
use App\Support\Verification\VerificationReportWriter; use App\Support\Verification\VerificationReportWriter;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
@ -99,7 +99,7 @@ public function handle(
$this->updateRunTargetScope($this->operationRun, $connection, $entraTenantName); $this->updateRunTargetScope($this->operationRun, $connection, $entraTenantName);
$permissionService = app(TenantPermissionService::class); $permissionService = app(ManagedEnvironmentPermissionService::class);
$graphOptions = null; $graphOptions = null;
@ -178,7 +178,7 @@ public function handle(
]; ];
} }
$permissionChecks = TenantPermissionCheckClusters::buildChecks($tenant, $permissionRows, $inventory); $permissionChecks = ManagedEnvironmentPermissionCheckClusters::buildChecks($tenant, $permissionRows, $inventory);
$targetScope = app(ProviderConnectionTargetScopeNormalizer::class) $targetScope = app(ProviderConnectionTargetScopeNormalizer::class)
->descriptorForConnection($connection) ->descriptorForConnection($connection)
->toArray(); ->toArray();

View File

@ -6,15 +6,15 @@
use App\Support\Concerns\DerivesWorkspaceIdFromTenant; use App\Support\Concerns\DerivesWorkspaceIdFromTenant;
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1; use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
class TenantReview extends Model class EnvironmentReview extends Model
{ {
use DerivesWorkspaceIdFromTenant; use DerivesWorkspaceIdFromTenant;
use HasFactory; use HasFactory;
@ -107,11 +107,11 @@ public function supersededReviews(): HasMany
} }
/** /**
* @return HasMany<TenantReviewSection, $this> * @return HasMany<EnvironmentReviewSection, $this>
*/ */
public function sections(): HasMany public function sections(): HasMany
{ {
return $this->hasMany(TenantReviewSection::class)->orderBy('sort_order')->orderBy('id'); return $this->hasMany(EnvironmentReviewSection::class)->orderBy('sort_order')->orderBy('id');
} }
/** /**
@ -146,7 +146,7 @@ public function scopeForWorkspace(Builder $query, int $workspaceId): Builder
*/ */
public function scopePublished(Builder $query): Builder public function scopePublished(Builder $query): Builder
{ {
return $query->where('status', TenantReviewStatus::Published->value); return $query->where('status', EnvironmentReviewStatus::Published->value);
} }
/** /**
@ -156,21 +156,21 @@ public function scopePublished(Builder $query): Builder
public function scopeMutable(Builder $query): Builder public function scopeMutable(Builder $query): Builder
{ {
return $query->whereIn('status', [ return $query->whereIn('status', [
TenantReviewStatus::Draft->value, EnvironmentReviewStatus::Draft->value,
TenantReviewStatus::Ready->value, EnvironmentReviewStatus::Ready->value,
TenantReviewStatus::Failed->value, EnvironmentReviewStatus::Failed->value,
]); ]);
} }
public function statusEnum(): TenantReviewStatus public function statusEnum(): EnvironmentReviewStatus
{ {
return TenantReviewStatus::from((string) $this->status); return EnvironmentReviewStatus::from((string) $this->status);
} }
public function completenessEnum(): TenantReviewCompletenessState public function completenessEnum(): EnvironmentReviewCompletenessState
{ {
return TenantReviewCompletenessState::tryFrom((string) $this->completeness_state) return EnvironmentReviewCompletenessState::tryFrom((string) $this->completeness_state)
?? TenantReviewCompletenessState::Missing; ?? EnvironmentReviewCompletenessState::Missing;
} }
public function isPublished(): bool public function isPublished(): bool
@ -253,12 +253,12 @@ public function controlInterpretationLimitationCounts(): array
->all(); ->all();
} }
public function controlInterpretationSection(): ?TenantReviewSection public function controlInterpretationSection(): ?EnvironmentReviewSection
{ {
if ($this->relationLoaded('sections')) { if ($this->relationLoaded('sections')) {
$section = $this->sections->firstWhere('section_key', ComplianceEvidenceMappingV1::SECTION_KEY); $section = $this->sections->firstWhere('section_key', ComplianceEvidenceMappingV1::SECTION_KEY);
return $section instanceof TenantReviewSection ? $section : null; return $section instanceof EnvironmentReviewSection ? $section : null;
} }
return $this->sections() return $this->sections()

View File

@ -4,14 +4,14 @@
namespace App\Models; namespace App\Models;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1; use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TenantReviewSection extends Model class EnvironmentReviewSection extends Model
{ {
use HasFactory; use HasFactory;
@ -31,11 +31,11 @@ protected function casts(): array
} }
/** /**
* @return BelongsTo<TenantReview, $this> * @return BelongsTo<EnvironmentReview, $this>
*/ */
public function tenantReview(): BelongsTo public function environmentReview(): BelongsTo
{ {
return $this->belongsTo(TenantReview::class); return $this->belongsTo(EnvironmentReview::class);
} }
/** /**
@ -63,10 +63,10 @@ public function scopeRequired(Builder $query): Builder
return $query->where('required', true); return $query->where('required', true);
} }
public function completenessEnum(): TenantReviewCompletenessState public function completenessEnum(): EnvironmentReviewCompletenessState
{ {
return TenantReviewCompletenessState::tryFrom((string) $this->completeness_state) return EnvironmentReviewCompletenessState::tryFrom((string) $this->completeness_state)
?? TenantReviewCompletenessState::Missing; ?? EnvironmentReviewCompletenessState::Missing;
} }
public function isControlInterpretation(): bool public function isControlInterpretation(): bool

View File

@ -81,11 +81,11 @@ public function reviewPacks(): HasMany
} }
/** /**
* @return HasMany<TenantReview, $this> * @return HasMany<EnvironmentReview, $this>
*/ */
public function tenantReviews(): HasMany public function environmentReviews(): HasMany
{ {
return $this->hasMany(TenantReview::class); return $this->hasMany(EnvironmentReview::class);
} }
/** /**

View File

@ -411,9 +411,9 @@ public function evidenceSnapshots(): HasMany
return $this->hasMany(EvidenceSnapshot::class); return $this->hasMany(EvidenceSnapshot::class);
} }
public function tenantReviews(): HasMany public function environmentReviews(): HasMany
{ {
return $this->hasMany(TenantReview::class); return $this->hasMany(EnvironmentReview::class);
} }
public function settings(): HasMany public function settings(): HasMany
@ -423,7 +423,7 @@ public function settings(): HasMany
public function permissions(): HasMany public function permissions(): HasMany
{ {
return $this->hasMany(TenantPermission::class); return $this->hasMany(ManagedEnvironmentPermission::class);
} }
public function providerConnections(): HasMany public function providerConnections(): HasMany

View File

@ -12,12 +12,12 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TenantOnboardingSession extends Model class ManagedEnvironmentOnboardingSession extends Model
{ {
/** @use HasFactory<\Database\Factories\TenantOnboardingSessionFactory> */ /** @use HasFactory<\Database\Factories\ManagedEnvironmentOnboardingSessionFactory> */
use HasFactory; use HasFactory;
protected $table = 'managed_tenant_onboarding_sessions'; protected $table = 'managed_environment_onboarding_sessions';
/** /**
* @var array<int, string> * @var array<int, string>
@ -25,7 +25,7 @@ class TenantOnboardingSession extends Model
public const STATE_ALLOWED_KEYS = [ public const STATE_ALLOWED_KEYS = [
'entra_tenant_id', 'entra_tenant_id',
'managed_environment_id', 'managed_environment_id',
'tenant_name', 'environment_name',
'environment', 'environment',
'primary_domain', 'primary_domain',
'notes', 'notes',
@ -80,11 +80,19 @@ public function workspace(): BelongsTo
/** /**
* @return BelongsTo<ManagedEnvironment, $this> * @return BelongsTo<ManagedEnvironment, $this>
*/ */
public function tenant(): BelongsTo public function managedEnvironment(): BelongsTo
{ {
return $this->belongsTo(ManagedEnvironment::class, 'managed_environment_id')->withTrashed(); return $this->belongsTo(ManagedEnvironment::class, 'managed_environment_id')->withTrashed();
} }
/**
* @return BelongsTo<ManagedEnvironment, $this>
*/
public function tenant(): BelongsTo
{
return $this->managedEnvironment();
}
/** /**
* @return BelongsTo<User, $this> * @return BelongsTo<User, $this>
*/ */

View File

@ -7,7 +7,7 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TenantPermission extends Model class ManagedEnvironmentPermission extends Model
{ {
use DerivesWorkspaceIdFromTenant; use DerivesWorkspaceIdFromTenant;
use HasFactory; use HasFactory;

View File

@ -10,7 +10,7 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TenantTriageReview extends Model class ManagedEnvironmentTriageReview extends Model
{ {
use HasFactory; use HasFactory;

View File

@ -80,11 +80,11 @@ public function evidenceSnapshot(): BelongsTo
} }
/** /**
* @return BelongsTo<TenantReview, $this> * @return BelongsTo<EnvironmentReview, $this>
*/ */
public function tenantReview(): BelongsTo public function environmentReview(): BelongsTo
{ {
return $this->belongsTo(TenantReview::class); return $this->belongsTo(EnvironmentReview::class);
} }
/** /**

View File

@ -5,14 +5,14 @@
namespace App\Policies; namespace App\Policies;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Services\Auth\CapabilityResolver; use App\Services\Auth\CapabilityResolver;
use App\Support\Auth\Capabilities; use App\Support\Auth\Capabilities;
use Illuminate\Auth\Access\HandlesAuthorization; use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response; use Illuminate\Auth\Access\Response;
class TenantReviewPolicy class EnvironmentReviewPolicy
{ {
use HandlesAuthorization; use HandlesAuthorization;
@ -24,10 +24,10 @@ public function viewAny(User $user): bool
return false; return false;
} }
return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::TENANT_REVIEW_VIEW); return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::ENVIRONMENT_REVIEW_VIEW);
} }
public function view(User $user, TenantReview $review): Response|bool public function view(User $user, EnvironmentReview $review): Response|bool
{ {
$tenant = $this->authorizedTenantOrNull($user, $review); $tenant = $this->authorizedTenantOrNull($user, $review);
@ -35,7 +35,7 @@ public function view(User $user, TenantReview $review): Response|bool
return Response::denyAsNotFound(); return Response::denyAsNotFound();
} }
return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::TENANT_REVIEW_VIEW) return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::ENVIRONMENT_REVIEW_VIEW)
? true ? true
: Response::deny(); : Response::deny();
} }
@ -48,35 +48,35 @@ public function create(User $user): bool
return false; return false;
} }
return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::TENANT_REVIEW_MANAGE); return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::ENVIRONMENT_REVIEW_MANAGE);
} }
public function refresh(User $user, TenantReview $review): Response|bool public function refresh(User $user, EnvironmentReview $review): Response|bool
{ {
return $this->authorizeManageAction($user, $review); return $this->authorizeManageAction($user, $review);
} }
public function publish(User $user, TenantReview $review): Response|bool public function publish(User $user, EnvironmentReview $review): Response|bool
{ {
return $this->authorizeManageAction($user, $review); return $this->authorizeManageAction($user, $review);
} }
public function archive(User $user, TenantReview $review): Response|bool public function archive(User $user, EnvironmentReview $review): Response|bool
{ {
return $this->authorizeManageAction($user, $review); return $this->authorizeManageAction($user, $review);
} }
public function export(User $user, TenantReview $review): Response|bool public function export(User $user, EnvironmentReview $review): Response|bool
{ {
return $this->authorizeManageAction($user, $review); return $this->authorizeManageAction($user, $review);
} }
public function createNextReview(User $user, TenantReview $review): Response|bool public function createNextReview(User $user, EnvironmentReview $review): Response|bool
{ {
return $this->authorizeManageAction($user, $review); return $this->authorizeManageAction($user, $review);
} }
private function authorizeManageAction(User $user, TenantReview $review): Response|bool private function authorizeManageAction(User $user, EnvironmentReview $review): Response|bool
{ {
$tenant = $this->authorizedTenantOrNull($user, $review); $tenant = $this->authorizedTenantOrNull($user, $review);
@ -84,12 +84,12 @@ private function authorizeManageAction(User $user, TenantReview $review): Respon
return Response::denyAsNotFound(); return Response::denyAsNotFound();
} }
return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::TENANT_REVIEW_MANAGE) return app(CapabilityResolver::class)->can($user, $tenant, Capabilities::ENVIRONMENT_REVIEW_MANAGE)
? true ? true
: Response::deny(); : Response::deny();
} }
private function authorizedTenantOrNull(User $user, TenantReview $review): ?ManagedEnvironment private function authorizedTenantOrNull(User $user, EnvironmentReview $review): ?ManagedEnvironment
{ {
$tenant = $review->tenant; $tenant = $review->tenant;

View File

@ -5,7 +5,7 @@
namespace App\Policies; namespace App\Policies;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Auth\WorkspaceCapabilityResolver; use App\Services\Auth\WorkspaceCapabilityResolver;
@ -17,7 +17,7 @@
use Illuminate\Auth\Access\Response; use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
class TenantOnboardingSessionPolicy class ManagedEnvironmentOnboardingSessionPolicy
{ {
public function viewAny(User $user): bool|Response public function viewAny(User $user): bool|Response
{ {
@ -27,33 +27,33 @@ public function viewAny(User $user): bool|Response
return Response::denyAsNotFound(); return Response::denyAsNotFound();
} }
return $this->authorizeForWorkspace($user, $workspace, Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD); return $this->authorizeForWorkspace($user, $workspace, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD);
} }
public function view(User $user, TenantOnboardingSession $tenantOnboardingSession): bool|Response public function view(User $user, ManagedEnvironmentOnboardingSession $environmentOnboardingSession): bool|Response
{ {
return $this->authorizeForDraft( return $this->authorizeForDraft(
user: $user, user: $user,
tenantOnboardingSession: $tenantOnboardingSession, environmentOnboardingSession: $environmentOnboardingSession,
capability: Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD, capability: Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD,
); );
} }
public function update(User $user, TenantOnboardingSession $tenantOnboardingSession): bool|Response public function update(User $user, ManagedEnvironmentOnboardingSession $environmentOnboardingSession): bool|Response
{ {
return $this->authorizeForDraft( return $this->authorizeForDraft(
user: $user, user: $user,
tenantOnboardingSession: $tenantOnboardingSession, environmentOnboardingSession: $environmentOnboardingSession,
capability: Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD, capability: Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD,
); );
} }
public function cancel(User $user, TenantOnboardingSession $tenantOnboardingSession): bool|Response public function cancel(User $user, ManagedEnvironmentOnboardingSession $environmentOnboardingSession): bool|Response
{ {
return $this->authorizeForDraft( return $this->authorizeForDraft(
user: $user, user: $user,
tenantOnboardingSession: $tenantOnboardingSession, environmentOnboardingSession: $environmentOnboardingSession,
capability: Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CANCEL, capability: Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CANCEL,
); );
} }
@ -83,7 +83,7 @@ private function currentWorkspace(User $user): ?Workspace
private function authorizeForDraft( private function authorizeForDraft(
User $user, User $user,
TenantOnboardingSession $tenantOnboardingSession, ManagedEnvironmentOnboardingSession $environmentOnboardingSession,
string $capability, string $capability,
): bool|Response { ): bool|Response {
$workspace = $this->currentWorkspace($user); $workspace = $this->currentWorkspace($user);
@ -92,11 +92,11 @@ private function authorizeForDraft(
return Response::denyAsNotFound(); return Response::denyAsNotFound();
} }
if ((int) $tenantOnboardingSession->workspace_id !== (int) $workspace->getKey()) { if ((int) $environmentOnboardingSession->workspace_id !== (int) $workspace->getKey()) {
return Response::denyAsNotFound(); return Response::denyAsNotFound();
} }
$tenant = $tenantOnboardingSession->tenant; $tenant = $environmentOnboardingSession->managedEnvironment;
if ($tenant instanceof ManagedEnvironment) { if ($tenant instanceof ManagedEnvironment) {
if ((int) $tenant->workspace_id !== (int) $workspace->getKey()) { if ((int) $tenant->workspace_id !== (int) $workspace->getKey()) {
@ -140,7 +140,7 @@ private function authorizeForWorkspace(User $user, Workspace $workspace, string
private function forbiddenCapabilityMessage(string $capability): string private function forbiddenCapabilityMessage(string $capability): string
{ {
return match ($capability) { return match ($capability) {
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CANCEL => 'You do not have permission to cancel this onboarding draft.', Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CANCEL => 'You do not have permission to cancel this onboarding draft.',
default => 'You do not have permission to continue this onboarding draft.', default => 'You do not have permission to continue this onboarding draft.',
}; };
} }

View File

@ -8,8 +8,8 @@
use App\Models\PlatformUser; use App\Models\PlatformUser;
use App\Models\ProviderConnection; use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Models\WorkspaceSetting; use App\Models\WorkspaceSetting;
@ -17,8 +17,8 @@
use App\Policies\AlertDestinationPolicy; use App\Policies\AlertDestinationPolicy;
use App\Policies\AlertRulePolicy; use App\Policies\AlertRulePolicy;
use App\Policies\ProviderConnectionPolicy; use App\Policies\ProviderConnectionPolicy;
use App\Policies\TenantOnboardingSessionPolicy; use App\Policies\ManagedEnvironmentOnboardingSessionPolicy;
use App\Policies\TenantReviewPolicy; use App\Policies\EnvironmentReviewPolicy;
use App\Policies\WorkspaceSettingPolicy; use App\Policies\WorkspaceSettingPolicy;
use App\Services\Auth\CapabilityResolver; use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver; use App\Services\Auth\WorkspaceCapabilityResolver;
@ -31,8 +31,8 @@ class AuthServiceProvider extends ServiceProvider
{ {
protected $policies = [ protected $policies = [
ProviderConnection::class => ProviderConnectionPolicy::class, ProviderConnection::class => ProviderConnectionPolicy::class,
TenantOnboardingSession::class => TenantOnboardingSessionPolicy::class, ManagedEnvironmentOnboardingSession::class => ManagedEnvironmentOnboardingSessionPolicy::class,
TenantReview::class => TenantReviewPolicy::class, EnvironmentReview::class => EnvironmentReviewPolicy::class,
WorkspaceSetting::class => WorkspaceSettingPolicy::class, WorkspaceSetting::class => WorkspaceSettingPolicy::class,
AlertDestination::class => AlertDestinationPolicy::class, AlertDestination::class => AlertDestinationPolicy::class,
AlertDelivery::class => AlertDeliveryPolicy::class, AlertDelivery::class => AlertDeliveryPolicy::class,

View File

@ -4,9 +4,9 @@
use App\Filament\Pages\Auth\Login; use App\Filament\Pages\Auth\Login;
use App\Filament\Pages\BaselineCompareLanding; use App\Filament\Pages\BaselineCompareLanding;
use App\Filament\Pages\ChooseTenant; use App\Filament\Pages\ChooseEnvironment;
use App\Filament\Pages\ChooseWorkspace; use App\Filament\Pages\ChooseWorkspace;
use App\Filament\Pages\CrossTenantComparePage; use App\Filament\Pages\CrossEnvironmentComparePage;
use App\Filament\Pages\Findings\FindingsHygieneReport; use App\Filament\Pages\Findings\FindingsHygieneReport;
use App\Filament\Pages\Findings\FindingsIntakeQueue; use App\Filament\Pages\Findings\FindingsIntakeQueue;
use App\Filament\Pages\Governance\DecisionRegister; use App\Filament\Pages\Governance\DecisionRegister;
@ -18,7 +18,7 @@
use App\Filament\Pages\Reviews\ReviewRegister; use App\Filament\Pages\Reviews\ReviewRegister;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace; use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Pages\Settings\WorkspaceSettings; use App\Filament\Pages\Settings\WorkspaceSettings;
use App\Filament\Pages\TenantRequiredPermissions; use App\Filament\Pages\EnvironmentRequiredPermissions;
use App\Filament\Pages\WorkspaceOverview; use App\Filament\Pages\WorkspaceOverview;
use App\Filament\Resources\AlertDeliveryResource; use App\Filament\Resources\AlertDeliveryResource;
use App\Filament\Resources\AlertDestinationResource; use App\Filament\Resources\AlertDestinationResource;
@ -28,7 +28,7 @@
use App\Filament\Resources\InventoryItemResource; use App\Filament\Resources\InventoryItemResource;
use App\Filament\Resources\PolicyResource; use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\ProviderConnectionResource; use App\Filament\Resources\ProviderConnectionResource;
use App\Filament\Resources\TenantReviewResource; use App\Filament\Resources\EnvironmentReviewResource;
use App\Filament\Resources\Workspaces\WorkspaceResource; use App\Filament\Resources\Workspaces\WorkspaceResource;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
@ -75,7 +75,7 @@ public function panel(Panel $panel): Panel
->font(null, provider: LocalFontProvider::class, preload: []) ->font(null, provider: LocalFontProvider::class, preload: [])
->authenticatedRoutes(function (Panel $panel): void { ->authenticatedRoutes(function (Panel $panel): void {
ChooseWorkspace::registerRoutes($panel); ChooseWorkspace::registerRoutes($panel);
ChooseTenant::registerRoutes($panel); ChooseEnvironment::registerRoutes($panel);
NoAccess::registerRoutes($panel); NoAccess::registerRoutes($panel);
}) })
->colors([ ->colors([
@ -166,7 +166,7 @@ public function panel(Panel $panel): Panel
) )
->renderHook( ->renderHook(
PanelsRenderHook::PAGE_START, PanelsRenderHook::PAGE_START,
fn (): string => request()->routeIs('admin.workspace.managed-tenants.index', 'admin.onboarding', 'admin.onboarding.draft', 'filament.admin.pages.choose-tenant') fn (): string => request()->routeIs('admin.workspace.managed-environments.index', 'admin.onboarding', 'admin.onboarding.draft', 'filament.admin.pages.choose-environment')
? '' ? ''
: ((bool) config('tenantpilot.bulk_operations.progress_widget_enabled', true) : ((bool) config('tenantpilot.bulk_operations.progress_widget_enabled', true)
? view('livewire.bulk-operation-progress-wrapper')->render() ? view('livewire.bulk-operation-progress-wrapper')->render()
@ -182,16 +182,16 @@ public function panel(Panel $panel): Panel
WorkspaceResource::class, WorkspaceResource::class,
BaselineProfileResource::class, BaselineProfileResource::class,
BaselineSnapshotResource::class, BaselineSnapshotResource::class,
TenantReviewResource::class, EnvironmentReviewResource::class,
]) ])
->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters') ->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->pages([ ->pages([
BaselineCompareLanding::class, BaselineCompareLanding::class,
InventoryCoverage::class, InventoryCoverage::class,
TenantRequiredPermissions::class, EnvironmentRequiredPermissions::class,
WorkspaceSettings::class, WorkspaceSettings::class,
CrossTenantComparePage::class, CrossEnvironmentComparePage::class,
GovernanceInbox::class, GovernanceInbox::class,
DecisionRegister::class, DecisionRegister::class,
FindingsHygieneReport::class, FindingsHygieneReport::class,

View File

@ -143,10 +143,10 @@ public function logSupportDiagnosticsOpened(
/** /**
* @param array<string, mixed> $preflight * @param array<string, mixed> $preflight
*/ */
public function logCrossTenantPromotionPreflightGenerated( public function logCrossEnvironmentPromotionPreflightGenerated(
Workspace $workspace, Workspace $workspace,
ManagedEnvironment $sourceTenant, ManagedEnvironment $sourceEnvironment,
ManagedEnvironment $targetTenant, ManagedEnvironment $targetEnvironment,
array $preflight, array $preflight,
User|PlatformUser|null $actor = null, User|PlatformUser|null $actor = null,
): \App\Models\AuditLog { ): \App\Models\AuditLog {
@ -154,12 +154,12 @@ public function logCrossTenantPromotionPreflightGenerated(
return $this->log( return $this->log(
workspace: $workspace, workspace: $workspace,
action: AuditActionId::CrossTenantPromotionPreflightGenerated, action: AuditActionId::CrossEnvironmentPromotionPreflightGenerated,
context: [ context: [
'source_tenant_id' => (int) $sourceTenant->getKey(), 'source_environment_id' => (int) $sourceEnvironment->getKey(),
'source_tenant_name' => (string) $sourceTenant->name, 'source_environment_name' => (string) $sourceEnvironment->name,
'target_tenant_id' => (int) $targetTenant->getKey(), 'target_environment_id' => (int) $targetEnvironment->getKey(),
'target_tenant_name' => (string) $targetTenant->name, 'target_environment_name' => (string) $targetEnvironment->name,
'ready_count' => (int) ($summary['ready'] ?? 0), 'ready_count' => (int) ($summary['ready'] ?? 0),
'blocked_count' => (int) ($summary['blocked'] ?? 0), 'blocked_count' => (int) ($summary['blocked'] ?? 0),
'manual_mapping_required_count' => (int) ($summary['manual_mapping_required'] ?? 0), 'manual_mapping_required_count' => (int) ($summary['manual_mapping_required'] ?? 0),
@ -170,20 +170,20 @@ public function logCrossTenantPromotionPreflightGenerated(
], ],
actor: $actor, actor: $actor,
status: 'success', status: 'success',
resourceType: 'cross_tenant_promotion_preflight', resourceType: 'cross_environment_promotion_preflight',
resourceId: sprintf('%s:%s', $sourceTenant->getKey(), $targetTenant->getKey()), resourceId: sprintf('%s:%s', $sourceEnvironment->getKey(), $targetEnvironment->getKey()),
targetLabel: $sourceTenant->name.' -> '.$targetTenant->name, targetLabel: $sourceEnvironment->name.' -> '.$targetEnvironment->name,
summary: 'Cross-tenant promotion preflight generated for '.$sourceTenant->name.' -> '.$targetTenant->name, summary: 'Cross-environment promotion preflight generated for '.$sourceEnvironment->name.' -> '.$targetEnvironment->name,
); );
} }
/** /**
* @param array<string, mixed> $plan * @param array<string, mixed> $plan
*/ */
public function logCrossTenantPromotionExecutionQueued( public function logCrossEnvironmentPromotionExecutionQueued(
Workspace $workspace, Workspace $workspace,
ManagedEnvironment $sourceTenant, ManagedEnvironment $sourceEnvironment,
ManagedEnvironment $targetTenant, ManagedEnvironment $targetEnvironment,
OperationRun $operationRun, OperationRun $operationRun,
array $plan, array $plan,
User|PlatformUser|null $actor = null, User|PlatformUser|null $actor = null,
@ -192,12 +192,12 @@ public function logCrossTenantPromotionExecutionQueued(
return $this->log( return $this->log(
workspace: $workspace, workspace: $workspace,
action: AuditActionId::CrossTenantPromotionExecutionQueued, action: AuditActionId::CrossEnvironmentPromotionExecutionQueued,
context: [ context: [
'source_tenant_id' => (int) $sourceTenant->getKey(), 'source_environment_id' => (int) $sourceEnvironment->getKey(),
'source_tenant_name' => (string) $sourceTenant->name, 'source_environment_name' => (string) $sourceEnvironment->name,
'target_tenant_id' => (int) $targetTenant->getKey(), 'target_environment_id' => (int) $targetEnvironment->getKey(),
'target_tenant_name' => (string) $targetTenant->name, 'target_environment_name' => (string) $targetEnvironment->name,
'selection' => is_array($plan['selection'] ?? null) ? $plan['selection'] : [], 'selection' => is_array($plan['selection'] ?? null) ? $plan['selection'] : [],
'ready_count' => (int) ($summary['ready'] ?? 0), 'ready_count' => (int) ($summary['ready'] ?? 0),
'excluded_count' => (int) ($summary['excluded'] ?? 0), 'excluded_count' => (int) ($summary['excluded'] ?? 0),
@ -208,36 +208,36 @@ public function logCrossTenantPromotionExecutionQueued(
status: 'queued', status: 'queued',
resourceType: 'operation_run', resourceType: 'operation_run',
resourceId: (string) $operationRun->getKey(), resourceId: (string) $operationRun->getKey(),
targetLabel: $sourceTenant->name.' -> '.$targetTenant->name, targetLabel: $sourceEnvironment->name.' -> '.$targetEnvironment->name,
summary: 'Cross-tenant promotion execution queued for '.$sourceTenant->name.' -> '.$targetTenant->name, summary: 'Cross-environment promotion execution queued for '.$sourceEnvironment->name.' -> '.$targetEnvironment->name,
operationRunId: (int) $operationRun->getKey(), operationRunId: (int) $operationRun->getKey(),
tenant: $targetTenant, tenant: $targetEnvironment,
); );
} }
/** /**
* @param array<string, int> $summaryCounts * @param array<string, int> $summaryCounts
*/ */
public function logCrossTenantPromotionExecutionCompleted( public function logCrossEnvironmentPromotionExecutionCompleted(
OperationRun $operationRun, OperationRun $operationRun,
?int $sourceTenantId, ?int $sourceEnvironmentId,
ManagedEnvironment $targetTenant, ManagedEnvironment $targetEnvironment,
array $summaryCounts, array $summaryCounts,
?RestoreRun $restoreRun = null, ?RestoreRun $restoreRun = null,
): \App\Models\AuditLog { ): \App\Models\AuditLog {
$context = is_array($operationRun->context) ? $operationRun->context : []; $context = is_array($operationRun->context) ? $operationRun->context : [];
$sourceTenantName = is_string($context['source_tenant_name'] ?? null) $sourceEnvironmentName = is_string($context['source_environment_name'] ?? null)
? (string) $context['source_tenant_name'] ? (string) $context['source_environment_name']
: null; : null;
return $this->log( return $this->log(
workspace: $targetTenant->workspace, workspace: $targetEnvironment->workspace,
action: AuditActionId::CrossTenantPromotionExecutionCompleted, action: AuditActionId::CrossEnvironmentPromotionExecutionCompleted,
context: [ context: [
'source_tenant_id' => $sourceTenantId, 'source_environment_id' => $sourceEnvironmentId,
'source_tenant_name' => $sourceTenantName, 'source_environment_name' => $sourceEnvironmentName,
'target_tenant_id' => (int) $targetTenant->getKey(), 'target_environment_id' => (int) $targetEnvironment->getKey(),
'target_tenant_name' => (string) $targetTenant->name, 'target_environment_name' => (string) $targetEnvironment->name,
'summary_counts' => $summaryCounts, 'summary_counts' => $summaryCounts,
'restore_run_id' => $restoreRun?->getKey(), 'restore_run_id' => $restoreRun?->getKey(),
'operation_outcome' => (string) $operationRun->outcome, 'operation_outcome' => (string) $operationRun->outcome,
@ -249,10 +249,10 @@ public function logCrossTenantPromotionExecutionCompleted(
}, },
resourceType: 'operation_run', resourceType: 'operation_run',
resourceId: (string) $operationRun->getKey(), resourceId: (string) $operationRun->getKey(),
targetLabel: ($sourceTenantName !== null ? $sourceTenantName.' -> ' : '').$targetTenant->name, targetLabel: ($sourceEnvironmentName !== null ? $sourceEnvironmentName.' -> ' : '').$targetEnvironment->name,
summary: 'Cross-tenant promotion execution completed for '.(($sourceTenantName !== null ? $sourceTenantName.' -> ' : '')).$targetTenant->name, summary: 'Cross-environment promotion execution completed for '.(($sourceEnvironmentName !== null ? $sourceEnvironmentName.' -> ' : '')).$targetEnvironment->name,
operationRunId: (int) $operationRun->getKey(), operationRunId: (int) $operationRun->getKey(),
tenant: $targetTenant, tenant: $targetEnvironment,
); );
} }

View File

@ -11,7 +11,7 @@
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class TenantDiagnosticsService class ManagedEnvironmentDiagnosticsService
{ {
public function __construct(public AuditLogger $auditLogger) {} public function __construct(public AuditLogger $auditLogger) {}

View File

@ -13,7 +13,7 @@
use DomainException; use DomainException;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class TenantMembershipManager class ManagedEnvironmentMembershipManager
{ {
private const string SCOPE_PLACEHOLDER_ROLE = 'readonly'; private const string SCOPE_PLACEHOLDER_ROLE = 'readonly';

View File

@ -55,9 +55,9 @@ class RoleCapabilityMap
Capabilities::REVIEW_PACK_VIEW, Capabilities::REVIEW_PACK_VIEW,
Capabilities::REVIEW_PACK_MANAGE, Capabilities::REVIEW_PACK_MANAGE,
Capabilities::TENANT_REVIEW_VIEW, Capabilities::ENVIRONMENT_REVIEW_VIEW,
Capabilities::TENANT_REVIEW_MANAGE, Capabilities::ENVIRONMENT_REVIEW_MANAGE,
Capabilities::TENANT_TRIAGE_REVIEW_MANAGE, Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE,
Capabilities::EVIDENCE_VIEW, Capabilities::EVIDENCE_VIEW,
Capabilities::EVIDENCE_MANAGE, Capabilities::EVIDENCE_MANAGE,
], ],
@ -99,9 +99,9 @@ class RoleCapabilityMap
Capabilities::REVIEW_PACK_VIEW, Capabilities::REVIEW_PACK_VIEW,
Capabilities::REVIEW_PACK_MANAGE, Capabilities::REVIEW_PACK_MANAGE,
Capabilities::TENANT_REVIEW_VIEW, Capabilities::ENVIRONMENT_REVIEW_VIEW,
Capabilities::TENANT_REVIEW_MANAGE, Capabilities::ENVIRONMENT_REVIEW_MANAGE,
Capabilities::TENANT_TRIAGE_REVIEW_MANAGE, Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE,
Capabilities::EVIDENCE_VIEW, Capabilities::EVIDENCE_VIEW,
Capabilities::EVIDENCE_MANAGE, Capabilities::EVIDENCE_MANAGE,
], ],
@ -131,8 +131,8 @@ class RoleCapabilityMap
Capabilities::PERMISSION_POSTURE_VIEW, Capabilities::PERMISSION_POSTURE_VIEW,
Capabilities::REVIEW_PACK_VIEW, Capabilities::REVIEW_PACK_VIEW,
Capabilities::TENANT_REVIEW_VIEW, Capabilities::ENVIRONMENT_REVIEW_VIEW,
Capabilities::TENANT_TRIAGE_REVIEW_MANAGE, Capabilities::MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE,
Capabilities::EVIDENCE_VIEW, Capabilities::EVIDENCE_VIEW,
], ],
@ -153,7 +153,7 @@ class RoleCapabilityMap
Capabilities::PERMISSION_POSTURE_VIEW, Capabilities::PERMISSION_POSTURE_VIEW,
Capabilities::REVIEW_PACK_VIEW, Capabilities::REVIEW_PACK_VIEW,
Capabilities::TENANT_REVIEW_VIEW, Capabilities::ENVIRONMENT_REVIEW_VIEW,
Capabilities::EVIDENCE_VIEW, Capabilities::EVIDENCE_VIEW,
], ],
]; ];

View File

@ -23,17 +23,17 @@ class WorkspaceRoleCapabilityMap
Capabilities::WORKSPACE_ARCHIVE, Capabilities::WORKSPACE_ARCHIVE,
Capabilities::WORKSPACE_MEMBERSHIP_VIEW, Capabilities::WORKSPACE_MEMBERSHIP_VIEW,
Capabilities::WORKSPACE_MEMBERSHIP_MANAGE, Capabilities::WORKSPACE_MEMBERSHIP_MANAGE,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_IDENTIFY, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_IDENTIFY,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CANCEL, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CANCEL,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_VIEW, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_VIEW,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_MANAGE,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE_DEDICATED, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_MANAGE_DEDICATED,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_VERIFICATION_START,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_POLICY_SYNC, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_POLICY_SYNC,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_ACTIVATE, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_ACTIVATE,
Capabilities::WORKSPACE_SETTINGS_VIEW, Capabilities::WORKSPACE_SETTINGS_VIEW,
Capabilities::WORKSPACE_SETTINGS_MANAGE, Capabilities::WORKSPACE_SETTINGS_MANAGE,
Capabilities::ALERTS_VIEW, Capabilities::ALERTS_VIEW,
@ -48,15 +48,15 @@ class WorkspaceRoleCapabilityMap
Capabilities::WORKSPACE_VIEW, Capabilities::WORKSPACE_VIEW,
Capabilities::WORKSPACE_MEMBERSHIP_VIEW, Capabilities::WORKSPACE_MEMBERSHIP_VIEW,
Capabilities::WORKSPACE_MEMBERSHIP_MANAGE, Capabilities::WORKSPACE_MEMBERSHIP_MANAGE,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_IDENTIFY, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_IDENTIFY,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CANCEL, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CANCEL,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_VIEW, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_VIEW,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_MANAGE,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_VERIFICATION_START,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_POLICY_SYNC, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_POLICY_SYNC,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP,
Capabilities::WORKSPACE_SETTINGS_VIEW, Capabilities::WORKSPACE_SETTINGS_VIEW,
Capabilities::WORKSPACE_SETTINGS_MANAGE, Capabilities::WORKSPACE_SETTINGS_MANAGE,
Capabilities::ALERTS_VIEW, Capabilities::ALERTS_VIEW,
@ -70,11 +70,11 @@ class WorkspaceRoleCapabilityMap
WorkspaceRole::Operator->value => [ WorkspaceRole::Operator->value => [
Capabilities::WORKSPACE_VIEW, Capabilities::WORKSPACE_VIEW,
Capabilities::WORKSPACE_MEMBERSHIP_VIEW, Capabilities::WORKSPACE_MEMBERSHIP_VIEW,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_VIEW, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_VIEW,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_VERIFICATION_START,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_POLICY_SYNC, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_POLICY_SYNC,
Capabilities::WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP, Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP,
Capabilities::WORKSPACE_SETTINGS_VIEW, Capabilities::WORKSPACE_SETTINGS_VIEW,
Capabilities::ALERTS_VIEW, Capabilities::ALERTS_VIEW,
Capabilities::WORKSPACE_BASELINES_VIEW, Capabilities::WORKSPACE_BASELINES_VIEW,

View File

@ -91,7 +91,7 @@ public function startCapture(
], ],
], ],
'baseline_profile_id' => (int) $profile->getKey(), 'baseline_profile_id' => (int) $profile->getKey(),
'source_tenant_id' => (int) $sourceTenant->getKey(), 'source_environment_id' => (int) $sourceTenant->getKey(),
'effective_scope' => $effectiveScope->toEffectiveScopeContext($this->capabilityGuard, 'capture'), 'effective_scope' => $effectiveScope->toEffectiveScopeContext($this->capabilityGuard, 'capture'),
'capture_mode' => $captureMode->value, 'capture_mode' => $captureMode->value,
'baseline_capture' => [ 'baseline_capture' => [

View File

@ -94,7 +94,7 @@ public function resume(OperationRun $priorRun, User $initiator): array
$newContext = []; $newContext = [];
foreach (['target_scope', 'baseline_profile_id', 'baseline_snapshot_id', 'source_tenant_id', 'effective_scope', 'capture_mode'] as $key) { foreach (['target_scope', 'baseline_profile_id', 'baseline_snapshot_id', 'source_environment_id', 'effective_scope', 'capture_mode'] as $key) {
if (array_key_exists($key, $context)) { if (array_key_exists($key, $context)) {
$newContext[$key] = $context[$key]; $newContext[$key] = $context[$key];
} }

View File

@ -2,18 +2,18 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
final class TenantReviewComposer final class EnvironmentReviewComposer
{ {
public function __construct( public function __construct(
private readonly TenantReviewFingerprint $fingerprint, private readonly EnvironmentReviewFingerprint $fingerprint,
private readonly TenantReviewSectionFactory $sectionFactory, private readonly EnvironmentReviewSectionFactory $sectionFactory,
private readonly TenantReviewReadinessGate $readinessGate, private readonly EnvironmentReviewReadinessGate $readinessGate,
) {} ) {}
/** /**
@ -25,7 +25,7 @@ public function __construct(
* sections: list<array<string, mixed>> * sections: list<array<string, mixed>>
* } * }
*/ */
public function compose(EvidenceSnapshot $snapshot, ?TenantReview $review = null): array public function compose(EvidenceSnapshot $snapshot, ?EnvironmentReview $review = null): array
{ {
$tenant = $snapshot->tenant; $tenant = $snapshot->tenant;
@ -49,8 +49,8 @@ public function compose(EvidenceSnapshot $snapshot, ?TenantReview $review = null
$operationsSection = collect($sections) $operationsSection = collect($sections)
->firstWhere('section_key', 'operations_health'); ->firstWhere('section_key', 'operations_health');
if ($review instanceof TenantReview && $review->isPublished()) { if ($review instanceof EnvironmentReview && $review->isPublished()) {
$status = TenantReviewStatus::Published; $status = EnvironmentReviewStatus::Published;
} }
return [ return [

View File

@ -2,13 +2,13 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
final class TenantReviewFingerprint final class EnvironmentReviewFingerprint
{ {
public function forSnapshot(ManagedEnvironment $tenant, EvidenceSnapshot $snapshot): string public function forSnapshot(ManagedEnvironment $tenant, EvidenceSnapshot $snapshot): string
{ {

View File

@ -2,31 +2,31 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use App\Support\Ui\DerivedState\DerivedStateFamily; use App\Support\Ui\DerivedState\DerivedStateFamily;
use App\Support\Ui\DerivedState\RequestScopedDerivedStateStore; use App\Support\Ui\DerivedState\RequestScopedDerivedStateStore;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use InvalidArgumentException; use InvalidArgumentException;
final class TenantReviewLifecycleService final class EnvironmentReviewLifecycleService
{ {
public function __construct( public function __construct(
private readonly TenantReviewReadinessGate $readinessGate, private readonly EnvironmentReviewReadinessGate $readinessGate,
private readonly TenantReviewService $reviewService, private readonly EnvironmentReviewService $reviewService,
private readonly WorkspaceAuditLogger $auditLogger, private readonly WorkspaceAuditLogger $auditLogger,
private readonly RequestScopedDerivedStateStore $derivedStateStore, private readonly RequestScopedDerivedStateStore $derivedStateStore,
) {} ) {}
public function publish(TenantReview $review, User $user, string $reason): TenantReview public function publish(EnvironmentReview $review, User $user, string $reason): EnvironmentReview
{ {
$review->loadMissing(['tenant', 'sections', 'currentExportReviewPack']); $review->loadMissing(['tenant', 'sections', 'currentExportReviewPack']);
$tenant = $review->tenant; $tenant = $review->tenant;
@ -44,7 +44,7 @@ public function publish(TenantReview $review, User $user, string $reason): Tenan
} }
$review->forceFill([ $review->forceFill([
'status' => TenantReviewStatus::Published->value, 'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(), 'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(), 'published_by_user_id' => (int) $user->getKey(),
'summary' => array_merge(is_array($review->summary) ? $review->summary : [], [ 'summary' => array_merge(is_array($review->summary) ? $review->summary : [], [
@ -54,17 +54,17 @@ public function publish(TenantReview $review, User $user, string $reason): Tenan
$this->auditLogger->log( $this->auditLogger->log(
workspace: $tenant->workspace, workspace: $tenant->workspace,
action: AuditActionId::TenantReviewPublished, action: AuditActionId::EnvironmentReviewPublished,
context: [ context: [
'metadata' => [ 'metadata' => [
'review_id' => (int) $review->getKey(), 'review_id' => (int) $review->getKey(),
'before_status' => $beforeStatus, 'before_status' => $beforeStatus,
'after_status' => TenantReviewStatus::Published->value, 'after_status' => EnvironmentReviewStatus::Published->value,
'reason' => $reason, 'reason' => $reason,
], ],
], ],
actor: $user, actor: $user,
resourceType: 'tenant_review', resourceType: 'environment_review',
resourceId: (string) $review->getKey(), resourceId: (string) $review->getKey(),
targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()), targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()),
tenant: $tenant, tenant: $tenant,
@ -75,7 +75,7 @@ public function publish(TenantReview $review, User $user, string $reason): Tenan
return $review->refresh()->load(['tenant', 'sections', 'currentExportReviewPack']); return $review->refresh()->load(['tenant', 'sections', 'currentExportReviewPack']);
} }
public function archive(TenantReview $review, User $user, string $reason): TenantReview public function archive(EnvironmentReview $review, User $user, string $reason): EnvironmentReview
{ {
$review->loadMissing('tenant'); $review->loadMissing('tenant');
$tenant = $review->tenant; $tenant = $review->tenant;
@ -92,23 +92,23 @@ public function archive(TenantReview $review, User $user, string $reason): Tenan
} }
$review->forceFill([ $review->forceFill([
'status' => TenantReviewStatus::Archived->value, 'status' => EnvironmentReviewStatus::Archived->value,
'archived_at' => now(), 'archived_at' => now(),
])->save(); ])->save();
$this->auditLogger->log( $this->auditLogger->log(
workspace: $tenant->workspace, workspace: $tenant->workspace,
action: AuditActionId::TenantReviewArchived, action: AuditActionId::EnvironmentReviewArchived,
context: [ context: [
'metadata' => [ 'metadata' => [
'review_id' => (int) $review->getKey(), 'review_id' => (int) $review->getKey(),
'before_status' => $beforeStatus, 'before_status' => $beforeStatus,
'after_status' => TenantReviewStatus::Archived->value, 'after_status' => EnvironmentReviewStatus::Archived->value,
'reason' => $reason, 'reason' => $reason,
], ],
], ],
actor: $user, actor: $user,
resourceType: 'tenant_review', resourceType: 'environment_review',
resourceId: (string) $review->getKey(), resourceId: (string) $review->getKey(),
targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()), targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()),
tenant: $tenant, tenant: $tenant,
@ -119,7 +119,7 @@ public function archive(TenantReview $review, User $user, string $reason): Tenan
return $review->refresh()->load(['tenant', 'sections', 'currentExportReviewPack']); return $review->refresh()->load(['tenant', 'sections', 'currentExportReviewPack']);
} }
public function createNextReview(TenantReview $review, User $user, ?EvidenceSnapshot $snapshot = null): TenantReview public function createNextReview(EnvironmentReview $review, User $user, ?EvidenceSnapshot $snapshot = null): EnvironmentReview
{ {
$review->loadMissing(['tenant', 'evidenceSnapshot']); $review->loadMissing(['tenant', 'evidenceSnapshot']);
$tenant = $review->tenant; $tenant = $review->tenant;
@ -138,29 +138,29 @@ public function createNextReview(TenantReview $review, User $user, ?EvidenceSnap
throw new InvalidArgumentException('An eligible evidence snapshot is required to create the next review.'); throw new InvalidArgumentException('An eligible evidence snapshot is required to create the next review.');
} }
$nextReview = DB::transaction(function () use ($review, $user, $snapshot, $tenant): TenantReview { $nextReview = DB::transaction(function () use ($review, $user, $snapshot, $tenant): EnvironmentReview {
$nextReview = $this->reviewService->create($tenant, $snapshot, $user); $nextReview = $this->reviewService->create($tenant, $snapshot, $user);
if ((int) $nextReview->getKey() !== (int) $review->getKey()) { if ((int) $nextReview->getKey() !== (int) $review->getKey()) {
$review->forceFill([ $review->forceFill([
'status' => TenantReviewStatus::Superseded->value, 'status' => EnvironmentReviewStatus::Superseded->value,
'superseded_by_review_id' => (int) $nextReview->getKey(), 'superseded_by_review_id' => (int) $nextReview->getKey(),
])->save(); ])->save();
} }
$this->auditLogger->log( $this->auditLogger->log(
workspace: $tenant->workspace, workspace: $tenant->workspace,
action: AuditActionId::TenantReviewSuccessorCreated, action: AuditActionId::EnvironmentReviewSuccessorCreated,
context: [ context: [
'metadata' => [ 'metadata' => [
'review_id' => (int) $review->getKey(), 'review_id' => (int) $review->getKey(),
'next_review_id' => (int) $nextReview->getKey(), 'next_review_id' => (int) $nextReview->getKey(),
'before_status' => TenantReviewStatus::Published->value, 'before_status' => EnvironmentReviewStatus::Published->value,
'after_status' => TenantReviewStatus::Superseded->value, 'after_status' => EnvironmentReviewStatus::Superseded->value,
], ],
], ],
actor: $user, actor: $user,
resourceType: 'tenant_review', resourceType: 'environment_review',
resourceId: (string) $nextReview->getKey(), resourceId: (string) $nextReview->getKey(),
targetLabel: sprintf('ManagedEnvironment review #%d', (int) $nextReview->getKey()), targetLabel: sprintf('ManagedEnvironment review #%d', (int) $nextReview->getKey()),
tenant: $tenant, tenant: $tenant,
@ -194,9 +194,9 @@ private function validatedReason(mixed $reason, string $field): string
return $resolved; return $resolved;
} }
private function invalidateArtifactTruthCache(TenantReview $review): void private function invalidateArtifactTruthCache(EnvironmentReview $review): void
{ {
$this->derivedStateStore->invalidateModel(DerivedStateFamily::ArtifactTruth, $review, 'tenant_review'); $this->derivedStateStore->invalidateModel(DerivedStateFamily::ArtifactTruth, $review, 'environment_review');
$review->loadMissing('currentExportReviewPack'); $review->loadMissing('currentExportReviewPack');

View File

@ -2,14 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
final class TenantReviewReadinessGate final class EnvironmentReviewReadinessGate
{ {
/** /**
* @param iterable<array<string, mixed>> $sections * @param iterable<array<string, mixed>> $sections
@ -21,18 +21,18 @@ public function blockersForSections(iterable $sections): array
foreach ($sections as $section) { foreach ($sections as $section) {
$required = (bool) ($section['required'] ?? false); $required = (bool) ($section['required'] ?? false);
$state = (string) ($section['completeness_state'] ?? TenantReviewCompletenessState::Missing->value); $state = (string) ($section['completeness_state'] ?? EnvironmentReviewCompletenessState::Missing->value);
$title = (string) ($section['title'] ?? 'Review section'); $title = (string) ($section['title'] ?? 'Review section');
if (! $required) { if (! $required) {
continue; continue;
} }
if ($state === TenantReviewCompletenessState::Missing->value) { if ($state === EnvironmentReviewCompletenessState::Missing->value) {
$blockers[] = sprintf('%s is missing.', $title); $blockers[] = sprintf('%s is missing.', $title);
} }
if ($state === TenantReviewCompletenessState::Stale->value) { if ($state === EnvironmentReviewCompletenessState::Stale->value) {
$blockers[] = sprintf('%s is stale and must be refreshed before publication.', $title); $blockers[] = sprintf('%s is stale and must be refreshed before publication.', $title);
} }
} }
@ -43,45 +43,45 @@ public function blockersForSections(iterable $sections): array
/** /**
* @param iterable<array<string, mixed>> $sections * @param iterable<array<string, mixed>> $sections
*/ */
public function completenessForSections(iterable $sections): TenantReviewCompletenessState public function completenessForSections(iterable $sections): EnvironmentReviewCompletenessState
{ {
$states = collect($sections) $states = collect($sections)
->map(static fn (array $section): string => (string) ($section['completeness_state'] ?? TenantReviewCompletenessState::Missing->value)) ->map(static fn (array $section): string => (string) ($section['completeness_state'] ?? EnvironmentReviewCompletenessState::Missing->value))
->values(); ->values();
if ($states->isEmpty()) { if ($states->isEmpty()) {
return TenantReviewCompletenessState::Missing; return EnvironmentReviewCompletenessState::Missing;
} }
if ($states->contains(TenantReviewCompletenessState::Missing->value)) { if ($states->contains(EnvironmentReviewCompletenessState::Missing->value)) {
return TenantReviewCompletenessState::Missing; return EnvironmentReviewCompletenessState::Missing;
} }
if ($states->contains(TenantReviewCompletenessState::Stale->value)) { if ($states->contains(EnvironmentReviewCompletenessState::Stale->value)) {
return TenantReviewCompletenessState::Stale; return EnvironmentReviewCompletenessState::Stale;
} }
if ($states->contains(TenantReviewCompletenessState::Partial->value)) { if ($states->contains(EnvironmentReviewCompletenessState::Partial->value)) {
return TenantReviewCompletenessState::Partial; return EnvironmentReviewCompletenessState::Partial;
} }
return TenantReviewCompletenessState::Complete; return EnvironmentReviewCompletenessState::Complete;
} }
/** /**
* @param iterable<array<string, mixed>> $sections * @param iterable<array<string, mixed>> $sections
*/ */
public function statusForSections(iterable $sections): TenantReviewStatus public function statusForSections(iterable $sections): EnvironmentReviewStatus
{ {
return $this->blockersForSections($sections) === [] return $this->blockersForSections($sections) === []
? TenantReviewStatus::Ready ? EnvironmentReviewStatus::Ready
: TenantReviewStatus::Draft; : EnvironmentReviewStatus::Draft;
} }
/** /**
* @return list<string> * @return list<string>
*/ */
public function blockersForReview(TenantReview $review): array public function blockersForReview(EnvironmentReview $review): array
{ {
$sections = $review->relationLoaded('sections') $sections = $review->relationLoaded('sections')
? $review->sections ? $review->sections
@ -96,7 +96,7 @@ public function blockersForReview(TenantReview $review): array
})->all()); })->all());
} }
public function canPublish(TenantReview $review): bool public function canPublish(EnvironmentReview $review): bool
{ {
if (! $review->isMutable()) { if (! $review->isMutable()) {
return false; return false;
@ -105,11 +105,11 @@ public function canPublish(TenantReview $review): bool
return $this->blockersForReview($review) === []; return $this->blockersForReview($review) === [];
} }
public function canExport(TenantReview $review): bool public function canExport(EnvironmentReview $review): bool
{ {
if (! in_array($review->statusEnum(), [ if (! in_array($review->statusEnum(), [
TenantReviewStatus::Ready, EnvironmentReviewStatus::Ready,
TenantReviewStatus::Published, EnvironmentReviewStatus::Published,
], true)) { ], true)) {
return false; return false;
} }
@ -124,14 +124,14 @@ public function canExport(TenantReview $review): bool
public function sectionStateCounts(iterable $sections): array public function sectionStateCounts(iterable $sections): array
{ {
$counts = collect($sections) $counts = collect($sections)
->groupBy(static fn (array $section): string => (string) ($section['completeness_state'] ?? TenantReviewCompletenessState::Missing->value)) ->groupBy(static fn (array $section): string => (string) ($section['completeness_state'] ?? EnvironmentReviewCompletenessState::Missing->value))
->map(static fn (Collection $group): int => $group->count()); ->map(static fn (Collection $group): int => $group->count());
return [ return [
'complete' => (int) ($counts[TenantReviewCompletenessState::Complete->value] ?? 0), 'complete' => (int) ($counts[EnvironmentReviewCompletenessState::Complete->value] ?? 0),
'partial' => (int) ($counts[TenantReviewCompletenessState::Partial->value] ?? 0), 'partial' => (int) ($counts[EnvironmentReviewCompletenessState::Partial->value] ?? 0),
'missing' => (int) ($counts[TenantReviewCompletenessState::Missing->value] ?? 0), 'missing' => (int) ($counts[EnvironmentReviewCompletenessState::Missing->value] ?? 0),
'stale' => (int) ($counts[TenantReviewCompletenessState::Stale->value] ?? 0), 'stale' => (int) ($counts[EnvironmentReviewCompletenessState::Stale->value] ?? 0),
]; ];
} }
} }

View File

@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Models\WorkspaceMembership; use App\Models\WorkspaceMembership;
@ -15,7 +15,7 @@
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
final class TenantReviewRegisterService final class EnvironmentReviewRegisterService
{ {
public function __construct( public function __construct(
private readonly CapabilityResolver $capabilityResolver, private readonly CapabilityResolver $capabilityResolver,
@ -44,7 +44,7 @@ public function authorizedTenants(User $user, Workspace $workspace): array
$this->capabilityResolver->primeMemberships($user, $tenants->modelKeys()); $this->capabilityResolver->primeMemberships($user, $tenants->modelKeys());
return $tenants return $tenants
->filter(fn (ManagedEnvironment $tenant): bool => $this->capabilityResolver->can($user, $tenant, Capabilities::TENANT_REVIEW_VIEW)) ->filter(fn (ManagedEnvironment $tenant): bool => $this->capabilityResolver->can($user, $tenant, Capabilities::ENVIRONMENT_REVIEW_VIEW))
->keyBy(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey()) ->keyBy(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey())
->all(); ->all();
} }
@ -53,7 +53,7 @@ public function query(User $user, Workspace $workspace): Builder
{ {
$tenantIds = array_keys($this->authorizedTenants($user, $workspace)); $tenantIds = array_keys($this->authorizedTenants($user, $workspace));
return TenantReview::query() return EnvironmentReview::query()
->with(['tenant', 'evidenceSnapshot', 'currentExportReviewPack']) ->with(['tenant', 'evidenceSnapshot', 'currentExportReviewPack'])
->forWorkspace((int) $workspace->getKey()) ->forWorkspace((int) $workspace->getKey())
->whereIn('managed_environment_id', $tenantIds === [] ? [-1] : $tenantIds) ->whereIn('managed_environment_id', $tenantIds === [] ? [-1] : $tenantIds)
@ -65,12 +65,12 @@ public function latestPublishedQuery(User $user, Workspace $workspace): Builder
{ {
$tenantIds = array_keys($this->authorizedTenants($user, $workspace)); $tenantIds = array_keys($this->authorizedTenants($user, $workspace));
$rankedReviews = TenantReview::query() $rankedReviews = EnvironmentReview::query()
->select([ ->select([
'tenant_reviews.id', 'environment_reviews.id',
'tenant_reviews.managed_environment_id', 'environment_reviews.managed_environment_id',
'tenant_reviews.published_at', 'environment_reviews.published_at',
'tenant_reviews.generated_at', 'environment_reviews.generated_at',
]) ])
->selectRaw('ROW_NUMBER() OVER (PARTITION BY managed_environment_id ORDER BY published_at DESC, generated_at DESC, id DESC) as rn') ->selectRaw('ROW_NUMBER() OVER (PARTITION BY managed_environment_id ORDER BY published_at DESC, generated_at DESC, id DESC) as rn')
->forWorkspace((int) $workspace->getKey()) ->forWorkspace((int) $workspace->getKey())
@ -78,14 +78,14 @@ public function latestPublishedQuery(User $user, Workspace $workspace): Builder
->published(); ->published();
$latestPublishedIds = DB::query() $latestPublishedIds = DB::query()
->fromSub($rankedReviews, 'ranked_tenant_reviews') ->fromSub($rankedReviews, 'ranked_environment_reviews')
->where('rn', 1) ->where('rn', 1)
->select('id'); ->select('id');
return TenantReview::query() return EnvironmentReview::query()
->with(['tenant', 'evidenceSnapshot', 'currentExportReviewPack']) ->with(['tenant', 'evidenceSnapshot', 'currentExportReviewPack'])
->forWorkspace((int) $workspace->getKey()) ->forWorkspace((int) $workspace->getKey())
->whereIn('tenant_reviews.id', $latestPublishedIds) ->whereIn('environment_reviews.id', $latestPublishedIds)
->orderByDesc('published_at') ->orderByDesc('published_at')
->orderByDesc('generated_at') ->orderByDesc('generated_at')
->orderByDesc('id'); ->orderByDesc('id');
@ -98,9 +98,9 @@ public function customerWorkspaceTenantQuery(User $user, Workspace $workspace):
return ManagedEnvironment::query() return ManagedEnvironment::query()
->where('workspace_id', (int) $workspace->getKey()) ->where('workspace_id', (int) $workspace->getKey())
->whereIn('id', $tenantIds === [] ? [-1] : $tenantIds) ->whereIn('id', $tenantIds === [] ? [-1] : $tenantIds)
->whereHas('tenantReviews', fn ($query) => $query->published()) ->whereHas('environmentReviews', fn ($query) => $query->published())
->with([ ->with([
'tenantReviews' => fn ($query) => $query 'environmentReviews' => fn ($query) => $query
->with(['tenant', 'evidenceSnapshot', 'currentExportReviewPack']) ->with(['tenant', 'evidenceSnapshot', 'currentExportReviewPack'])
->published() ->published()
->orderByDesc('published_at') ->orderByDesc('published_at')

View File

@ -2,17 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\EvidenceSnapshotItem; use App\Models\EvidenceSnapshotItem;
use App\Support\Findings\FindingOutcomeSemantics; use App\Support\Findings\FindingOutcomeSemantics;
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1; use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
use App\Support\TenantReviewCompletenessState; use App\Support\EnvironmentReviewCompletenessState;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
final class TenantReviewSectionFactory final class EnvironmentReviewSectionFactory
{ {
public function __construct( public function __construct(
private readonly FindingOutcomeSemantics $findingOutcomeSemantics, private readonly FindingOutcomeSemantics $findingOutcomeSemantics,
@ -333,10 +333,10 @@ private function summary(?EvidenceSnapshotItem $item): array
return is_array($item?->summary_payload) ? $item->summary_payload : []; return is_array($item?->summary_payload) ? $item->summary_payload : [];
} }
private function state(?EvidenceSnapshotItem $item): TenantReviewCompletenessState private function state(?EvidenceSnapshotItem $item): EnvironmentReviewCompletenessState
{ {
return TenantReviewCompletenessState::tryFrom((string) $item?->state) return EnvironmentReviewCompletenessState::tryFrom((string) $item?->state)
?? TenantReviewCompletenessState::Missing; ?? EnvironmentReviewCompletenessState::Missing;
} }
private function sourceFingerprint(?EvidenceSnapshotItem $item): ?string private function sourceFingerprint(?EvidenceSnapshotItem $item): ?string
@ -361,23 +361,23 @@ private function canonicalControlsFromEntries(array $entries): array
} }
/** /**
* @param array<int, TenantReviewCompletenessState> $states * @param array<int, EnvironmentReviewCompletenessState> $states
*/ */
private function maxState(array $states): TenantReviewCompletenessState private function maxState(array $states): EnvironmentReviewCompletenessState
{ {
if (in_array(TenantReviewCompletenessState::Missing, $states, true)) { if (in_array(EnvironmentReviewCompletenessState::Missing, $states, true)) {
return TenantReviewCompletenessState::Missing; return EnvironmentReviewCompletenessState::Missing;
} }
if (in_array(TenantReviewCompletenessState::Stale, $states, true)) { if (in_array(EnvironmentReviewCompletenessState::Stale, $states, true)) {
return TenantReviewCompletenessState::Stale; return EnvironmentReviewCompletenessState::Stale;
} }
if (in_array(TenantReviewCompletenessState::Partial, $states, true)) { if (in_array(EnvironmentReviewCompletenessState::Partial, $states, true)) {
return TenantReviewCompletenessState::Partial; return EnvironmentReviewCompletenessState::Partial;
} }
return TenantReviewCompletenessState::Complete; return EnvironmentReviewCompletenessState::Complete;
} }
/** /**

View File

@ -2,43 +2,43 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Services\TenantReviews; namespace App\Services\EnvironmentReviews;
use App\Jobs\ComposeTenantReviewJob; use App\Jobs\ComposeEnvironmentReviewJob;
use App\Models\EvidenceSnapshot; use App\Models\EvidenceSnapshot;
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\OperationRunService; use App\Services\OperationRunService;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\OperationRunType; use App\Support\OperationRunType;
use App\Support\TenantReviewStatus; use App\Support\EnvironmentReviewStatus;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use InvalidArgumentException; use InvalidArgumentException;
final class TenantReviewService final class EnvironmentReviewService
{ {
public function __construct( public function __construct(
private readonly OperationRunService $operationRuns, private readonly OperationRunService $operationRuns,
private readonly WorkspaceAuditLogger $auditLogger, private readonly WorkspaceAuditLogger $auditLogger,
private readonly TenantReviewComposer $composer, private readonly EnvironmentReviewComposer $composer,
private readonly TenantReviewFingerprint $fingerprint, private readonly EnvironmentReviewFingerprint $fingerprint,
) {} ) {}
public function create(ManagedEnvironment $tenant, EvidenceSnapshot $snapshot, User $user): TenantReview public function create(ManagedEnvironment $tenant, EvidenceSnapshot $snapshot, User $user): EnvironmentReview
{ {
return $this->queueComposition( return $this->queueComposition(
tenant: $tenant, tenant: $tenant,
snapshot: $snapshot, snapshot: $snapshot,
user: $user, user: $user,
existingReview: null, existingReview: null,
auditAction: AuditActionId::TenantReviewCreated, auditAction: AuditActionId::EnvironmentReviewCreated,
); );
} }
public function refresh(TenantReview $review, User $user, ?EvidenceSnapshot $snapshot = null): TenantReview public function refresh(EnvironmentReview $review, User $user, ?EvidenceSnapshot $snapshot = null): EnvironmentReview
{ {
$tenant = $review->tenant; $tenant = $review->tenant;
@ -53,11 +53,11 @@ public function refresh(TenantReview $review, User $user, ?EvidenceSnapshot $sna
snapshot: $snapshot, snapshot: $snapshot,
user: $user, user: $user,
existingReview: $review, existingReview: $review,
auditAction: AuditActionId::TenantReviewRefreshed, auditAction: AuditActionId::EnvironmentReviewRefreshed,
); );
} }
public function compose(TenantReview $review): TenantReview public function compose(EnvironmentReview $review): EnvironmentReview
{ {
$review->loadMissing(['tenant', 'evidenceSnapshot.items']); $review->loadMissing(['tenant', 'evidenceSnapshot.items']);
@ -121,7 +121,7 @@ public function activeCompositionRun(ManagedEnvironment $tenant, ?EvidenceSnapsh
return $this->operationRuns->findCanonicalRunWithIdentity( return $this->operationRuns->findCanonicalRunWithIdentity(
tenant: $tenant, tenant: $tenant,
type: OperationRunType::TenantReviewCompose->value, type: OperationRunType::EnvironmentReviewCompose->value,
identityInputs: [ identityInputs: [
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $tenant->getKey(),
'snapshot_id' => (int) $snapshot->getKey(), 'snapshot_id' => (int) $snapshot->getKey(),
@ -134,9 +134,9 @@ private function queueComposition(
ManagedEnvironment $tenant, ManagedEnvironment $tenant,
?EvidenceSnapshot $snapshot, ?EvidenceSnapshot $snapshot,
User $user, User $user,
?TenantReview $existingReview, ?EnvironmentReview $existingReview,
AuditActionId $auditAction, AuditActionId $auditAction,
): TenantReview { ): EnvironmentReview {
if (! $snapshot instanceof EvidenceSnapshot) { if (! $snapshot instanceof EvidenceSnapshot) {
throw new InvalidArgumentException('An eligible evidence snapshot is required.'); throw new InvalidArgumentException('An eligible evidence snapshot is required.');
} }
@ -148,17 +148,17 @@ private function queueComposition(
$fingerprint = $this->fingerprint->forSnapshot($tenant, $snapshot); $fingerprint = $this->fingerprint->forSnapshot($tenant, $snapshot);
$review = $existingReview; $review = $existingReview;
if (! $review instanceof TenantReview) { if (! $review instanceof EnvironmentReview) {
$existing = $this->findExistingMutableReview($tenant, $fingerprint); $existing = $this->findExistingMutableReview($tenant, $fingerprint);
if ($existing instanceof TenantReview) { if ($existing instanceof EnvironmentReview) {
return $existing->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']); return $existing->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']);
} }
} }
$operationRun = $this->operationRuns->ensureRunWithIdentity( $operationRun = $this->operationRuns->ensureRunWithIdentity(
tenant: $tenant, tenant: $tenant,
type: OperationRunType::TenantReviewCompose->value, type: OperationRunType::EnvironmentReviewCompose->value,
identityInputs: [ identityInputs: [
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $tenant->getKey(),
'snapshot_id' => (int) $snapshot->getKey(), 'snapshot_id' => (int) $snapshot->getKey(),
@ -177,14 +177,14 @@ private function queueComposition(
initiator: $user, initiator: $user,
); );
$review ??= TenantReview::query()->create([ $review ??= EnvironmentReview::query()->create([
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id, 'workspace_id' => (int) $tenant->workspace_id,
'evidence_snapshot_id' => (int) $snapshot->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(),
'operation_run_id' => (int) $operationRun->getKey(), 'operation_run_id' => (int) $operationRun->getKey(),
'initiated_by_user_id' => (int) $user->getKey(), 'initiated_by_user_id' => (int) $user->getKey(),
'fingerprint' => $fingerprint, 'fingerprint' => $fingerprint,
'status' => TenantReviewStatus::Draft->value, 'status' => EnvironmentReviewStatus::Draft->value,
'completeness_state' => (string) $snapshot->completeness_state, 'completeness_state' => (string) $snapshot->completeness_state,
'summary' => [ 'summary' => [
'evidence_basis' => [ 'evidence_basis' => [
@ -199,12 +199,12 @@ private function queueComposition(
], ],
]); ]);
if ($existingReview instanceof TenantReview) { if ($existingReview instanceof EnvironmentReview) {
$existingReview->forceFill([ $existingReview->forceFill([
'evidence_snapshot_id' => (int) $snapshot->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(),
'operation_run_id' => (int) $operationRun->getKey(), 'operation_run_id' => (int) $operationRun->getKey(),
'fingerprint' => $fingerprint, 'fingerprint' => $fingerprint,
'status' => TenantReviewStatus::Draft->value, 'status' => EnvironmentReviewStatus::Draft->value,
])->save(); ])->save();
$review = $existingReview->refresh(); $review = $existingReview->refresh();
@ -212,8 +212,8 @@ private function queueComposition(
if ($operationRun->wasRecentlyCreated) { if ($operationRun->wasRecentlyCreated) {
$this->operationRuns->dispatchOrFail($operationRun, function () use ($review, $operationRun): void { $this->operationRuns->dispatchOrFail($operationRun, function () use ($review, $operationRun): void {
ComposeTenantReviewJob::dispatch( ComposeEnvironmentReviewJob::dispatch(
tenantReviewId: (int) $review->getKey(), environmentReviewId: (int) $review->getKey(),
operationRunId: (int) $operationRun->getKey(), operationRunId: (int) $operationRun->getKey(),
); );
}); });
@ -231,7 +231,7 @@ private function queueComposition(
], ],
], ],
actor: $user, actor: $user,
resourceType: 'tenant_review', resourceType: 'environment_review',
resourceId: (string) $review->getKey(), resourceId: (string) $review->getKey(),
targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()), targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()),
operationRunId: (int) $operationRun->getKey(), operationRunId: (int) $operationRun->getKey(),
@ -241,9 +241,9 @@ private function queueComposition(
return $review->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']); return $review->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']);
} }
private function findExistingMutableReview(ManagedEnvironment $tenant, string $fingerprint): ?TenantReview private function findExistingMutableReview(ManagedEnvironment $tenant, string $fingerprint): ?EnvironmentReview
{ {
return TenantReview::query() return EnvironmentReview::query()
->forTenant((int) $tenant->getKey()) ->forTenant((int) $tenant->getKey())
->mutable() ->mutable()
->where('fingerprint', $fingerprint) ->where('fingerprint', $fingerprint)

View File

@ -3,13 +3,13 @@
namespace App\Services\Intune; namespace App\Services\Intune;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantPermission; use App\Models\ManagedEnvironmentPermission;
use App\Services\Graph\GraphClientInterface; use App\Services\Graph\GraphClientInterface;
use App\Services\Providers\MicrosoftGraphOptionsResolver; use App\Services\Providers\MicrosoftGraphOptionsResolver;
use DateTimeInterface; use DateTimeInterface;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
class TenantPermissionService class ManagedEnvironmentPermissionService
{ {
public function __construct( public function __construct(
private readonly GraphClientInterface $graphClient, private readonly GraphClientInterface $graphClient,
@ -32,11 +32,11 @@ public function getRequiredPermissions(): array
*/ */
public function getGrantedPermissions(ManagedEnvironment $tenant): array public function getGrantedPermissions(ManagedEnvironment $tenant): array
{ {
return TenantPermission::query() return ManagedEnvironmentPermission::query()
->where('managed_environment_id', $tenant->id) ->where('managed_environment_id', $tenant->id)
->get() ->get()
->keyBy('permission_key') ->keyBy('permission_key')
->map(fn (TenantPermission $permission) => [ ->map(fn (ManagedEnvironmentPermission $permission) => [
'status' => $permission->status, 'status' => $permission->status,
'details' => $permission->details, 'details' => $permission->details,
'last_checked_at' => $permission->last_checked_at, 'last_checked_at' => $permission->last_checked_at,
@ -46,7 +46,7 @@ public function getGrantedPermissions(ManagedEnvironment $tenant): array
/** /**
* @param array<string, array{status:string,details?:array<string,mixed>|null}|string>|null $grantedStatuses * @param array<string, array{status:string,details?:array<string,mixed>|null}|string>|null $grantedStatuses
* @param bool $persist Persist comparison results to tenant_permissions * @param bool $persist Persist comparison results to managed_environment_permissions
* @param bool $liveCheck If true, fetch actual permissions from Graph API * @param bool $liveCheck If true, fetch actual permissions from Graph API
* @param bool $useConfiguredStub Include configured stub permissions when no live check is used * @param bool $useConfiguredStub Include configured stub permissions when no live check is used
* @param array{tenant?:string|null,client_id?:string|null,client_secret?:string|null,client_request_id?:string|null}|null $graphOptions * @param array{tenant?:string|null,client_id?:string|null,client_secret?:string|null,client_request_id?:string|null}|null $graphOptions
@ -165,7 +165,7 @@ public function compare(
if (! $shouldPersistErrorSnapshot) { if (! $shouldPersistErrorSnapshot) {
$canPersist = false; $canPersist = false;
} else { } else {
$hasStoredStatuses = TenantPermission::query() $hasStoredStatuses = ManagedEnvironmentPermission::query()
->where('managed_environment_id', $tenant->id) ->where('managed_environment_id', $tenant->id)
->exists(); ->exists();
@ -189,7 +189,7 @@ public function compare(
: ($granted[$key]['details'] ?? null); : ($granted[$key]['details'] ?? null);
if ($canPersist) { if ($canPersist) {
TenantPermission::updateOrCreate( ManagedEnvironmentPermission::updateOrCreate(
[ [
'managed_environment_id' => $tenant->id, 'managed_environment_id' => $tenant->id,
'permission_key' => $key, 'permission_key' => $key,
@ -433,7 +433,7 @@ private function fetchLivePermissions(ManagedEnvironment $tenant, ?array $graphO
private function lastRefreshedAtIso(ManagedEnvironment $tenant): ?string private function lastRefreshedAtIso(ManagedEnvironment $tenant): ?string
{ {
$lastCheckedAt = TenantPermission::query() $lastCheckedAt = ManagedEnvironmentPermission::query()
->where('managed_environment_id', (int) $tenant->getKey()) ->where('managed_environment_id', (int) $tenant->getKey())
->max('last_checked_at'); ->max('last_checked_at');

View File

@ -6,15 +6,15 @@
use App\Support\Providers\Capabilities\ProviderCapabilityDefinition; use App\Support\Providers\Capabilities\ProviderCapabilityDefinition;
use App\Support\Providers\Capabilities\ProviderCapabilityRegistry; use App\Support\Providers\Capabilities\ProviderCapabilityRegistry;
use App\Support\Providers\Capabilities\ProviderCapabilityStatus; use App\Support\Providers\Capabilities\ProviderCapabilityStatus;
use App\Support\Verification\TenantPermissionCheckClusters; use App\Support\Verification\ManagedEnvironmentPermissionCheckClusters;
use App\Support\Verification\VerificationReportOverall; use App\Support\Verification\VerificationReportOverall;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
class TenantRequiredPermissionsViewModelBuilder class ManagedEnvironmentRequiredPermissionsViewModelBuilder
{ {
/** /**
* @phpstan-type TenantPermissionRow array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null} * @phpstan-type ManagedEnvironmentPermissionRow array{key:string,type:'application'|'delegated',description:?string,features:array<int,string>,status:'granted'|'missing'|'error',details:array<string,mixed>|null}
* @phpstan-type FeatureImpact array{feature:string,missing:int,required_application:int,required_delegated:int,blocked:bool} * @phpstan-type FeatureImpact array{feature:string,missing:int,required_application:int,required_delegated:int,blocked:bool}
* @phpstan-type CapabilityGroup array{provider_capability_key:string,label:string,status:string,provider_requirement_keys:array<int,string>,missing_requirement_keys:array<int,string>,evidence_counts:array{requirements:int,missing:int,errors:int},message:string} * @phpstan-type CapabilityGroup array{provider_capability_key:string,label:string,status:string,provider_requirement_keys:array<int,string>,missing_requirement_keys:array<int,string>,evidence_counts:array{requirements:int,missing:int,errors:int},message:string}
* @phpstan-type FilterState array{status:'missing'|'present'|'all',type:'application'|'delegated'|'all',features:array<int,string>,search:string} * @phpstan-type FilterState array{status:'missing'|'present'|'all',type:'application'|'delegated'|'all',features:array<int,string>,search:string}
@ -28,12 +28,12 @@ class TenantRequiredPermissionsViewModelBuilder
* primary_capability_group: CapabilityGroup|null, * primary_capability_group: CapabilityGroup|null,
* freshness: array{last_refreshed_at:?string,is_stale:bool} * freshness: array{last_refreshed_at:?string,is_stale:bool}
* }, * },
* permissions: array<int, TenantPermissionRow>, * permissions: array<int, ManagedEnvironmentPermissionRow>,
* filters: FilterState, * filters: FilterState,
* copy: array{application:string,delegated:string} * copy: array{application:string,delegated:string}
* } * }
*/ */
public function __construct(private readonly TenantPermissionService $permissionService) {} public function __construct(private readonly ManagedEnvironmentPermissionService $permissionService) {}
/** /**
* @param array<string, mixed> $filters * @param array<string, mixed> $filters
@ -48,7 +48,7 @@ public function build(ManagedEnvironment $tenant, array $filters = []): array
useConfiguredStub: false, useConfiguredStub: false,
); );
/** @var array<int, TenantPermissionRow> $allPermissions */ /** @var array<int, ManagedEnvironmentPermissionRow> $allPermissions */
$allPermissions = collect($comparison['permissions'] ?? []) $allPermissions = collect($comparison['permissions'] ?? [])
->filter(fn (mixed $row): bool => is_array($row)) ->filter(fn (mixed $row): bool => is_array($row))
->map(fn (array $row): array => self::normalizePermissionRow($row)) ->map(fn (array $row): array => self::normalizePermissionRow($row))
@ -87,7 +87,7 @@ public function build(ManagedEnvironment $tenant, array $filters = []): array
} }
/** /**
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
* @param array{last_refreshed_at:?string,is_stale:bool} $freshness * @param array{last_refreshed_at:?string,is_stale:bool} $freshness
* @return array<int, CapabilityGroup> * @return array<int, CapabilityGroup>
*/ */
@ -127,7 +127,7 @@ public static function primaryCapabilityGroup(array $groups): ?array
} }
/** /**
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
* @return CapabilityGroup * @return CapabilityGroup
*/ */
private static function deriveCapabilityGroup( private static function deriveCapabilityGroup(
@ -138,7 +138,7 @@ private static function deriveCapabilityGroup(
$rowsByRequirement = []; $rowsByRequirement = [];
foreach ($definition->providerRequirementKeys as $requirementKey) { foreach ($definition->providerRequirementKeys as $requirementKey) {
$rowsByRequirement[$requirementKey] = TenantPermissionCheckClusters::rowsForRequirementKey($permissions, $requirementKey); $rowsByRequirement[$requirementKey] = ManagedEnvironmentPermissionCheckClusters::rowsForRequirementKey($permissions, $requirementKey);
} }
$rows = array_values(array_merge(...array_values($rowsByRequirement ?: [[]]))); $rows = array_values(array_merge(...array_values($rowsByRequirement ?: [[]])));
@ -193,7 +193,7 @@ private static function deriveCapabilityGroup(
} }
/** /**
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
*/ */
public static function deriveOverallStatus(array $permissions, bool $hasStaleFreshness = false): string public static function deriveOverallStatus(array $permissions, bool $hasStaleFreshness = false): string
{ {
@ -243,7 +243,7 @@ public static function deriveFreshness(?CarbonInterface $lastRefreshedAt, ?Carbo
} }
/** /**
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
* @return array{missing_application:int,missing_delegated:int,present:int,error:int} * @return array{missing_application:int,missing_delegated:int,present:int,error:int}
*/ */
public static function deriveCounts(array $permissions): array public static function deriveCounts(array $permissions): array
@ -281,7 +281,7 @@ public static function deriveCounts(array $permissions): array
} }
/** /**
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
* @return array<int, FeatureImpact> * @return array<int, FeatureImpact>
*/ */
public static function deriveFeatureImpacts(array $permissions): array public static function deriveFeatureImpacts(array $permissions): array
@ -345,7 +345,7 @@ public static function deriveFeatureImpacts(array $permissions): array
* - Respects Feature filter only * - Respects Feature filter only
* - Ignores Search * - Ignores Search
* *
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
* @param 'application'|'delegated' $type * @param 'application'|'delegated' $type
* @param array<int, string> $featureFilter * @param array<int, string> $featureFilter
*/ */
@ -383,8 +383,8 @@ public static function deriveCopyPayload(array $permissions, string $type, array
} }
/** /**
* @param array<int, TenantPermissionRow> $permissions * @param array<int, ManagedEnvironmentPermissionRow> $permissions
* @return array<int, TenantPermissionRow> * @return array<int, ManagedEnvironmentPermissionRow>
*/ */
public static function applyFilterState(array $permissions, array $state): array public static function applyFilterState(array $permissions, array $state): array
{ {
@ -489,7 +489,7 @@ public static function normalizeFilterState(array $filters): array
/** /**
* @param array<string, mixed> $row * @param array<string, mixed> $row
* @return TenantPermissionRow * @return ManagedEnvironmentPermissionRow
*/ */
private static function normalizePermissionRow(array $row): array private static function normalizePermissionRow(array $row): array
{ {

View File

@ -6,7 +6,7 @@
use App\Exceptions\Onboarding\OnboardingDraftConflictException; use App\Exceptions\Onboarding\OnboardingDraftConflictException;
use App\Exceptions\Onboarding\OnboardingDraftImmutableException; use App\Exceptions\Onboarding\OnboardingDraftImmutableException;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -19,25 +19,25 @@ public function __construct(
) {} ) {}
/** /**
* @param callable(TenantOnboardingSession):void $mutator * @param callable(ManagedEnvironmentOnboardingSession):void $mutator
*/ */
public function createOrResume( public function createOrResume(
Workspace $workspace, Workspace $workspace,
User $actor, User $actor,
string $entraTenantId, string $entraTenantId,
callable $mutator, callable $mutator,
?TenantOnboardingSession $preferredDraft = null, ?ManagedEnvironmentOnboardingSession $preferredDraft = null,
?int $expectedVersion = null, ?int $expectedVersion = null,
bool $incrementVersion = true, bool $incrementVersion = true,
?bool &$wasCreated = null, ?bool &$wasCreated = null,
): TenantOnboardingSession { ): ManagedEnvironmentOnboardingSession {
return DB::transaction(function () use ($workspace, $actor, $entraTenantId, $mutator, $preferredDraft, $expectedVersion, $incrementVersion, &$wasCreated): TenantOnboardingSession { return DB::transaction(function () use ($workspace, $actor, $entraTenantId, $mutator, $preferredDraft, $expectedVersion, $incrementVersion, &$wasCreated): ManagedEnvironmentOnboardingSession {
$draft = $this->resolveDraftForIdentity($workspace, $entraTenantId, $preferredDraft); $draft = $this->resolveDraftForIdentity($workspace, $entraTenantId, $preferredDraft);
$isNew = ! $draft instanceof TenantOnboardingSession; $isNew = ! $draft instanceof ManagedEnvironmentOnboardingSession;
$wasCreated = $isNew; $wasCreated = $isNew;
if ($isNew) { if ($isNew) {
$draft = new TenantOnboardingSession; $draft = new ManagedEnvironmentOnboardingSession;
$draft->workspace_id = (int) $workspace->getKey(); $draft->workspace_id = (int) $workspace->getKey();
$draft->entra_tenant_id = $entraTenantId; $draft->entra_tenant_id = $entraTenantId;
$draft->started_by_user_id = (int) $actor->getKey(); $draft->started_by_user_id = (int) $actor->getKey();
@ -61,18 +61,18 @@ public function createOrResume(
} }
/** /**
* @param callable(TenantOnboardingSession):void $mutator * @param callable(ManagedEnvironmentOnboardingSession):void $mutator
*/ */
public function mutate( public function mutate(
TenantOnboardingSession $draft, ManagedEnvironmentOnboardingSession $draft,
User $actor, User $actor,
callable $mutator, callable $mutator,
?int $expectedVersion = null, ?int $expectedVersion = null,
bool $incrementVersion = true, bool $incrementVersion = true,
bool $allowTerminal = false, bool $allowTerminal = false,
): TenantOnboardingSession { ): ManagedEnvironmentOnboardingSession {
return DB::transaction(function () use ($draft, $actor, $mutator, $expectedVersion, $incrementVersion, $allowTerminal): TenantOnboardingSession { return DB::transaction(function () use ($draft, $actor, $mutator, $expectedVersion, $incrementVersion, $allowTerminal): ManagedEnvironmentOnboardingSession {
$lockedDraft = TenantOnboardingSession::query() $lockedDraft = ManagedEnvironmentOnboardingSession::query()
->whereKey($draft->getKey()) ->whereKey($draft->getKey())
->lockForUpdate() ->lockForUpdate()
->firstOrFail(); ->firstOrFail();
@ -101,19 +101,19 @@ public function mutate(
}); });
} }
public function lockForTrustedMutation(TenantOnboardingSession|int|string $draft, Workspace $workspace): TenantOnboardingSession public function lockForTrustedMutation(ManagedEnvironmentOnboardingSession|int|string $draft, Workspace $workspace): ManagedEnvironmentOnboardingSession
{ {
$draftId = $draft instanceof TenantOnboardingSession $draftId = $draft instanceof ManagedEnvironmentOnboardingSession
? (int) $draft->getKey() ? (int) $draft->getKey()
: (int) $draft; : (int) $draft;
$lockedDraft = TenantOnboardingSession::query() $lockedDraft = ManagedEnvironmentOnboardingSession::query()
->whereKey($draftId) ->whereKey($draftId)
->where('workspace_id', (int) $workspace->getKey()) ->where('workspace_id', (int) $workspace->getKey())
->lockForUpdate() ->lockForUpdate()
->first(); ->first();
if (! $lockedDraft instanceof TenantOnboardingSession) { if (! $lockedDraft instanceof ManagedEnvironmentOnboardingSession) {
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
@ -123,16 +123,16 @@ public function lockForTrustedMutation(TenantOnboardingSession|int|string $draft
private function resolveDraftForIdentity( private function resolveDraftForIdentity(
Workspace $workspace, Workspace $workspace,
string $entraTenantId, string $entraTenantId,
?TenantOnboardingSession $preferredDraft = null, ?ManagedEnvironmentOnboardingSession $preferredDraft = null,
): ?TenantOnboardingSession { ): ?ManagedEnvironmentOnboardingSession {
if ($preferredDraft instanceof TenantOnboardingSession) { if ($preferredDraft instanceof ManagedEnvironmentOnboardingSession) {
$lockedPreferredDraft = TenantOnboardingSession::query() $lockedPreferredDraft = ManagedEnvironmentOnboardingSession::query()
->whereKey($preferredDraft->getKey()) ->whereKey($preferredDraft->getKey())
->where('workspace_id', (int) $workspace->getKey()) ->where('workspace_id', (int) $workspace->getKey())
->lockForUpdate() ->lockForUpdate()
->first(); ->first();
if ($lockedPreferredDraft instanceof TenantOnboardingSession && $lockedPreferredDraft->entra_tenant_id === $entraTenantId) { if ($lockedPreferredDraft instanceof ManagedEnvironmentOnboardingSession && $lockedPreferredDraft->entra_tenant_id === $entraTenantId) {
if ($lockedPreferredDraft->lifecycleState()->isTerminal()) { if ($lockedPreferredDraft->lifecycleState()->isTerminal()) {
throw new OnboardingDraftImmutableException( throw new OnboardingDraftImmutableException(
draftId: (int) $lockedPreferredDraft->getKey(), draftId: (int) $lockedPreferredDraft->getKey(),
@ -144,7 +144,7 @@ private function resolveDraftForIdentity(
} }
} }
return TenantOnboardingSession::query() return ManagedEnvironmentOnboardingSession::query()
->where('workspace_id', (int) $workspace->getKey()) ->where('workspace_id', (int) $workspace->getKey())
->where('entra_tenant_id', $entraTenantId) ->where('entra_tenant_id', $entraTenantId)
->resumable() ->resumable()
@ -153,7 +153,7 @@ private function resolveDraftForIdentity(
->first(); ->first();
} }
private function assertExpectedVersion(TenantOnboardingSession $draft, int $expectedVersion): void private function assertExpectedVersion(ManagedEnvironmentOnboardingSession $draft, int $expectedVersion): void
{ {
$actualVersion = max(1, (int) ($draft->version ?? 1)); $actualVersion = max(1, (int) ($draft->version ?? 1));
@ -168,7 +168,7 @@ private function assertExpectedVersion(TenantOnboardingSession $draft, int $expe
); );
} }
private function persistDraft(TenantOnboardingSession $draft, bool $incrementVersion): void private function persistDraft(ManagedEnvironmentOnboardingSession $draft, bool $incrementVersion): void
{ {
$currentVersion = max(0, (int) ($draft->version ?? 0)); $currentVersion = max(0, (int) ($draft->version ?? 0));

View File

@ -4,7 +4,7 @@
namespace App\Services\Onboarding; namespace App\Services\Onboarding;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\User; use App\Models\User;
use App\Models\Workspace; use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
@ -25,18 +25,18 @@ public function __construct(
* @throws AuthorizationException * @throws AuthorizationException
* @throws NotFoundHttpException * @throws NotFoundHttpException
*/ */
public function resolve(TenantOnboardingSession|int|string $draft, User $user, Workspace $workspace): TenantOnboardingSession public function resolve(ManagedEnvironmentOnboardingSession|int|string $draft, User $user, Workspace $workspace): ManagedEnvironmentOnboardingSession
{ {
$draftId = $draft instanceof TenantOnboardingSession $draftId = $draft instanceof ManagedEnvironmentOnboardingSession
? (int) $draft->getKey() ? (int) $draft->getKey()
: (int) $draft; : (int) $draft;
$resolvedDraft = TenantOnboardingSession::query() $resolvedDraft = ManagedEnvironmentOnboardingSession::query()
->with(['tenant', 'startedByUser', 'updatedByUser']) ->with(['managedEnvironment', 'startedByUser', 'updatedByUser'])
->whereKey($draftId) ->whereKey($draftId)
->first(); ->first();
if (! $resolvedDraft instanceof TenantOnboardingSession) { if (! $resolvedDraft instanceof ManagedEnvironmentOnboardingSession) {
throw new NotFoundHttpException; throw new NotFoundHttpException;
} }
@ -48,7 +48,7 @@ public function resolve(TenantOnboardingSession|int|string $draft, User $user, W
$resolvedDraft = $this->lifecycleService $resolvedDraft = $this->lifecycleService
->syncPersistedLifecycle($resolvedDraft) ->syncPersistedLifecycle($resolvedDraft)
->loadMissing(['tenant', 'startedByUser', 'updatedByUser']); ->loadMissing(['managedEnvironment', 'startedByUser', 'updatedByUser']);
$normalizedTenant = $this->lifecycleService->syncLinkedTenantAfterCancellation($resolvedDraft); $normalizedTenant = $this->lifecycleService->syncLinkedTenantAfterCancellation($resolvedDraft);
@ -65,7 +65,7 @@ public function resolve(TenantOnboardingSession|int|string $draft, User $user, W
], ],
); );
$resolvedDraft->setRelation('tenant', $normalizedTenant); $resolvedDraft->setRelation('managedEnvironment', $normalizedTenant);
} }
return $resolvedDraft; return $resolvedDraft;
@ -75,18 +75,18 @@ public function resolve(TenantOnboardingSession|int|string $draft, User $user, W
* @throws AuthorizationException * @throws AuthorizationException
* @throws NotFoundHttpException * @throws NotFoundHttpException
*/ */
public function resolveForTrustedAction(TenantOnboardingSession|int|string $draft, User $user, Workspace $workspace): TenantOnboardingSession public function resolveForTrustedAction(ManagedEnvironmentOnboardingSession|int|string $draft, User $user, Workspace $workspace): ManagedEnvironmentOnboardingSession
{ {
return $this->resolve($draft, $user, $workspace); return $this->resolve($draft, $user, $workspace);
} }
/** /**
* @return Collection<int, TenantOnboardingSession> * @return Collection<int, ManagedEnvironmentOnboardingSession>
*/ */
public function resumableDraftsFor(User $user, Workspace $workspace): Collection public function resumableDraftsFor(User $user, Workspace $workspace): Collection
{ {
$drafts = TenantOnboardingSession::query() $drafts = ManagedEnvironmentOnboardingSession::query()
->with(['tenant', 'startedByUser', 'updatedByUser']) ->with(['managedEnvironment', 'startedByUser', 'updatedByUser'])
->where('workspace_id', (int) $workspace->getKey()) ->where('workspace_id', (int) $workspace->getKey())
->resumable() ->resumable()
->orderByDesc('updated_at') ->orderByDesc('updated_at')
@ -103,7 +103,7 @@ public function resumableDraftsFor(User $user, Workspace $workspace): Collection
$resolvedDraft = $this->lifecycleService $resolvedDraft = $this->lifecycleService
->syncPersistedLifecycle($draft) ->syncPersistedLifecycle($draft)
->loadMissing(['tenant', 'startedByUser', 'updatedByUser']); ->loadMissing(['managedEnvironment', 'startedByUser', 'updatedByUser']);
if (! $this->lifecycleService->canResumeDraft($resolvedDraft)) { if (! $this->lifecycleService->canResumeDraft($resolvedDraft)) {
continue; continue;

View File

@ -4,7 +4,7 @@
namespace App\Services\Onboarding; namespace App\Services\Onboarding;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Support\Onboarding\OnboardingCheckpoint; use App\Support\Onboarding\OnboardingCheckpoint;
use App\Support\Onboarding\OnboardingDraftStage; use App\Support\Onboarding\OnboardingDraftStage;
use App\Support\Onboarding\OnboardingLifecycleState; use App\Support\Onboarding\OnboardingLifecycleState;
@ -15,9 +15,9 @@ public function __construct(
private readonly OnboardingLifecycleService $lifecycleService, private readonly OnboardingLifecycleService $lifecycleService,
) {} ) {}
public function resolve(?TenantOnboardingSession $draft): OnboardingDraftStage public function resolve(?ManagedEnvironmentOnboardingSession $draft): OnboardingDraftStage
{ {
if (! $draft instanceof TenantOnboardingSession) { if (! $draft instanceof ManagedEnvironmentOnboardingSession) {
return OnboardingDraftStage::Identify; return OnboardingDraftStage::Identify;
} }

View File

@ -8,7 +8,7 @@
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\ProviderConnection; use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Services\Tenants\TenantOperabilityService; use App\Services\Tenants\TenantOperabilityService;
use App\Support\Onboarding\OnboardingCheckpoint; use App\Support\Onboarding\OnboardingCheckpoint;
use App\Support\Onboarding\OnboardingLifecycleState; use App\Support\Onboarding\OnboardingLifecycleState;
@ -26,11 +26,11 @@ public function __construct(
private readonly ProductTelemetryRecorder $productTelemetryRecorder, private readonly ProductTelemetryRecorder $productTelemetryRecorder,
) {} ) {}
public function syncPersistedLifecycle(TenantOnboardingSession $draft, bool $incrementVersion = false): TenantOnboardingSession public function syncPersistedLifecycle(ManagedEnvironmentOnboardingSession $draft, bool $incrementVersion = false): ManagedEnvironmentOnboardingSession
{ {
$freshDraft = TenantOnboardingSession::query()->whereKey($draft->getKey())->first(); $freshDraft = ManagedEnvironmentOnboardingSession::query()->whereKey($draft->getKey())->first();
if (! $freshDraft instanceof TenantOnboardingSession) { if (! $freshDraft instanceof ManagedEnvironmentOnboardingSession) {
return $draft; return $draft;
} }
@ -44,7 +44,7 @@ public function syncPersistedLifecycle(TenantOnboardingSession $draft, bool $inc
return $freshDraft->refresh(); return $freshDraft->refresh();
} }
public function applySnapshot(TenantOnboardingSession $draft, bool $incrementVersion = false): bool public function applySnapshot(ManagedEnvironmentOnboardingSession $draft, bool $incrementVersion = false): bool
{ {
$snapshot = $this->snapshot($draft); $snapshot = $this->snapshot($draft);
$lifecycleState = $draft->lifecycle_state instanceof OnboardingLifecycleState $lifecycleState = $draft->lifecycle_state instanceof OnboardingLifecycleState
@ -98,7 +98,7 @@ public function applySnapshot(TenantOnboardingSession $draft, bool $incrementVer
return $changed; return $changed;
} }
public function recordCompletedCheckpointTelemetryIfNeeded(TenantOnboardingSession $draft): void public function recordCompletedCheckpointTelemetryIfNeeded(ManagedEnvironmentOnboardingSession $draft): void
{ {
if (! $draft->wasChanged('last_completed_checkpoint')) { if (! $draft->wasChanged('last_completed_checkpoint')) {
return; return;
@ -127,7 +127,7 @@ public function recordCompletedCheckpointTelemetryIfNeeded(TenantOnboardingSessi
workspaceId: $workspaceId, workspaceId: $workspaceId,
tenantId: $tenantId, tenantId: $tenantId,
userId: $userId, userId: $userId,
subjectType: 'tenant_onboarding_session', subjectType: 'managed_environment_onboarding_session',
subjectId: (int) $draft->getKey(), subjectId: (int) $draft->getKey(),
metadata: [ metadata: [
'checkpoint_key' => $checkpoint->value, 'checkpoint_key' => $checkpoint->value,
@ -147,7 +147,7 @@ public function recordCompletedCheckpointTelemetryIfNeeded(TenantOnboardingSessi
* blocking_reason_code: string|null * blocking_reason_code: string|null
* } * }
*/ */
public function snapshot(TenantOnboardingSession $draft): array public function snapshot(ManagedEnvironmentOnboardingSession $draft): array
{ {
$selectedProviderConnectionId = $this->selectedProviderConnectionId($draft); $selectedProviderConnectionId = $this->selectedProviderConnectionId($draft);
$verificationRun = $this->verificationRun($draft); $verificationRun = $this->verificationRun($draft);
@ -334,7 +334,7 @@ public function snapshot(TenantOnboardingSession $draft): array
]; ];
} }
public function verificationRun(TenantOnboardingSession $draft): ?OperationRun public function verificationRun(ManagedEnvironmentOnboardingSession $draft): ?OperationRun
{ {
$state = is_array($draft->state) ? $draft->state : []; $state = is_array($draft->state) ? $draft->state : [];
$runId = $this->normalizeInteger($state['verification_operation_run_id'] ?? $state['verification_run_id'] ?? null); $runId = $this->normalizeInteger($state['verification_operation_run_id'] ?? $state['verification_run_id'] ?? null);
@ -355,7 +355,7 @@ public function verificationRun(TenantOnboardingSession $draft): ?OperationRun
} }
public function verificationStatus( public function verificationStatus(
TenantOnboardingSession $draft, ManagedEnvironmentOnboardingSession $draft,
?int $selectedProviderConnectionId = null, ?int $selectedProviderConnectionId = null,
?OperationRun $run = null, ?OperationRun $run = null,
): string { ): string {
@ -386,7 +386,7 @@ public function verificationStatus(
} }
public function verificationCanProceed( public function verificationCanProceed(
TenantOnboardingSession $draft, ManagedEnvironmentOnboardingSession $draft,
?int $selectedProviderConnectionId = null, ?int $selectedProviderConnectionId = null,
?OperationRun $run = null, ?OperationRun $run = null,
): bool { ): bool {
@ -408,7 +408,7 @@ public function verificationCanProceed(
return in_array($this->verificationStatus($draft, $selectedProviderConnectionId, $run), ['ready', 'needs_attention'], true); return in_array($this->verificationStatus($draft, $selectedProviderConnectionId, $run), ['ready', 'needs_attention'], true);
} }
public function verificationIsBlocked(TenantOnboardingSession $draft, ?int $selectedProviderConnectionId = null): bool public function verificationIsBlocked(ManagedEnvironmentOnboardingSession $draft, ?int $selectedProviderConnectionId = null): bool
{ {
return $this->verificationStatus($draft, $selectedProviderConnectionId) === 'blocked'; return $this->verificationStatus($draft, $selectedProviderConnectionId) === 'blocked';
} }
@ -425,32 +425,32 @@ public function verificationIsBlocked(TenantOnboardingSession $draft, ?int $sele
* is_completed: bool * is_completed: bool
* }> * }>
*/ */
public function bootstrapRunSummaries(TenantOnboardingSession $draft, ?int $selectedProviderConnectionId = null): array public function bootstrapRunSummaries(ManagedEnvironmentOnboardingSession $draft, ?int $selectedProviderConnectionId = null): array
{ {
$selectedProviderConnectionId ??= $this->selectedProviderConnectionId($draft); $selectedProviderConnectionId ??= $this->selectedProviderConnectionId($draft);
return $this->bootstrapState($draft, $selectedProviderConnectionId)['summaries']; return $this->bootstrapState($draft, $selectedProviderConnectionId)['summaries'];
} }
public function isReadyForActivation(TenantOnboardingSession $draft): bool public function isReadyForActivation(ManagedEnvironmentOnboardingSession $draft): bool
{ {
return $this->snapshot($draft)['lifecycle_state'] === OnboardingLifecycleState::ReadyForActivation; return $this->snapshot($draft)['lifecycle_state'] === OnboardingLifecycleState::ReadyForActivation;
} }
public function hasActiveCheckpoint(TenantOnboardingSession $draft): bool public function hasActiveCheckpoint(ManagedEnvironmentOnboardingSession $draft): bool
{ {
$snapshot = $this->snapshot($draft); $snapshot = $this->snapshot($draft);
return in_array($snapshot['lifecycle_state'], [OnboardingLifecycleState::Verifying, OnboardingLifecycleState::Bootstrapping], true); return in_array($snapshot['lifecycle_state'], [OnboardingLifecycleState::Verifying, OnboardingLifecycleState::Bootstrapping], true);
} }
public function canResumeDraft(TenantOnboardingSession $draft): bool public function canResumeDraft(ManagedEnvironmentOnboardingSession $draft): bool
{ {
if (! $draft->isWorkflowResumable()) { if (! $draft->isWorkflowResumable()) {
return false; return false;
} }
$tenant = $draft->tenant; $tenant = $draft->managedEnvironment;
if (! $tenant instanceof ManagedEnvironment) { if (! $tenant instanceof ManagedEnvironment) {
return true; return true;
@ -459,9 +459,9 @@ public function canResumeDraft(TenantOnboardingSession $draft): bool
return $this->tenantOperabilityService->canResumeOnboarding($tenant); return $this->tenantOperabilityService->canResumeOnboarding($tenant);
} }
public function syncLinkedTenantAfterCancellation(TenantOnboardingSession $draft): ?ManagedEnvironment public function syncLinkedTenantAfterCancellation(ManagedEnvironmentOnboardingSession $draft): ?ManagedEnvironment
{ {
$tenant = $draft->tenant; $tenant = $draft->managedEnvironment;
if (! $tenant instanceof ManagedEnvironment) { if (! $tenant instanceof ManagedEnvironment) {
return null; return null;
@ -471,7 +471,7 @@ public function syncLinkedTenantAfterCancellation(TenantOnboardingSession $draft
return null; return null;
} }
$hasOtherResumableDrafts = TenantOnboardingSession::query() $hasOtherResumableDrafts = ManagedEnvironmentOnboardingSession::query()
->where('workspace_id', (int) $draft->workspace_id) ->where('workspace_id', (int) $draft->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey()) ->where('managed_environment_id', (int) $tenant->getKey())
->whereKeyNot((int) $draft->getKey()) ->whereKeyNot((int) $draft->getKey())
@ -487,7 +487,7 @@ public function syncLinkedTenantAfterCancellation(TenantOnboardingSession $draft
return $tenant->fresh(); return $tenant->fresh();
} }
private function hasTenantIdentity(TenantOnboardingSession $draft): bool private function hasTenantIdentity(ManagedEnvironmentOnboardingSession $draft): bool
{ {
if ($draft->managed_environment_id !== null) { if ($draft->managed_environment_id !== null) {
return true; return true;
@ -499,7 +499,7 @@ private function hasTenantIdentity(TenantOnboardingSession $draft): bool
return is_string($entraTenantId) && trim($entraTenantId) !== ''; return is_string($entraTenantId) && trim($entraTenantId) !== '';
} }
private function selectedProviderConnectionId(TenantOnboardingSession $draft): ?int private function selectedProviderConnectionId(ManagedEnvironmentOnboardingSession $draft): ?int
{ {
$state = is_array($draft->state) ? $draft->state : []; $state = is_array($draft->state) ? $draft->state : [];
@ -518,7 +518,7 @@ private function selectedProviderConnectionId(TenantOnboardingSession $draft): ?
return $exists ? $providerConnectionId : null; return $exists ? $providerConnectionId : null;
} }
private function connectionRecentlyUpdated(TenantOnboardingSession $draft): bool private function connectionRecentlyUpdated(ManagedEnvironmentOnboardingSession $draft): bool
{ {
$state = is_array($draft->state) ? $draft->state : []; $state = is_array($draft->state) ? $draft->state : [];
@ -639,7 +639,7 @@ private function runReasonCodes(OperationRun $run): array
* }> * }>
* } * }
*/ */
private function bootstrapState(TenantOnboardingSession $draft, ?int $selectedProviderConnectionId): array private function bootstrapState(ManagedEnvironmentOnboardingSession $draft, ?int $selectedProviderConnectionId): array
{ {
$selectedTypes = $this->bootstrapOperationTypes($draft); $selectedTypes = $this->bootstrapOperationTypes($draft);
$runMap = $this->bootstrapRunMap($draft, $selectedTypes); $runMap = $this->bootstrapRunMap($draft, $selectedTypes);
@ -717,7 +717,7 @@ private function bootstrapState(TenantOnboardingSession $draft, ?int $selectedPr
/** /**
* @return array<int, string> * @return array<int, string>
*/ */
private function bootstrapOperationTypes(TenantOnboardingSession $draft): array private function bootstrapOperationTypes(ManagedEnvironmentOnboardingSession $draft): array
{ {
$state = is_array($draft->state) ? $draft->state : []; $state = is_array($draft->state) ? $draft->state : [];
$types = $state['bootstrap_operation_types'] ?? []; $types = $state['bootstrap_operation_types'] ?? [];
@ -736,7 +736,7 @@ private function bootstrapOperationTypes(TenantOnboardingSession $draft): array
* @param array<int, string> $selectedTypes * @param array<int, string> $selectedTypes
* @return array<string, int> * @return array<string, int>
*/ */
private function bootstrapRunMap(TenantOnboardingSession $draft, array $selectedTypes): array private function bootstrapRunMap(ManagedEnvironmentOnboardingSession $draft, array $selectedTypes): array
{ {
$state = is_array($draft->state) ? $draft->state : []; $state = is_array($draft->state) ? $draft->state : [];
$runs = $state['bootstrap_operation_runs'] ?? null; $runs = $state['bootstrap_operation_runs'] ?? null;

View File

@ -342,7 +342,7 @@ private function laneForContext(QueuedExecutionContext $context): TenantInteract
$runContext = is_array($context->run->context) ? $context->run->context : []; $runContext = is_array($context->run->context) ? $context->run->context : [];
$wizardFlow = data_get($runContext, 'wizard.flow'); $wizardFlow = data_get($runContext, 'wizard.flow');
if (is_string($wizardFlow) && trim($wizardFlow) === 'managed_tenant_onboarding') { if (is_string($wizardFlow) && trim($wizardFlow) === 'managed_environment_onboarding') {
return TenantInteractionLane::OnboardingWorkflow; return TenantInteractionLane::OnboardingWorkflow;
} }

View File

@ -17,7 +17,7 @@
/** /**
* Generates, auto-resolves, and re-opens permission posture findings * Generates, auto-resolves, and re-opens permission posture findings
* based on the output of TenantPermissionService::compare(). * based on the output of ManagedEnvironmentPermissionService::compare().
*/ */
final class PermissionPostureFindingGenerator implements FindingGeneratorContract final class PermissionPostureFindingGenerator implements FindingGeneratorContract
{ {

View File

@ -4,7 +4,7 @@
namespace App\Services\PortfolioCompare; namespace App\Services\PortfolioCompare;
use App\Jobs\Operations\CrossTenantPromotionExecutionJob; use App\Jobs\Operations\CrossEnvironmentPromotionExecutionJob;
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\ProviderConnection; use App\Models\ProviderConnection;
use App\Models\User; use App\Models\User;
@ -17,14 +17,14 @@
use App\Support\OperationalControls\OperationalControlBlockedException; use App\Support\OperationalControls\OperationalControlBlockedException;
use App\Support\OperationalControls\OperationalControlEvaluator; use App\Support\OperationalControls\OperationalControlEvaluator;
use App\Support\OperationRunStatus; use App\Support\OperationRunStatus;
use App\Support\PortfolioCompare\CrossTenantCompareSelection; use App\Support\PortfolioCompare\CrossEnvironmentCompareSelection;
use App\Support\PortfolioCompare\CrossTenantPromotionExecutionPlanner; use App\Support\PortfolioCompare\CrossEnvironmentPromotionExecutionPlanner;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
final class CrossTenantPromotionExecutionService final class CrossEnvironmentPromotionExecutionService
{ {
public function __construct( public function __construct(
private readonly CrossTenantPromotionExecutionPlanner $planner, private readonly CrossEnvironmentPromotionExecutionPlanner $planner,
private readonly OperationRunService $operationRuns, private readonly OperationRunService $operationRuns,
private readonly WorkspaceAuditLogger $auditLogger, private readonly WorkspaceAuditLogger $auditLogger,
private readonly OperationalControlEvaluator $operationalControls, private readonly OperationalControlEvaluator $operationalControls,
@ -35,12 +35,12 @@ public function __construct(
* @param array<string, mixed> $preflight * @param array<string, mixed> $preflight
*/ */
public function start( public function start(
CrossTenantCompareSelection $selection, CrossEnvironmentCompareSelection $selection,
array $preview, array $preview,
array $preflight, array $preflight,
User $actor, User $actor,
): ProviderOperationStartResult { ): ProviderOperationStartResult {
$workspace = $selection->targetTenant->workspace; $workspace = $selection->targetEnvironment->workspace;
if (! $workspace instanceof Workspace) { if (! $workspace instanceof Workspace) {
throw new \RuntimeException('Promotion execution requires a workspace context.'); throw new \RuntimeException('Promotion execution requires a workspace context.');
@ -60,8 +60,8 @@ public function start(
'reason_text' => $decision->reasonText, 'reason_text' => $decision->reasonText,
'expires_at' => $decision->expiresAt?->toIso8601String(), 'expires_at' => $decision->expiresAt?->toIso8601String(),
'actor_id' => (int) $actor->getKey(), 'actor_id' => (int) $actor->getKey(),
'source_tenant_id' => (int) $selection->sourceTenant->getKey(), 'source_environment_id' => (int) $selection->sourceEnvironment->getKey(),
'target_tenant_id' => (int) $selection->targetTenant->getKey(), 'target_environment_id' => (int) $selection->targetEnvironment->getKey(),
'requested_scope' => 'promotion.execute', 'requested_scope' => 'promotion.execute',
], static fn (mixed $value): bool => $value !== null && $value !== ''), ], static fn (mixed $value): bool => $value !== null && $value !== ''),
], ],
@ -71,14 +71,14 @@ public function start(
resourceId: $decision->sourceActivationId !== null ? (string) $decision->sourceActivationId : null, resourceId: $decision->sourceActivationId !== null ? (string) $decision->sourceActivationId : null,
targetLabel: 'Promotion execution', targetLabel: 'Promotion execution',
summary: 'Promotion execution blocked by operational control', summary: 'Promotion execution blocked by operational control',
tenant: $selection->targetTenant, tenant: $selection->targetEnvironment,
); );
throw OperationalControlBlockedException::forDecision($decision, 'Promotion execution'); throw OperationalControlBlockedException::forDecision($decision, 'Promotion execution');
} }
$plan = $this->planner->build($preview, $preflight); $plan = $this->planner->build($preview, $preflight);
$providerConnection = $this->defaultProviderConnection((int) $selection->targetTenant->getKey()); $providerConnection = $this->defaultProviderConnection((int) $selection->targetEnvironment->getKey());
$now = CarbonImmutable::now(); $now = CarbonImmutable::now();
$identity = array_replace($plan['identity'], [ $identity = array_replace($plan['identity'], [
@ -87,20 +87,20 @@ public function start(
$context = [ $context = [
'operation_type' => 'promotion.execute', 'operation_type' => 'promotion.execute',
'source_tenant_id' => (int) $selection->sourceTenant->getKey(), 'source_environment_id' => (int) $selection->sourceEnvironment->getKey(),
'source_tenant_name' => (string) $selection->sourceTenant->name, 'source_environment_name' => (string) $selection->sourceEnvironment->name,
'target_tenant_id' => (int) $selection->targetTenant->getKey(), 'target_environment_id' => (int) $selection->targetEnvironment->getKey(),
'target_tenant_name' => (string) $selection->targetTenant->name, 'target_environment_name' => (string) $selection->targetEnvironment->name,
'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null, 'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null,
'required_capability' => Capabilities::TENANT_MANAGE, 'required_capability' => Capabilities::TENANT_MANAGE,
'workspace_required_capability' => Capabilities::WORKSPACE_BASELINES_MANAGE, 'workspace_required_capability' => Capabilities::WORKSPACE_BASELINES_MANAGE,
'target_scope' => [ 'target_scope' => [
'workspace_id' => (int) $workspace->getKey(), 'workspace_id' => (int) $workspace->getKey(),
'managed_environment_id' => (int) $selection->targetTenant->getKey(), 'managed_environment_id' => (int) $selection->targetEnvironment->getKey(),
'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null, 'provider_connection_id' => $providerConnection instanceof ProviderConnection ? (int) $providerConnection->getKey() : null,
'entra_tenant_id' => $providerConnection instanceof ProviderConnection 'entra_tenant_id' => $providerConnection instanceof ProviderConnection
? (string) $providerConnection->entra_tenant_id ? (string) $providerConnection->entra_tenant_id
: (string) ($selection->targetTenant->managed_environment_id ?? $selection->targetTenant->external_id ?? $selection->targetTenant->getKey()), : (string) ($selection->targetEnvironment->managed_environment_id ?? $selection->targetEnvironment->external_id ?? $selection->targetEnvironment->getKey()),
], ],
'promotion_execution' => [ 'promotion_execution' => [
'queued_at' => $now->toIso8601String(), 'queued_at' => $now->toIso8601String(),
@ -111,7 +111,7 @@ public function start(
]; ];
$run = $this->operationRuns->ensureRunWithIdentity( $run = $this->operationRuns->ensureRunWithIdentity(
tenant: $selection->targetTenant, tenant: $selection->targetEnvironment,
type: 'promotion.execute', type: 'promotion.execute',
identityInputs: $identity, identityInputs: $identity,
context: $context, context: $context,
@ -134,13 +134,13 @@ public function start(
$this->operationRuns->dispatchOrFail( $this->operationRuns->dispatchOrFail(
$run, $run,
fn (OperationRun $operationRun): mixed => CrossTenantPromotionExecutionJob::dispatch($operationRun), fn (OperationRun $operationRun): mixed => CrossEnvironmentPromotionExecutionJob::dispatch($operationRun),
); );
$this->auditLogger->logCrossTenantPromotionExecutionQueued( $this->auditLogger->logCrossEnvironmentPromotionExecutionQueued(
workspace: $workspace, workspace: $workspace,
sourceTenant: $selection->sourceTenant, sourceEnvironment: $selection->sourceEnvironment,
targetTenant: $selection->targetTenant, targetEnvironment: $selection->targetEnvironment,
operationRun: $run->fresh() ?? $run, operationRun: $run->fresh() ?? $run,
plan: $plan, plan: $plan,
actor: $actor, actor: $actor,
@ -149,10 +149,10 @@ public function start(
return ProviderOperationStartResult::started($run->fresh() ?? $run, true); return ProviderOperationStartResult::started($run->fresh() ?? $run, true);
} }
private function defaultProviderConnection(int $tenantId): ?ProviderConnection private function defaultProviderConnection(int $environmentId): ?ProviderConnection
{ {
return ProviderConnection::query() return ProviderConnection::query()
->where('managed_environment_id', $tenantId) ->where('managed_environment_id', $environmentId)
->where('provider', 'microsoft') ->where('provider', 'microsoft')
->where('is_default', true) ->where('is_default', true)
->orderBy('id') ->orderBy('id')

View File

@ -5,19 +5,19 @@
namespace App\Services\PortfolioTriage; namespace App\Services\PortfolioTriage;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantTriageReview; use App\Models\ManagedEnvironmentTriageReview;
use App\Models\User; use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
use App\Support\BackupHealth\TenantBackupHealthAssessment; use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\PortfolioTriage\TenantTriageReviewFingerprint; use App\Support\PortfolioTriage\ManagedEnvironmentTriageReviewFingerprint;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use InvalidArgumentException; use InvalidArgumentException;
final readonly class TenantTriageReviewService final readonly class ManagedEnvironmentTriageReviewService
{ {
public function __construct( public function __construct(
private TenantTriageReviewFingerprint $fingerprints, private ManagedEnvironmentTriageReviewFingerprint $fingerprints,
private WorkspaceAuditLogger $auditLogger, private WorkspaceAuditLogger $auditLogger,
) {} ) {}
@ -30,11 +30,11 @@ public function markReviewed(
?TenantBackupHealthAssessment $backupHealth = null, ?TenantBackupHealthAssessment $backupHealth = null,
?array $recoveryEvidence = null, ?array $recoveryEvidence = null,
?User $actor = null, ?User $actor = null,
): TenantTriageReview { ): ManagedEnvironmentTriageReview {
return $this->store( return $this->store(
tenant: $tenant, tenant: $tenant,
concernFamily: $concernFamily, concernFamily: $concernFamily,
manualState: TenantTriageReview::STATE_REVIEWED, manualState: ManagedEnvironmentTriageReview::STATE_REVIEWED,
backupHealth: $backupHealth, backupHealth: $backupHealth,
recoveryEvidence: $recoveryEvidence, recoveryEvidence: $recoveryEvidence,
actor: $actor, actor: $actor,
@ -50,11 +50,11 @@ public function markFollowUpNeeded(
?TenantBackupHealthAssessment $backupHealth = null, ?TenantBackupHealthAssessment $backupHealth = null,
?array $recoveryEvidence = null, ?array $recoveryEvidence = null,
?User $actor = null, ?User $actor = null,
): TenantTriageReview { ): ManagedEnvironmentTriageReview {
return $this->store( return $this->store(
tenant: $tenant, tenant: $tenant,
concernFamily: $concernFamily, concernFamily: $concernFamily,
manualState: TenantTriageReview::STATE_FOLLOW_UP_NEEDED, manualState: ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED,
backupHealth: $backupHealth, backupHealth: $backupHealth,
recoveryEvidence: $recoveryEvidence, recoveryEvidence: $recoveryEvidence,
actor: $actor, actor: $actor,
@ -71,8 +71,8 @@ private function store(
?TenantBackupHealthAssessment $backupHealth, ?TenantBackupHealthAssessment $backupHealth,
?array $recoveryEvidence, ?array $recoveryEvidence,
?User $actor, ?User $actor,
): TenantTriageReview { ): ManagedEnvironmentTriageReview {
if (! in_array($manualState, TenantTriageReview::MANUAL_STATES, true)) { if (! in_array($manualState, ManagedEnvironmentTriageReview::MANUAL_STATES, true)) {
throw new InvalidArgumentException('Unsupported triage review state.'); throw new InvalidArgumentException('Unsupported triage review state.');
} }
@ -89,7 +89,7 @@ private function store(
$workspaceId = (int) $tenant->workspace_id; $workspaceId = (int) $tenant->workspace_id;
$now = now(); $now = now();
/** @var TenantTriageReview $review */ /** @var ManagedEnvironmentTriageReview $review */
$review = DB::transaction(function () use ( $review = DB::transaction(function () use (
$tenant, $tenant,
$workspaceId, $workspaceId,
@ -97,8 +97,8 @@ private function store(
$currentConcern, $currentConcern,
$actor, $actor,
$now, $now,
): TenantTriageReview { ): ManagedEnvironmentTriageReview {
TenantTriageReview::query() ManagedEnvironmentTriageReview::query()
->forWorkspace($workspaceId) ->forWorkspace($workspaceId)
->forTenant((int) $tenant->getKey()) ->forTenant((int) $tenant->getKey())
->where('concern_family', $currentConcern['concern_family']) ->where('concern_family', $currentConcern['concern_family'])
@ -108,7 +108,7 @@ private function store(
'updated_at' => $now, 'updated_at' => $now,
]); ]);
return TenantTriageReview::query()->create([ return ManagedEnvironmentTriageReview::query()->create([
'workspace_id' => $workspaceId, 'workspace_id' => $workspaceId,
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $tenant->getKey(),
'concern_family' => $currentConcern['concern_family'], 'concern_family' => $currentConcern['concern_family'],
@ -126,9 +126,9 @@ private function store(
$this->auditLogger->log( $this->auditLogger->log(
workspace: $tenant->workspace, workspace: $tenant->workspace,
action: $manualState === TenantTriageReview::STATE_REVIEWED action: $manualState === ManagedEnvironmentTriageReview::STATE_REVIEWED
? AuditActionId::TenantTriageReviewMarkedReviewed ? AuditActionId::ManagedEnvironmentTriageReviewMarkedReviewed
: AuditActionId::TenantTriageReviewMarkedFollowUpNeeded, : AuditActionId::ManagedEnvironmentTriageReviewMarkedFollowUpNeeded,
context: [ context: [
'metadata' => [ 'metadata' => [
'concern_family' => $currentConcern['concern_family'], 'concern_family' => $currentConcern['concern_family'],
@ -138,7 +138,7 @@ private function store(
], ],
], ],
actor: $actor, actor: $actor,
resourceType: 'tenant_triage_review', resourceType: 'managed_environment_triage_review',
resourceId: (string) $review->getKey(), resourceId: (string) $review->getKey(),
targetLabel: $tenant->name, targetLabel: $tenant->name,
tenant: $tenant, tenant: $tenant,
@ -156,7 +156,7 @@ private function summaryFor(string $concernFamily, string $manualState): string
default => 'Portfolio concern', default => 'Portfolio concern',
}; };
$state = $manualState === TenantTriageReview::STATE_REVIEWED $state = $manualState === ManagedEnvironmentTriageReview::STATE_REVIEWED
? 'reviewed' ? 'reviewed'
: 'follow-up needed'; : 'follow-up needed';

View File

@ -11,7 +11,7 @@
use App\Models\OperationRun; use App\Models\OperationRun;
use App\Models\ReviewPack; use App\Models\ReviewPack;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantReview; use App\Models\EnvironmentReview;
use App\Models\User; use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger; use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver; use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver;
@ -136,7 +136,7 @@ public function generate(ManagedEnvironment $tenant, User $user, array $options
* *
* @param array<string, mixed> $options * @param array<string, mixed> $options
*/ */
public function generateFromReview(TenantReview $review, User $user, array $options = []): ReviewPack public function generateFromReview(EnvironmentReview $review, User $user, array $options = []): ReviewPack
{ {
$review->loadMissing(['tenant', 'evidenceSnapshot', 'sections']); $review->loadMissing(['tenant', 'evidenceSnapshot', 'sections']);
@ -155,7 +155,7 @@ public function generateFromReview(TenantReview $review, User $user, array $opti
if ($existing instanceof ReviewPack) { if ($existing instanceof ReviewPack) {
$this->logReviewExport($review, $user, $existing, 'reused'); $this->logReviewExport($review, $user, $existing, 'reused');
$this->recordReviewPackRequestTelemetry($existing, $user, 'tenant_review'); $this->recordReviewPackRequestTelemetry($existing, $user, 'environment_review');
return $existing; return $existing;
} }
@ -164,7 +164,7 @@ public function generateFromReview(TenantReview $review, User $user, array $opti
tenant: $tenant, tenant: $tenant,
type: OperationRunType::ReviewPackGenerate->value, type: OperationRunType::ReviewPackGenerate->value,
inputs: [ inputs: [
'tenant_review_id' => (int) $review->getKey(), 'environment_review_id' => (int) $review->getKey(),
'include_pii' => $options['include_pii'], 'include_pii' => $options['include_pii'],
'include_operations' => $options['include_operations'], 'include_operations' => $options['include_operations'],
'evidence_snapshot_id' => (int) $snapshot->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(),
@ -177,7 +177,7 @@ public function generateFromReview(TenantReview $review, User $user, array $opti
if ($queuedPack instanceof ReviewPack) { if ($queuedPack instanceof ReviewPack) {
$this->logReviewExport($review, $user, $queuedPack, 'reused_active_run'); $this->logReviewExport($review, $user, $queuedPack, 'reused_active_run');
$this->recordReviewPackRequestTelemetry($queuedPack, $user, 'tenant_review'); $this->recordReviewPackRequestTelemetry($queuedPack, $user, 'environment_review');
return $queuedPack; return $queuedPack;
} }
@ -186,14 +186,14 @@ public function generateFromReview(TenantReview $review, User $user, array $opti
$reviewPack = ReviewPack::create([ $reviewPack = ReviewPack::create([
'managed_environment_id' => (int) $tenant->getKey(), 'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id, 'workspace_id' => (int) $tenant->workspace_id,
'tenant_review_id' => (int) $review->getKey(), 'environment_review_id' => (int) $review->getKey(),
'operation_run_id' => (int) $operationRun->getKey(), 'operation_run_id' => (int) $operationRun->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(), 'initiated_by_user_id' => (int) $user->getKey(),
'status' => ReviewPackStatus::Queued->value, 'status' => ReviewPackStatus::Queued->value,
'options' => $options, 'options' => $options,
'summary' => [ 'summary' => [
'tenant_review_id' => (int) $review->getKey(), 'environment_review_id' => (int) $review->getKey(),
'review_status' => (string) $review->status, 'review_status' => (string) $review->status,
'review_completeness_state' => (string) $review->completeness_state, 'review_completeness_state' => (string) $review->completeness_state,
'section_count' => $review->sections->count(), 'section_count' => $review->sections->count(),
@ -226,7 +226,7 @@ public function generateFromReview(TenantReview $review, User $user, array $opti
}); });
$this->logReviewExport($review, $user, $reviewPack, 'queued'); $this->logReviewExport($review, $user, $reviewPack, 'queued');
$this->recordReviewPackRequestTelemetry($reviewPack, $user, 'tenant_review'); $this->recordReviewPackRequestTelemetry($reviewPack, $user, 'environment_review');
return $reviewPack; return $reviewPack;
} }
@ -308,10 +308,10 @@ public function findExistingPack(ManagedEnvironment $tenant, string $fingerprint
->first(); ->first();
} }
public function findExistingPackForReview(TenantReview $review, string $fingerprint): ?ReviewPack public function findExistingPackForReview(EnvironmentReview $review, string $fingerprint): ?ReviewPack
{ {
return ReviewPack::query() return ReviewPack::query()
->where('tenant_review_id', (int) $review->getKey()) ->where('environment_review_id', (int) $review->getKey())
->ready() ->ready()
->where('fingerprint', $fingerprint) ->where('fingerprint', $fingerprint)
->where('expires_at', '>', now()) ->where('expires_at', '>', now())
@ -330,12 +330,12 @@ public function checkActiveRun(ManagedEnvironment $tenant): bool
->exists(); ->exists();
} }
public function checkActiveRunForReview(TenantReview $review): bool public function checkActiveRunForReview(EnvironmentReview $review): bool
{ {
return OperationRun::query() return OperationRun::query()
->where('managed_environment_id', (int) $review->managed_environment_id) ->where('managed_environment_id', (int) $review->managed_environment_id)
->where('type', OperationRunType::ReviewPackGenerate->value) ->where('type', OperationRunType::ReviewPackGenerate->value)
->whereJsonContains('context->tenant_review_id', (int) $review->getKey()) ->whereJsonContains('context->environment_review_id', (int) $review->getKey())
->active() ->active()
->exists(); ->exists();
} }
@ -376,10 +376,10 @@ private function computeFingerprintForSnapshot(EvidenceSnapshot $snapshot, array
return hash('sha256', json_encode($data, JSON_THROW_ON_ERROR)); return hash('sha256', json_encode($data, JSON_THROW_ON_ERROR));
} }
public function computeFingerprintForReview(TenantReview $review, array $options): string public function computeFingerprintForReview(EnvironmentReview $review, array $options): string
{ {
$data = [ $data = [
'tenant_review_id' => (int) $review->getKey(), 'environment_review_id' => (int) $review->getKey(),
'review_fingerprint' => (string) $review->fingerprint, 'review_fingerprint' => (string) $review->fingerprint,
'review_status' => (string) $review->status, 'review_status' => (string) $review->status,
'delivery_contract' => self::REVIEW_DERIVED_DELIVERY_CONTRACT, 'delivery_contract' => self::REVIEW_DERIVED_DELIVERY_CONTRACT,
@ -414,7 +414,7 @@ private function findPackForRun(ManagedEnvironment $tenant, OperationRun $operat
->first(); ->first();
} }
private function logReviewExport(TenantReview $review, User $user, ReviewPack $reviewPack, string $mode): void private function logReviewExport(EnvironmentReview $review, User $user, ReviewPack $reviewPack, string $mode): void
{ {
$tenant = $review->tenant; $tenant = $review->tenant;
@ -424,7 +424,7 @@ private function logReviewExport(TenantReview $review, User $user, ReviewPack $r
$this->auditLogger->log( $this->auditLogger->log(
workspace: $tenant->workspace, workspace: $tenant->workspace,
action: AuditActionId::TenantReviewExported, action: AuditActionId::EnvironmentReviewExported,
context: [ context: [
'metadata' => [ 'metadata' => [
'review_id' => (int) $review->getKey(), 'review_id' => (int) $review->getKey(),
@ -434,7 +434,7 @@ private function logReviewExport(TenantReview $review, User $user, ReviewPack $r
], ],
], ],
actor: $user, actor: $user,
resourceType: 'tenant_review', resourceType: 'environment_review',
resourceId: (string) $review->getKey(), resourceId: (string) $review->getKey(),
targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()), targetLabel: sprintf('ManagedEnvironment review #%d', (int) $review->getKey()),
operationRunId: $reviewPack->operation_run_id, operationRunId: $reviewPack->operation_run_id,

View File

@ -19,8 +19,8 @@ final class OperationRunTriageService
'directory.groups.sync', 'directory.groups.sync',
'rbac.health_check', 'rbac.health_check',
'entra.admin_roles.scan', 'entra.admin_roles.scan',
'tenant.review_pack.generate', 'environment.review_pack.generate',
'tenant.review.compose', 'environment.review.compose',
]; ];
private const CANCELABLE_TYPES = [ private const CANCELABLE_TYPES = [
@ -29,8 +29,8 @@ final class OperationRunTriageService
'directory.groups.sync', 'directory.groups.sync',
'rbac.health_check', 'rbac.health_check',
'entra.admin_roles.scan', 'entra.admin_roles.scan',
'tenant.review_pack.generate', 'environment.review_pack.generate',
'tenant.review.compose', 'environment.review.compose',
]; ];
public function __construct( public function __construct(

View File

@ -5,7 +5,7 @@
namespace App\Services\Tenants; namespace App\Services\Tenants;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\User; use App\Models\User;
use App\Services\Onboarding\OnboardingLifecycleService; use App\Services\Onboarding\OnboardingLifecycleService;
use App\Support\Audit\AuditActionId; use App\Support\Audit\AuditActionId;
@ -38,7 +38,7 @@ public function buildContext(ManagedEnvironment $tenant, TenantActionSurface $su
$workspaceId = request() !== null $workspaceId = request() !== null
? $this->workspaceContext->currentWorkspaceId(request()) ? $this->workspaceContext->currentWorkspaceId(request())
: null; : null;
$resumeOutcome = $draft instanceof TenantOnboardingSession $resumeOutcome = $draft instanceof ManagedEnvironmentOnboardingSession
? $this->tenantOperabilityService->outcomeFor( ? $this->tenantOperabilityService->outcomeFor(
tenant: $tenant, tenant: $tenant,
question: TenantOperabilityQuestion::ResumeOnboardingEligibility, question: TenantOperabilityQuestion::ResumeOnboardingEligibility,
@ -58,7 +58,7 @@ public function buildContext(ManagedEnvironment $tenant, TenantActionSurface $su
lane: $lane, lane: $lane,
relatedOnboardingDraft: $draft, relatedOnboardingDraft: $draft,
relatedOnboardingIsResumable: $resumeOutcome?->allowed ?? false, relatedOnboardingIsResumable: $resumeOutcome?->allowed ?? false,
hasRelatedOnboardingDraft: $draft instanceof TenantOnboardingSession, hasRelatedOnboardingDraft: $draft instanceof ManagedEnvironmentOnboardingSession,
isArchived: $lifecycle->canRestore(), isArchived: $lifecycle->canRestore(),
); );
} }
@ -130,7 +130,7 @@ public function onboardingEntryDescriptor(int $resumableDraftCount): TenantActio
}; };
} }
public function relatedOnboardingDraft(ManagedEnvironment $tenant, ?User $user = null): ?TenantOnboardingSession public function relatedOnboardingDraft(ManagedEnvironment $tenant, ?User $user = null): ?ManagedEnvironmentOnboardingSession
{ {
$user ??= auth()->user(); $user ??= auth()->user();
@ -138,12 +138,12 @@ public function relatedOnboardingDraft(ManagedEnvironment $tenant, ?User $user =
return null; return null;
} }
return TenantOnboardingSession::query() return ManagedEnvironmentOnboardingSession::query()
->where('workspace_id', (int) $tenant->workspace_id) ->where('workspace_id', (int) $tenant->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey()) ->where('managed_environment_id', (int) $tenant->getKey())
->orderByDesc('updated_at') ->orderByDesc('updated_at')
->get() ->get()
->first(fn (TenantOnboardingSession $draft): bool => Gate::forUser($user)->allows('view', $draft)); ->first(fn (ManagedEnvironmentOnboardingSession $draft): bool => Gate::forUser($user)->allows('view', $draft));
} }
private function viewAction(): TenantActionDescriptor private function viewAction(): TenantActionDescriptor
@ -267,7 +267,7 @@ private function relatedOnboardingActionForContext(
): ?TenantActionDescriptor { ): ?TenantActionDescriptor {
$draft = $context->relatedOnboardingDraft; $draft = $context->relatedOnboardingDraft;
if (! $draft instanceof TenantOnboardingSession) { if (! $draft instanceof ManagedEnvironmentOnboardingSession) {
return null; return null;
} }

View File

@ -5,7 +5,7 @@
namespace App\Services\Tenants; namespace App\Services\Tenants;
use App\Models\ManagedEnvironment; use App\Models\ManagedEnvironment;
use App\Models\TenantOnboardingSession; use App\Models\ManagedEnvironmentOnboardingSession;
use App\Models\User; use App\Models\User;
use App\Services\Auth\CapabilityResolver; use App\Services\Auth\CapabilityResolver;
use App\Support\ReasonTranslation\ReasonResolutionEnvelope; use App\Support\ReasonTranslation\ReasonResolutionEnvelope;
@ -186,7 +186,7 @@ public function outcomeFor(
?User $actor = null, ?User $actor = null,
?int $workspaceId = null, ?int $workspaceId = null,
TenantInteractionLane $lane = TenantInteractionLane::AdministrativeManagement, TenantInteractionLane $lane = TenantInteractionLane::AdministrativeManagement,
?TenantOnboardingSession $onboardingDraft = null, ?ManagedEnvironmentOnboardingSession $onboardingDraft = null,
?string $requiredCapability = null, ?string $requiredCapability = null,
?ManagedEnvironment $selectedTenant = null, ?ManagedEnvironment $selectedTenant = null,
?string $linkedRecordType = null, ?string $linkedRecordType = null,
@ -478,7 +478,7 @@ private function resumeOnboardingOutcome(TenantOperabilityContext $context, Tena
); );
} }
if ($context->onboardingDraft instanceof TenantOnboardingSession && ! $context->onboardingDraft->isWorkflowResumable()) { if ($context->onboardingDraft instanceof ManagedEnvironmentOnboardingSession && ! $context->onboardingDraft->isWorkflowResumable()) {
return TenantOperabilityOutcome::deny( return TenantOperabilityOutcome::deny(
question: TenantOperabilityQuestion::ResumeOnboardingEligibility, question: TenantOperabilityQuestion::ResumeOnboardingEligibility,
lifecycle: $lifecycle, lifecycle: $lifecycle,

View File

@ -34,19 +34,19 @@ enum AuditActionId: string
case PolicyProviderMissingDetected = 'policy.provider_missing_detected'; case PolicyProviderMissingDetected = 'policy.provider_missing_detected';
case PolicyProviderMissingCleared = 'policy.provider_missing_cleared'; case PolicyProviderMissingCleared = 'policy.provider_missing_cleared';
// Managed tenant onboarding wizard. // Managed environment onboarding wizard.
case ManagedTenantOnboardingStart = 'managed_tenant_onboarding.start'; case ManagedEnvironmentOnboardingStart = 'managed_environment_onboarding.start';
case ManagedTenantOnboardingResume = 'managed_tenant_onboarding.resume'; case ManagedEnvironmentOnboardingResume = 'managed_environment_onboarding.resume';
case ManagedTenantOnboardingDraftSelected = 'managed_tenant_onboarding.draft_selected'; case ManagedEnvironmentOnboardingDraftSelected = 'managed_environment_onboarding.draft_selected';
case ManagedTenantOnboardingDraftUpdated = 'managed_tenant_onboarding.draft_updated'; case ManagedEnvironmentOnboardingDraftUpdated = 'managed_environment_onboarding.draft_updated';
case ManagedTenantOnboardingProviderConnectionChanged = 'managed_tenant_onboarding.provider_connection_changed'; case ManagedEnvironmentOnboardingProviderConnectionChanged = 'managed_environment_onboarding.provider_connection_changed';
case ManagedTenantOnboardingVerificationStart = 'managed_tenant_onboarding.verification_start'; case ManagedEnvironmentOnboardingVerificationStart = 'managed_environment_onboarding.verification_start';
case ManagedTenantOnboardingVerificationPersisted = 'managed_tenant_onboarding.verification_persisted'; case ManagedEnvironmentOnboardingVerificationPersisted = 'managed_environment_onboarding.verification_persisted';
case ManagedTenantOnboardingBootstrapStarted = 'managed_tenant_onboarding.bootstrap_started'; case ManagedEnvironmentOnboardingBootstrapStarted = 'managed_environment_onboarding.bootstrap_started';
case ManagedTenantOnboardingCancelled = 'managed_tenant_onboarding.cancelled'; case ManagedEnvironmentOnboardingCancelled = 'managed_environment_onboarding.cancelled';
case ManagedTenantOnboardingDeleted = 'managed_tenant_onboarding.deleted'; case ManagedEnvironmentOnboardingDeleted = 'managed_environment_onboarding.deleted';
case ManagedTenantOnboardingActivationOverrideUsed = 'managed_tenant_onboarding.activation_override_used'; case ManagedEnvironmentOnboardingActivationOverrideUsed = 'managed_environment_onboarding.activation_override_used';
case ManagedTenantOnboardingActivation = 'managed_tenant_onboarding.activation'; case ManagedEnvironmentOnboardingActivation = 'managed_environment_onboarding.activation';
case VerificationCompleted = 'verification.completed'; case VerificationCompleted = 'verification.completed';
case VerificationCheckAcknowledged = 'verification.check_acknowledged'; case VerificationCheckAcknowledged = 'verification.check_acknowledged';
@ -79,9 +79,9 @@ enum AuditActionId: string
case BaselineCompareStarted = 'baseline_compare.started'; case BaselineCompareStarted = 'baseline_compare.started';
case BaselineCompareCompleted = 'baseline_compare.completed'; case BaselineCompareCompleted = 'baseline_compare.completed';
case BaselineCompareFailed = 'baseline_compare.failed'; case BaselineCompareFailed = 'baseline_compare.failed';
case CrossTenantPromotionPreflightGenerated = 'cross_tenant_promotion_preflight.generated'; case CrossEnvironmentPromotionPreflightGenerated = 'cross_environment_promotion_preflight.generated';
case CrossTenantPromotionExecutionQueued = 'cross_tenant_promotion_execution.queued'; case CrossEnvironmentPromotionExecutionQueued = 'cross_environment_promotion_execution.queued';
case CrossTenantPromotionExecutionCompleted = 'cross_tenant_promotion_execution.completed'; case CrossEnvironmentPromotionExecutionCompleted = 'cross_environment_promotion_execution.completed';
case BaselineAssignmentCreated = 'baseline_assignment.created'; case BaselineAssignmentCreated = 'baseline_assignment.created';
case BaselineAssignmentUpdated = 'baseline_assignment.updated'; case BaselineAssignmentUpdated = 'baseline_assignment.updated';
case BaselineAssignmentDeleted = 'baseline_assignment.deleted'; case BaselineAssignmentDeleted = 'baseline_assignment.deleted';
@ -104,17 +104,17 @@ enum AuditActionId: string
case EvidenceSnapshotRefreshed = 'evidence_snapshot.refreshed'; case EvidenceSnapshotRefreshed = 'evidence_snapshot.refreshed';
case EvidenceSnapshotExpired = 'evidence_snapshot.expired'; case EvidenceSnapshotExpired = 'evidence_snapshot.expired';
case EvidenceSnapshotOpened = 'evidence_snapshot.opened'; case EvidenceSnapshotOpened = 'evidence_snapshot.opened';
case TenantReviewCreated = 'tenant_review.created'; case EnvironmentReviewCreated = 'environment_review.created';
case TenantReviewRefreshed = 'tenant_review.refreshed'; case EnvironmentReviewRefreshed = 'environment_review.refreshed';
case TenantReviewPublished = 'tenant_review.published'; case EnvironmentReviewPublished = 'environment_review.published';
case TenantReviewArchived = 'tenant_review.archived'; case EnvironmentReviewArchived = 'environment_review.archived';
case TenantReviewOpened = 'tenant_review.opened'; case EnvironmentReviewOpened = 'environment_review.opened';
case TenantReviewExported = 'tenant_review.exported'; case EnvironmentReviewExported = 'environment_review.exported';
case TenantReviewSuccessorCreated = 'tenant_review.successor_created'; case EnvironmentReviewSuccessorCreated = 'environment_review.successor_created';
case CustomerReviewWorkspaceOpened = 'customer_review_workspace.opened'; case CustomerReviewWorkspaceOpened = 'customer_review_workspace.opened';
case ReviewPackDownloaded = 'review_pack.downloaded'; case ReviewPackDownloaded = 'review_pack.downloaded';
case TenantTriageReviewMarkedReviewed = 'tenant_triage_review.marked_reviewed'; case ManagedEnvironmentTriageReviewMarkedReviewed = 'managed_environment_triage_review.marked_reviewed';
case TenantTriageReviewMarkedFollowUpNeeded = 'tenant_triage_review.marked_follow_up_needed'; case ManagedEnvironmentTriageReviewMarkedFollowUpNeeded = 'managed_environment_triage_review.marked_follow_up_needed';
case SupportDiagnosticsOpened = 'support_diagnostics.opened'; case SupportDiagnosticsOpened = 'support_diagnostics.opened';
case SupportRequestCreated = 'support_request.created'; case SupportRequestCreated = 'support_request.created';
@ -209,18 +209,18 @@ private static function labels(): array
self::ManagedEnvironmentAccessScopeRemove->value => 'ManagedEnvironment access scope removal', self::ManagedEnvironmentAccessScopeRemove->value => 'ManagedEnvironment access scope removal',
self::PolicyProviderMissingDetected->value => 'Policy provider missing detected', self::PolicyProviderMissingDetected->value => 'Policy provider missing detected',
self::PolicyProviderMissingCleared->value => 'Policy provider missing cleared', self::PolicyProviderMissingCleared->value => 'Policy provider missing cleared',
self::ManagedTenantOnboardingStart->value => 'Managed tenant onboarding start', self::ManagedEnvironmentOnboardingStart->value => 'Managed environment onboarding start',
self::ManagedTenantOnboardingResume->value => 'Managed tenant onboarding resume', self::ManagedEnvironmentOnboardingResume->value => 'Managed environment onboarding resume',
self::ManagedTenantOnboardingDraftSelected->value => 'Managed tenant onboarding draft selected', self::ManagedEnvironmentOnboardingDraftSelected->value => 'Managed environment onboarding draft selected',
self::ManagedTenantOnboardingDraftUpdated->value => 'Managed tenant onboarding draft updated', self::ManagedEnvironmentOnboardingDraftUpdated->value => 'Managed environment onboarding draft updated',
self::ManagedTenantOnboardingProviderConnectionChanged->value => 'Managed tenant onboarding provider connection changed', self::ManagedEnvironmentOnboardingProviderConnectionChanged->value => 'Managed environment onboarding provider connection changed',
self::ManagedTenantOnboardingVerificationStart->value => 'Managed tenant onboarding verification start', self::ManagedEnvironmentOnboardingVerificationStart->value => 'Managed environment onboarding verification start',
self::ManagedTenantOnboardingVerificationPersisted->value => 'Managed tenant onboarding verification persisted', self::ManagedEnvironmentOnboardingVerificationPersisted->value => 'Managed environment onboarding verification persisted',
self::ManagedTenantOnboardingBootstrapStarted->value => 'Managed tenant onboarding bootstrap started', self::ManagedEnvironmentOnboardingBootstrapStarted->value => 'Managed environment onboarding bootstrap started',
self::ManagedTenantOnboardingCancelled->value => 'Managed tenant onboarding cancelled', self::ManagedEnvironmentOnboardingCancelled->value => 'Managed environment onboarding cancelled',
self::ManagedTenantOnboardingDeleted->value => 'Managed tenant onboarding deleted', self::ManagedEnvironmentOnboardingDeleted->value => 'Managed environment onboarding deleted',
self::ManagedTenantOnboardingActivationOverrideUsed->value => 'Managed tenant onboarding activation override used', self::ManagedEnvironmentOnboardingActivationOverrideUsed->value => 'Managed environment onboarding activation override used',
self::ManagedTenantOnboardingActivation->value => 'Managed tenant onboarding activation', self::ManagedEnvironmentOnboardingActivation->value => 'Managed environment onboarding activation',
self::VerificationCompleted->value => 'Verification completed', self::VerificationCompleted->value => 'Verification completed',
self::VerificationCheckAcknowledged->value => 'Verification check acknowledged', self::VerificationCheckAcknowledged->value => 'Verification check acknowledged',
self::AlertDestinationCreated->value => 'Alert destination created', self::AlertDestinationCreated->value => 'Alert destination created',
@ -249,9 +249,9 @@ private static function labels(): array
self::BaselineCompareStarted->value => 'Baseline compare started', self::BaselineCompareStarted->value => 'Baseline compare started',
self::BaselineCompareCompleted->value => 'Baseline compare completed', self::BaselineCompareCompleted->value => 'Baseline compare completed',
self::BaselineCompareFailed->value => 'Baseline compare failed', self::BaselineCompareFailed->value => 'Baseline compare failed',
self::CrossTenantPromotionPreflightGenerated->value => 'Cross-tenant promotion preflight generated', self::CrossEnvironmentPromotionPreflightGenerated->value => 'Cross-environment promotion preflight generated',
self::CrossTenantPromotionExecutionQueued->value => 'Cross-tenant promotion execution queued', self::CrossEnvironmentPromotionExecutionQueued->value => 'Cross-environment promotion execution queued',
self::CrossTenantPromotionExecutionCompleted->value => 'Cross-tenant promotion execution completed', self::CrossEnvironmentPromotionExecutionCompleted->value => 'Cross-environment promotion execution completed',
self::BaselineAssignmentCreated->value => 'Baseline assignment created', self::BaselineAssignmentCreated->value => 'Baseline assignment created',
self::BaselineAssignmentUpdated->value => 'Baseline assignment updated', self::BaselineAssignmentUpdated->value => 'Baseline assignment updated',
self::BaselineAssignmentDeleted->value => 'Baseline assignment deleted', self::BaselineAssignmentDeleted->value => 'Baseline assignment deleted',
@ -274,17 +274,17 @@ private static function labels(): array
self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed', self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed',
self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired', self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired',
self::EvidenceSnapshotOpened->value => 'Evidence snapshot opened', self::EvidenceSnapshotOpened->value => 'Evidence snapshot opened',
self::TenantReviewCreated->value => 'ManagedEnvironment review created', self::EnvironmentReviewCreated->value => 'ManagedEnvironment review created',
self::TenantReviewRefreshed->value => 'ManagedEnvironment review refreshed', self::EnvironmentReviewRefreshed->value => 'ManagedEnvironment review refreshed',
self::TenantReviewPublished->value => 'ManagedEnvironment review published', self::EnvironmentReviewPublished->value => 'ManagedEnvironment review published',
self::TenantReviewArchived->value => 'ManagedEnvironment review archived', self::EnvironmentReviewArchived->value => 'ManagedEnvironment review archived',
self::TenantReviewOpened->value => 'ManagedEnvironment review opened', self::EnvironmentReviewOpened->value => 'ManagedEnvironment review opened',
self::TenantReviewExported->value => 'ManagedEnvironment review exported', self::EnvironmentReviewExported->value => 'ManagedEnvironment review exported',
self::TenantReviewSuccessorCreated->value => 'ManagedEnvironment review next cycle created', self::EnvironmentReviewSuccessorCreated->value => 'ManagedEnvironment review next cycle created',
self::CustomerReviewWorkspaceOpened->value => 'Customer review workspace opened', self::CustomerReviewWorkspaceOpened->value => 'Customer review workspace opened',
self::ReviewPackDownloaded->value => 'Review pack downloaded', self::ReviewPackDownloaded->value => 'Review pack downloaded',
self::TenantTriageReviewMarkedReviewed->value => 'Triage review marked reviewed', self::ManagedEnvironmentTriageReviewMarkedReviewed->value => 'Triage review marked reviewed',
self::TenantTriageReviewMarkedFollowUpNeeded->value => 'Triage review marked follow-up needed', self::ManagedEnvironmentTriageReviewMarkedFollowUpNeeded->value => 'Triage review marked follow-up needed',
self::SupportDiagnosticsOpened->value => 'Support diagnostics opened', self::SupportDiagnosticsOpened->value => 'Support diagnostics opened',
self::SupportRequestCreated->value => 'Support request created', self::SupportRequestCreated->value => 'Support request created',
self::SupportRequestExternalTicketCreated->value => 'Support request external ticket created', self::SupportRequestExternalTicketCreated->value => 'Support request external ticket created',
@ -362,9 +362,9 @@ private static function summaries(): array
self::BaselineProfileUpdated->value => 'Baseline profile updated', self::BaselineProfileUpdated->value => 'Baseline profile updated',
self::BaselineProfileArchived->value => 'Baseline profile archived', self::BaselineProfileArchived->value => 'Baseline profile archived',
self::BaselineProfileScopeBackfilled->value => 'Baseline profile scope backfilled', self::BaselineProfileScopeBackfilled->value => 'Baseline profile scope backfilled',
self::CrossTenantPromotionPreflightGenerated->value => 'Cross-tenant promotion preflight generated', self::CrossEnvironmentPromotionPreflightGenerated->value => 'Cross-environment promotion preflight generated',
self::CrossTenantPromotionExecutionQueued->value => 'Cross-tenant promotion execution queued', self::CrossEnvironmentPromotionExecutionQueued->value => 'Cross-environment promotion execution queued',
self::CrossTenantPromotionExecutionCompleted->value => 'Cross-tenant promotion execution completed', self::CrossEnvironmentPromotionExecutionCompleted->value => 'Cross-environment promotion execution completed',
self::AlertDestinationCreated->value => 'Alert destination created', self::AlertDestinationCreated->value => 'Alert destination created',
self::AlertDestinationUpdated->value => 'Alert destination updated', self::AlertDestinationUpdated->value => 'Alert destination updated',
self::AlertDestinationDeleted->value => 'Alert destination deleted', self::AlertDestinationDeleted->value => 'Alert destination deleted',
@ -388,13 +388,13 @@ private static function summaries(): array
self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed', self::EvidenceSnapshotRefreshed->value => 'Evidence snapshot refreshed',
self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired', self::EvidenceSnapshotExpired->value => 'Evidence snapshot expired',
self::EvidenceSnapshotOpened->value => 'Evidence snapshot opened', self::EvidenceSnapshotOpened->value => 'Evidence snapshot opened',
self::TenantReviewCreated->value => 'ManagedEnvironment review created', self::EnvironmentReviewCreated->value => 'ManagedEnvironment review created',
self::TenantReviewRefreshed->value => 'ManagedEnvironment review refreshed', self::EnvironmentReviewRefreshed->value => 'ManagedEnvironment review refreshed',
self::TenantReviewPublished->value => 'ManagedEnvironment review published', self::EnvironmentReviewPublished->value => 'ManagedEnvironment review published',
self::TenantReviewArchived->value => 'ManagedEnvironment review archived', self::EnvironmentReviewArchived->value => 'ManagedEnvironment review archived',
self::TenantReviewOpened->value => 'ManagedEnvironment review opened', self::EnvironmentReviewOpened->value => 'ManagedEnvironment review opened',
self::TenantReviewExported->value => 'ManagedEnvironment review exported', self::EnvironmentReviewExported->value => 'ManagedEnvironment review exported',
self::TenantReviewSuccessorCreated->value => 'ManagedEnvironment review next cycle created', self::EnvironmentReviewSuccessorCreated->value => 'ManagedEnvironment review next cycle created',
self::CustomerReviewWorkspaceOpened->value => 'Customer review workspace opened', self::CustomerReviewWorkspaceOpened->value => 'Customer review workspace opened',
self::ReviewPackDownloaded->value => 'Review pack downloaded', self::ReviewPackDownloaded->value => 'Review pack downloaded',
self::SupportDiagnosticsOpened->value => 'Support diagnostics opened', self::SupportDiagnosticsOpened->value => 'Support diagnostics opened',

View File

@ -27,28 +27,28 @@ class Capabilities
public const WORKSPACE_MEMBERSHIP_MANAGE = 'workspace_membership.manage'; public const WORKSPACE_MEMBERSHIP_MANAGE = 'workspace_membership.manage';
// Managed tenant onboarding // Managed environment onboarding
public const WORKSPACE_MANAGED_TENANT_ONBOARD = 'workspace_managed_tenant.onboard'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD = 'workspace_managed_environment.onboard';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_IDENTIFY = 'workspace_managed_tenant.onboard.identify'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_IDENTIFY = 'workspace_managed_environment.onboard.identify';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_CANCEL = 'workspace_managed_tenant.onboard.cancel'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CANCEL = 'workspace_managed_environment.onboard.cancel';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_VIEW = 'workspace_managed_tenant.onboard.connection.view'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_VIEW = 'workspace_managed_environment.onboard.connection.view';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE = 'workspace_managed_tenant.onboard.connection.manage'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_MANAGE = 'workspace_managed_environment.onboard.connection.manage';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_CONNECTION_MANAGE_DEDICATED = 'workspace_managed_tenant.onboard.connection.manage_dedicated'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_CONNECTION_MANAGE_DEDICATED = 'workspace_managed_environment.onboard.connection.manage_dedicated';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_VERIFICATION_START = 'workspace_managed_tenant.onboard.verification.start'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_VERIFICATION_START = 'workspace_managed_environment.onboard.verification.start';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC = 'workspace_managed_tenant.onboard.bootstrap.inventory_sync'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC = 'workspace_managed_environment.onboard.bootstrap.inventory_sync';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_POLICY_SYNC = 'workspace_managed_tenant.onboard.bootstrap.policy_sync'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_POLICY_SYNC = 'workspace_managed_environment.onboard.bootstrap.policy_sync';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP = 'workspace_managed_tenant.onboard.bootstrap.backup_bootstrap'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_BACKUP_BOOTSTRAP = 'workspace_managed_environment.onboard.bootstrap.backup_bootstrap';
public const WORKSPACE_MANAGED_TENANT_ONBOARD_ACTIVATE = 'workspace_managed_tenant.onboard.activate'; public const WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_ACTIVATE = 'workspace_managed_environment.onboard.activate';
// Workspace settings // Workspace settings
public const WORKSPACE_SETTINGS_VIEW = 'workspace_settings.view'; public const WORKSPACE_SETTINGS_VIEW = 'workspace_settings.view';
@ -146,12 +146,12 @@ class Capabilities
public const REVIEW_PACK_MANAGE = 'review_pack.manage'; public const REVIEW_PACK_MANAGE = 'review_pack.manage';
// ManagedEnvironment reviews // ManagedEnvironment reviews
public const TENANT_REVIEW_VIEW = 'tenant_review.view'; public const ENVIRONMENT_REVIEW_VIEW = 'environment_review.view';
public const TENANT_REVIEW_MANAGE = 'tenant_review.manage'; public const ENVIRONMENT_REVIEW_MANAGE = 'environment_review.manage';
// Portfolio triage review progress // Portfolio triage review progress
public const TENANT_TRIAGE_REVIEW_MANAGE = 'tenant_triage_review.manage'; public const MANAGED_ENVIRONMENT_TRIAGE_REVIEW_MANAGE = 'managed_environment_triage_review.manage';
// Evidence snapshots // Evidence snapshots
public const EVIDENCE_VIEW = 'evidence.view'; public const EVIDENCE_VIEW = 'evidence.view';

View File

@ -42,7 +42,7 @@ final class BadgeCatalog
BadgeDomain::TenantStatus->value => Domains\TenantStatusBadge::class, BadgeDomain::TenantStatus->value => Domains\TenantStatusBadge::class,
BadgeDomain::TenantWorkspacePosture->value => Domains\TenantWorkspacePostureBadge::class, BadgeDomain::TenantWorkspacePosture->value => Domains\TenantWorkspacePostureBadge::class,
BadgeDomain::TenantRbacStatus->value => Domains\TenantRbacStatusBadge::class, BadgeDomain::TenantRbacStatus->value => Domains\TenantRbacStatusBadge::class,
BadgeDomain::TenantPermissionStatus->value => Domains\TenantPermissionStatusBadge::class, BadgeDomain::ManagedEnvironmentPermissionStatus->value => Domains\ManagedEnvironmentPermissionStatusBadge::class,
BadgeDomain::PolicySnapshotMode->value => Domains\PolicySnapshotModeBadge::class, BadgeDomain::PolicySnapshotMode->value => Domains\PolicySnapshotModeBadge::class,
BadgeDomain::PolicyRestoreMode->value => Domains\PolicyRestoreModeBadge::class, BadgeDomain::PolicyRestoreMode->value => Domains\PolicyRestoreModeBadge::class,
BadgeDomain::PolicyRisk->value => Domains\PolicyRiskBadge::class, BadgeDomain::PolicyRisk->value => Domains\PolicyRiskBadge::class,
@ -53,7 +53,7 @@ final class BadgeCatalog
BadgeDomain::ProviderConsentStatus->value => Domains\ProviderConsentStatusBadge::class, BadgeDomain::ProviderConsentStatus->value => Domains\ProviderConsentStatusBadge::class,
BadgeDomain::ProviderVerificationStatus->value => Domains\ProviderVerificationStatusBadge::class, BadgeDomain::ProviderVerificationStatus->value => Domains\ProviderVerificationStatusBadge::class,
BadgeDomain::ProviderCapabilityStatus->value => Domains\ProviderCapabilityStatusBadge::class, BadgeDomain::ProviderCapabilityStatus->value => Domains\ProviderCapabilityStatusBadge::class,
BadgeDomain::ManagedTenantOnboardingVerificationStatus->value => Domains\ManagedTenantOnboardingVerificationStatusBadge::class, BadgeDomain::ManagedEnvironmentOnboardingVerificationStatus->value => Domains\ManagedEnvironmentOnboardingVerificationStatusBadge::class,
BadgeDomain::VerificationCheckStatus->value => Domains\VerificationCheckStatusBadge::class, BadgeDomain::VerificationCheckStatus->value => Domains\VerificationCheckStatusBadge::class,
BadgeDomain::VerificationCheckSeverity->value => Domains\VerificationCheckSeverityBadge::class, BadgeDomain::VerificationCheckSeverity->value => Domains\VerificationCheckSeverityBadge::class,
BadgeDomain::VerificationReportOverall->value => Domains\VerificationReportOverallBadge::class, BadgeDomain::VerificationReportOverall->value => Domains\VerificationReportOverallBadge::class,
@ -66,9 +66,9 @@ final class BadgeCatalog
BadgeDomain::CommercialLifecycleState->value => Domains\CommercialLifecycleStateBadge::class, BadgeDomain::CommercialLifecycleState->value => Domains\CommercialLifecycleStateBadge::class,
BadgeDomain::EvidenceSnapshotStatus->value => Domains\EvidenceSnapshotStatusBadge::class, BadgeDomain::EvidenceSnapshotStatus->value => Domains\EvidenceSnapshotStatusBadge::class,
BadgeDomain::EvidenceCompleteness->value => Domains\EvidenceCompletenessBadge::class, BadgeDomain::EvidenceCompleteness->value => Domains\EvidenceCompletenessBadge::class,
BadgeDomain::TenantReviewStatus->value => Domains\TenantReviewStatusBadge::class, BadgeDomain::EnvironmentReviewStatus->value => Domains\EnvironmentReviewStatusBadge::class,
BadgeDomain::TenantReviewCompleteness->value => Domains\TenantReviewCompletenessStateBadge::class, BadgeDomain::EnvironmentReviewCompleteness->value => Domains\EnvironmentReviewCompletenessStateBadge::class,
BadgeDomain::TenantTriageReviewState->value => Domains\TenantTriageReviewStateBadge::class, BadgeDomain::ManagedEnvironmentTriageReviewState->value => Domains\ManagedEnvironmentTriageReviewStateBadge::class,
BadgeDomain::SystemHealth->value => Domains\SystemHealthBadge::class, BadgeDomain::SystemHealth->value => Domains\SystemHealthBadge::class,
BadgeDomain::ReferenceResolutionState->value => Domains\ReferenceResolutionStateBadge::class, BadgeDomain::ReferenceResolutionState->value => Domains\ReferenceResolutionStateBadge::class,
BadgeDomain::DiffRowStatus->value => Domains\DiffRowStatusBadge::class, BadgeDomain::DiffRowStatus->value => Domains\DiffRowStatusBadge::class,
@ -204,7 +204,7 @@ public static function normalizeProviderVerificationStatus(mixed $value): ?strin
}; };
} }
public static function normalizeManagedTenantOnboardingVerificationStatus(mixed $value): ?string public static function normalizeManagedEnvironmentOnboardingVerificationStatus(mixed $value): ?string
{ {
$state = self::normalizeState($value); $state = self::normalizeState($value);

View File

@ -33,7 +33,7 @@ enum BadgeDomain: string
case TenantStatus = 'tenant_status'; case TenantStatus = 'tenant_status';
case TenantWorkspacePosture = 'tenant_workspace_posture'; case TenantWorkspacePosture = 'tenant_workspace_posture';
case TenantRbacStatus = 'tenant_rbac_status'; case TenantRbacStatus = 'tenant_rbac_status';
case TenantPermissionStatus = 'tenant_permission_status'; case ManagedEnvironmentPermissionStatus = 'managed_environment_permission_status';
case PolicySnapshotMode = 'policy_snapshot_mode'; case PolicySnapshotMode = 'policy_snapshot_mode';
case PolicyRestoreMode = 'policy_restore_mode'; case PolicyRestoreMode = 'policy_restore_mode';
case PolicyRisk = 'policy_risk'; case PolicyRisk = 'policy_risk';
@ -44,7 +44,7 @@ enum BadgeDomain: string
case ProviderConsentStatus = 'provider_connection.consent_status'; case ProviderConsentStatus = 'provider_connection.consent_status';
case ProviderVerificationStatus = 'provider_connection.verification_status'; case ProviderVerificationStatus = 'provider_connection.verification_status';
case ProviderCapabilityStatus = 'provider_capability_status'; case ProviderCapabilityStatus = 'provider_capability_status';
case ManagedTenantOnboardingVerificationStatus = 'managed_tenant_onboarding.verification_status'; case ManagedEnvironmentOnboardingVerificationStatus = 'managed_environment_onboarding.verification_status';
case VerificationCheckStatus = 'verification_check_status'; case VerificationCheckStatus = 'verification_check_status';
case VerificationCheckSeverity = 'verification_check_severity'; case VerificationCheckSeverity = 'verification_check_severity';
case VerificationReportOverall = 'verification_report_overall'; case VerificationReportOverall = 'verification_report_overall';
@ -57,9 +57,9 @@ enum BadgeDomain: string
case CommercialLifecycleState = 'commercial_lifecycle_state'; case CommercialLifecycleState = 'commercial_lifecycle_state';
case EvidenceSnapshotStatus = 'evidence_snapshot_status'; case EvidenceSnapshotStatus = 'evidence_snapshot_status';
case EvidenceCompleteness = 'evidence_completeness'; case EvidenceCompleteness = 'evidence_completeness';
case TenantReviewStatus = 'tenant_review_status'; case EnvironmentReviewStatus = 'environment_review_status';
case TenantReviewCompleteness = 'tenant_review_completeness'; case EnvironmentReviewCompleteness = 'environment_review_completeness';
case TenantTriageReviewState = 'tenant_triage_review_state'; case ManagedEnvironmentTriageReviewState = 'managed_environment_triage_review_state';
case SystemHealth = 'system_health'; case SystemHealth = 'system_health';
case ReferenceResolutionState = 'reference_resolution_state'; case ReferenceResolutionState = 'reference_resolution_state';
case DiffRowStatus = 'diff_row_status'; case DiffRowStatus = 'diff_row_status';

Some files were not shown because too many files have changed in this diff Show More