TenantAtlas/apps/platform/app/Support/OperateHub/OperateHubShell.php
ahmido e324bd7bd6 feat: canonicalize admin scope links and queries (341) (#413)
## Summary
- remove remaining legacy scope query hint parsing from shared workspace and environment scoping seams so hubs only narrow via explicit `environment_id`
- align canonical link generation across workspace hubs, provider connections, audit log, alerts, and decision register flows
- add focused Spec 341 regression coverage for canonical link/query behavior and legacy alias rejection
- include the Spec 341 artifacts and move the review screenshots into `specs/341-canonical-link-query-cleanup/artifacts/screenshots/`
- ignore local `.playwright-mcp` browser tool output so it does not pollute future commits or pull requests

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation --filter=Spec341`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation/Spec341CanonicalLinkQueryCleanupTest.php tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php tests/Feature/ProviderConnections/ProviderConnectionsWorkspaceHubContractTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `git diff --check`

## Notes
- Livewire v4 compliance unchanged
- Filament provider registration remains in `apps/platform/bootstrap/providers.php`
- no globally searchable resource behavior was changed in this slice
- no destructive action behavior was changed
- no new Filament assets; deploy `filament:assets` posture is unchanged

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #413
2026-05-31 22:46:39 +00:00

436 lines
15 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\OperateHub;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Auth\CapabilityResolver;
use App\Services\Tenants\TenantOperabilityService;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Navigation\AdminSurfaceScope;
use App\Support\Tenants\TenantOperabilityQuestion;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Illuminate\Http\Request;
final class OperateHubShell
{
private const string REQUEST_ATTRIBUTE = 'tenantpilot.resolved_shell_context';
public function __construct(
private WorkspaceContext $workspaceContext,
private CapabilityResolver $capabilityResolver,
private TenantOperabilityService $tenantOperabilityService,
) {}
public function scopeLabel(?Request $request = null): string
{
$activeEnvironment = $this->activeEntitledTenant($request);
if ($activeEnvironment instanceof ManagedEnvironment) {
return __('localization.shell.environment_scope').': '.$activeEnvironment->name;
}
return __('localization.shell.all_environments');
}
/**
* @return array{label: string, url: string}|null
*/
public function returnAffordance(?Request $request = null): ?array
{
$activeEnvironment = $this->activeEntitledTenant($request);
if ($activeEnvironment instanceof ManagedEnvironment) {
return [
'label' => 'Back to '.$activeEnvironment->name,
'url' => ManagedEnvironmentLinks::viewUrl($activeEnvironment),
];
}
return null;
}
/**
* @return array<Action>
*/
public function headerActions(
string $scopeActionName = 'operate_hub_scope',
string $returnActionName = 'operate_hub_return',
?Request $request = null,
): array {
$actions = [];
$activeEnvironment = $this->activeEntitledTenant($request);
if ($activeEnvironment instanceof ManagedEnvironment) {
$actions[] = Action::make($scopeActionName)
->label($this->scopeLabel($request))
->color('gray')
->disabled();
}
$returnAffordance = $this->returnAffordance($request);
if (is_array($returnAffordance)) {
$actions[] = Action::make($returnActionName)
->label($returnAffordance['label'])
->icon('heroicon-o-arrow-left')
->color('gray')
->url($returnAffordance['url']);
}
return $actions;
}
public function activeEntitledTenant(?Request $request = null): ?ManagedEnvironment
{
return $this->resolvedContext($request)->tenant;
}
public function tenantOwnedPanelContext(?Request $request = null): ?ManagedEnvironment
{
return $this->activeEntitledTenant($request);
}
public function resolvedContext(?Request $request = null): ResolvedShellContext
{
$request ??= request();
if ($request instanceof Request) {
$cached = $request->attributes->get(self::REQUEST_ATTRIBUTE);
if ($cached instanceof ResolvedShellContext) {
return $cached;
}
}
$resolved = $this->buildResolvedContext($request);
if ($request instanceof Request) {
$request->attributes->set(self::REQUEST_ATTRIBUTE, $resolved);
}
return $resolved;
}
private function buildResolvedContext(?Request $request = null): ResolvedShellContext
{
$pageCategory = $this->pageCategory($request);
$routeTenantCandidate = $this->resolveRouteTenantCandidate($request, $pageCategory);
$sessionWorkspace = $this->workspaceContext->currentWorkspace($request);
$workspace = $this->resolveWorkspaceForPageCategory($routeTenantCandidate, $pageCategory, $request);
$workspaceSource = match (true) {
$workspace instanceof Workspace && $sessionWorkspace instanceof Workspace && $workspace->is($sessionWorkspace) => 'session_workspace',
$workspace instanceof Workspace && $routeTenantCandidate instanceof ManagedEnvironment && (int) $routeTenantCandidate->workspace_id === (int) $workspace->getKey() => 'route',
default => 'none',
};
if (! $workspace instanceof Workspace) {
return new ResolvedShellContext(
workspace: null,
tenant: null,
pageCategory: $pageCategory,
state: 'missing_workspace',
displayMode: 'recovery',
recoveryAction: $pageCategory === AdminSurfaceScope::WorkspaceChooserException ? 'none' : 'redirect_choose_workspace',
recoveryDestination: '/admin/choose-workspace',
recoveryReason: 'missing_workspace',
);
}
$routeTenant = $this->resolveValidatedRouteTenant($routeTenantCandidate, $workspace, $request, $pageCategory);
if ($routeTenant['tenant'] instanceof ManagedEnvironment) {
return new ResolvedShellContext(
workspace: $workspace,
tenant: $routeTenant['tenant'],
pageCategory: $pageCategory,
state: 'tenant_scoped',
displayMode: 'tenant_scoped',
workspaceSource: $workspaceSource,
tenantSource: 'route',
);
}
$recoveryReason = $routeTenant['reason'];
if ($pageCategory === AdminSurfaceScope::EnvironmentBound && $recoveryReason !== null) {
return new ResolvedShellContext(
workspace: $workspace,
tenant: null,
pageCategory: $pageCategory,
state: 'invalid_tenant',
displayMode: 'recovery',
workspaceSource: $workspaceSource,
recoveryAction: 'abort_not_found',
recoveryReason: $recoveryReason,
);
}
if ($pageCategory->forcesEnvironmentlessShellContext()) {
return new ResolvedShellContext(
workspace: $workspace,
tenant: null,
pageCategory: $pageCategory,
state: 'tenantless_workspace',
displayMode: 'tenantless',
workspaceSource: $workspaceSource,
recoveryReason: $recoveryReason,
);
}
$tenant = $this->resolveValidatedFilamentTenant($request, $pageCategory, $workspace);
if ($tenant instanceof ManagedEnvironment) {
return new ResolvedShellContext(
workspace: $workspace,
tenant: $tenant,
pageCategory: $pageCategory,
state: 'tenant_scoped',
displayMode: 'tenant_scoped',
workspaceSource: $workspaceSource,
tenantSource: 'filament_tenant',
);
}
if ($pageCategory->allowsRememberedEnvironmentRestore()) {
$rememberedEnvironment = $this->workspaceContext->rememberedEnvironment($request);
if ($rememberedEnvironment instanceof ManagedEnvironment) {
return new ResolvedShellContext(
workspace: $workspace,
tenant: $rememberedEnvironment,
pageCategory: $pageCategory,
state: 'tenant_scoped',
displayMode: 'tenant_scoped',
workspaceSource: $workspaceSource,
tenantSource: 'remembered',
);
}
}
if ($pageCategory->requiresExplicitEnvironment()) {
return new ResolvedShellContext(
workspace: $workspace,
tenant: null,
pageCategory: $pageCategory,
state: 'missing_tenant',
displayMode: 'recovery',
workspaceSource: $workspaceSource,
recoveryAction: 'abort_not_found',
recoveryDestination: null,
recoveryReason: $recoveryReason ?? 'missing_tenant',
);
}
return new ResolvedShellContext(
workspace: $workspace,
tenant: null,
pageCategory: $pageCategory,
state: 'tenantless_workspace',
displayMode: 'tenantless',
workspaceSource: $workspaceSource,
recoveryReason: $recoveryReason,
);
}
private function resolveValidatedFilamentTenant(
?Request $request = null,
?AdminSurfaceScope $pageCategory = null,
?Workspace $workspace = null,
): ?ManagedEnvironment {
$tenant = Filament::getTenant();
if (! $tenant instanceof ManagedEnvironment) {
return null;
}
$pageCategory ??= $this->pageCategory($request);
$workspace ??= $this->resolveWorkspaceForPageCategory($tenant, $pageCategory, $request);
if ($workspace instanceof Workspace && $this->tenantValidationReason($tenant, $workspace, $request, $pageCategory) === null) {
return $tenant;
}
Filament::setTenant(null, true);
return null;
}
private function resolveValidatedRouteTenant(
?ManagedEnvironment $tenant,
Workspace $workspace,
?Request $request = null,
?AdminSurfaceScope $pageCategory = null,
): array {
$pageCategory ??= $this->pageCategory($request);
if (! $tenant instanceof ManagedEnvironment) {
return ['tenant' => null, 'reason' => null];
}
$reason = $this->tenantValidationReason($tenant, $workspace, $request, $pageCategory);
if ($reason !== null) {
return ['tenant' => null, 'reason' => $reason];
}
return ['tenant' => $tenant, 'reason' => null];
}
private function resolveWorkspaceForPageCategory(
?ManagedEnvironment $tenant,
AdminSurfaceScope $pageCategory,
?Request $request = null,
): ?Workspace {
return match ($pageCategory) {
AdminSurfaceScope::EnvironmentBound => $this->workspaceContext->currentWorkspaceOrEnvironmentWorkspace($tenant, $request),
default => $this->workspaceContext->currentWorkspaceOrEnvironmentWorkspace($tenant, $request),
};
}
private function resolveRouteTenantCandidate(?Request $request = null, ?AdminSurfaceScope $pageCategory = null): ?ManagedEnvironment
{
$route = $request?->route();
$pageCategory ??= $this->pageCategory($request);
if ($route?->hasParameter('environment')) {
return $this->resolveTenantIdentifier($route->parameter('environment'));
}
if ($route?->hasParameter('tenant')) {
return $this->resolveTenantIdentifier($route->parameter('tenant'));
}
$refererEnvironment = $this->resolveRefererTenantCandidate($request, $pageCategory);
if ($refererEnvironment instanceof ManagedEnvironment) {
return $refererEnvironment;
}
return null;
}
private function resolveRefererTenantCandidate(?Request $request, AdminSurfaceScope $pageCategory): ?ManagedEnvironment
{
if (! $request instanceof Request) {
return null;
}
$path = '/'.ltrim((string) $request->path(), '/');
$isLivewireUpdateRequest = preg_match('#^/(?:livewire(?:-[^/]+)?/update|livewire-unit-test-endpoint)(?:/|$)#', $path) === 1
|| $request->headers->has('x-livewire');
if (! $isLivewireUpdateRequest) {
return null;
}
if ($pageCategory !== AdminSurfaceScope::EnvironmentBound) {
return null;
}
$refererPath = parse_url((string) $request->headers->get('referer', ''), PHP_URL_PATH);
if (! is_string($refererPath) || $refererPath === '') {
return null;
}
$normalizedRefererPath = '/'.ltrim($refererPath, '/');
$matches = [];
if (preg_match('#^/admin/workspaces/[^/]+/environments/([^/]+)(?:/|$)#', $normalizedRefererPath, $matches) !== 1) {
return null;
}
$environmentIdentifier = $matches[1] ?? null;
if (! is_string($environmentIdentifier) || $environmentIdentifier === '') {
return null;
}
return $this->resolveTenantIdentifier($environmentIdentifier);
}
private function resolveTenantIdentifier(mixed $tenantIdentifier): ?ManagedEnvironment
{
if ($tenantIdentifier instanceof ManagedEnvironment) {
return $tenantIdentifier;
}
$tenantIdentifier = trim((string) $tenantIdentifier);
if ($tenantIdentifier === '') {
return null;
}
$tenantKeyColumn = (new ManagedEnvironment)->getQualifiedKeyName();
return ManagedEnvironment::query()
->withTrashed()
->where(static function ($query) use ($tenantIdentifier, $tenantKeyColumn): void {
$query->where('slug', $tenantIdentifier);
if (ctype_digit($tenantIdentifier)) {
$query->orWhere($tenantKeyColumn, (int) $tenantIdentifier);
}
})
->first();
}
private function tenantValidationReason(
ManagedEnvironment $tenant,
Workspace $workspace,
?Request $request = null,
?AdminSurfaceScope $pageCategory = null,
): ?string {
$pageCategory ??= $this->pageCategory($request);
if ((int) $tenant->workspace_id !== (int) $workspace->getKey()) {
return 'mismatched_workspace';
}
$user = auth()->user();
if (! $user instanceof User) {
return 'not_member';
}
if (! $this->capabilityResolver->isMember($user, $tenant)) {
return 'not_member';
}
$question = $pageCategory === AdminSurfaceScope::EnvironmentBound
? TenantOperabilityQuestion::EnvironmentBoundViewability
: TenantOperabilityQuestion::AdministrativeDiscoverability;
$allowed = $this->tenantOperabilityService->outcomeFor(
tenant: $tenant,
question: $question,
actor: $user,
workspaceId: (int) $workspace->getKey(),
lane: $pageCategory->lane(),
selectedTenant: Filament::getTenant() instanceof ManagedEnvironment ? Filament::getTenant() : null,
)->allowed;
if ($allowed) {
return null;
}
return $pageCategory === AdminSurfaceScope::EnvironmentBound
? 'inaccessible'
: 'not_operable';
}
private function pageCategory(?Request $request = null): AdminSurfaceScope
{
return AdminSurfaceScope::fromRequest($request);
}
}