Spec 280: prepare workspace tenancy and environment routing cutover (#336)
## Summary - add the implementation-ready spec-prep artifacts for Spec 280: Filament Workspace Tenancy & Environment Routing Cutover - define the bounded scope, rollout constraints, route contract, and validation plan for the workspace-first routing cutover - update the generated Copilot agent context for the active feature branch ## Testing - not run; this branch adds spec-prep artifacts only and does not change application code ## Notes - no application runtime, database, or frontend code changes are included in this PR - target base branch requested: `platform-dev` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #336
This commit is contained in:
parent
e64bae9cfc
commit
670c46dedd
4
.github/agents/copilot-instructions.md
vendored
4
.github/agents/copilot-instructions.md
vendored
@ -276,6 +276,8 @@ ## Active Technologies
|
|||||||
- PostgreSQL via existing `users.preferred_locale`, existing workspace localization setting, existing `tenant_reviews`, `review_packs`, `evidence_snapshots`, memberships, and `audit_logs`; translation catalogs in `apps/platform/lang/en` and `apps/platform/lang/de`; no new persistence planned (275-customer-facing-localization-adoption)
|
- PostgreSQL via existing `users.preferred_locale`, existing workspace localization setting, existing `tenant_reviews`, `review_packs`, `evidence_snapshots`, memberships, and `audit_logs`; translation catalogs in `apps/platform/lang/en` and `apps/platform/lang/de`; no new persistence planned (275-customer-facing-localization-adoption)
|
||||||
- Markdown and YAML planning artifacts over PHP 8.4 / Laravel 12 source anchors + `spec.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `docs/ui/tenantpilot-enterprise-ui-standards.md`, nearby Specs `270`, `275`, and `277`, and repo-real source anchors such as `OperationUxPresenter`, `InventoryKpiHeader`, `RecoveryReadiness`, `BaselineSnapshotPresenter`, `ReviewPackService`, and `TenantDashboardSummaryBuilder` (278-cross-domain-indicator-audit)
|
- Markdown and YAML planning artifacts over PHP 8.4 / Laravel 12 source anchors + `spec.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `docs/ui/tenantpilot-enterprise-ui-standards.md`, nearby Specs `270`, `275`, and `277`, and repo-real source anchors such as `OperationUxPresenter`, `InventoryKpiHeader`, `RecoveryReadiness`, `BaselineSnapshotPresenter`, `ReviewPackService`, and `TenantDashboardSummaryBuilder` (278-cross-domain-indicator-audit)
|
||||||
- Repository files only; no database or runtime persistence changes (278-cross-domain-indicator-audit)
|
- Repository files only; no database or runtime persistence changes (278-cross-domain-indicator-audit)
|
||||||
|
- PHP 8.4.15, Laravel 12.52 + Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, existing workspace and environment authorization/context helpers, existing Filament panel providers and page/resource action-surface contracts (280-workspace-tenancy-environment-routing)
|
||||||
|
- PostgreSQL, no new persistence or schema change in this slice (280-workspace-tenancy-environment-routing)
|
||||||
|
|
||||||
- PHP 8.4.15 (feat/005-bulk-operations)
|
- PHP 8.4.15 (feat/005-bulk-operations)
|
||||||
|
|
||||||
@ -310,9 +312,9 @@ ## Code Style
|
|||||||
PHP 8.4.15: Follow standard conventions
|
PHP 8.4.15: Follow standard conventions
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
- 280-workspace-tenancy-environment-routing: Added PHP 8.4.15, Laravel 12.52 + Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, existing workspace and environment authorization/context helpers, existing Filament panel providers and page/resource action-surface contracts
|
||||||
- 278-cross-domain-indicator-audit: Added Markdown and YAML planning artifacts over PHP 8.4 / Laravel 12 source anchors + `spec.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `docs/ui/tenantpilot-enterprise-ui-standards.md`, nearby Specs `270`, `275`, and `277`, and repo-real source anchors such as `OperationUxPresenter`, `InventoryKpiHeader`, `RecoveryReadiness`, `BaselineSnapshotPresenter`, `ReviewPackService`, and `TenantDashboardSummaryBuilder`
|
- 278-cross-domain-indicator-audit: Added Markdown and YAML planning artifacts over PHP 8.4 / Laravel 12 source anchors + `spec.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `docs/ui/tenantpilot-enterprise-ui-standards.md`, nearby Specs `270`, `275`, and `277`, and repo-real source anchors such as `OperationUxPresenter`, `InventoryKpiHeader`, `RecoveryReadiness`, `BaselineSnapshotPresenter`, `ReviewPackService`, and `TenantDashboardSummaryBuilder`
|
||||||
- 275-customer-facing-localization-adoption: Added PHP 8.4, Laravel 12 + Filament v5, Livewire v4, Laravel translator, existing `App\Services\Localization\LocaleResolver`, `App\Http\Controllers\LocalizationController`, current `localization.review.*` and locale feedback catalogs, `CustomerReviewWorkspace`, `TenantReviewResource`, `ViewTenantReview`, current review-pack and evidence resource paths, shared RBAC and audit helpers
|
- 275-customer-facing-localization-adoption: Added PHP 8.4, Laravel 12 + Filament v5, Livewire v4, Laravel translator, existing `App\Services\Localization\LocaleResolver`, `App\Http\Controllers\LocalizationController`, current `localization.review.*` and locale feedback catalogs, `CustomerReviewWorkspace`, `TenantReviewResource`, `ViewTenantReview`, current review-pack and evidence resource paths, shared RBAC and audit helpers
|
||||||
- 266-tenant-dashboard-productization-v1: Added PHP 8.4, Laravel 12 + Filament v5, Livewire v4, Tailwind v4, Pest v4, existing dashboard widgets, `TenantGovernanceAggregateResolver`, `RestoreSafetyResolver`, `BackupHealthDashboardSignal`, `OperationRunLinks`, `RequiredPermissionsLinks`, `TenantRequiredPermissionsViewModelBuilder`, tenant review/evidence/review-pack resources, shared badge rendering, and capability helpers
|
|
||||||
<!-- MANUAL ADDITIONS START -->
|
<!-- MANUAL ADDITIONS START -->
|
||||||
|
|
||||||
### Pre-production compatibility check
|
### Pre-production compatibility check
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
# Specification Quality Checklist: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
**Purpose**: Validate package completeness, boundedness, and readiness before implementation
|
||||||
|
**Created**: 2026-05-07
|
||||||
|
**Feature**: [spec.md](../spec.md)
|
||||||
|
|
||||||
|
## Content Quality
|
||||||
|
|
||||||
|
- [x] The package stays on reserved slot `280` and does not silently absorb Specs `281`-`287`.
|
||||||
|
- [x] The package remains product- and behavior-oriented instead of reading like a raw route diff.
|
||||||
|
- [x] The plan is grounded in verified repo seams: `AdminPanelProvider`, `TenantPanelProvider`, `ChooseWorkspace`, `ChooseTenant`, `ManagedTenantsLanding`, `WorkspaceOverview`, `TenantDashboard`, `Monitoring\Operations`, `WorkspaceRedirectResolver`, middleware, route classifiers, and shared navigation builders.
|
||||||
|
- [x] Workspace and managed-environment dashboards are explicitly reused rather than replaced.
|
||||||
|
- [x] No compatibility routes, redirects, aliases, or dual-panel end state remain in scope.
|
||||||
|
|
||||||
|
## Requirement Completeness
|
||||||
|
|
||||||
|
- [x] No `[NEEDS CLARIFICATION]` markers remain in `spec.md`, `plan.md`, `research.md`, `data-model.md`, or `quickstart.md`.
|
||||||
|
- [x] The package explicitly makes `Workspace` the only Filament tenant for operator routing.
|
||||||
|
- [x] The package explicitly removes `/admin/t/{environment}` and `/admin/tenants/{environment}/required-permissions` instead of preserving them as compatibility surfaces.
|
||||||
|
- [x] The package explicitly removes `/admin/w/{workspace}/managed-tenants` and `/admin/operations` plus `/admin/operations/{run}` instead of preserving them as compatibility surfaces.
|
||||||
|
- [x] The package aligns on the canonical workspace-first route family for workspace dashboard, environment chooser, environment dashboard, and operations hub.
|
||||||
|
- [x] The package aligns on the workspace-scoped environment chooser as the canonical public environment-selection surface and does not preserve a second public chooser route.
|
||||||
|
- [x] The package forces touched global-search surfaces to keep valid view/edit destinations or disable search in the same slice.
|
||||||
|
- [x] Dependencies, assumptions, risks, and out-of-scope boundaries are explicit.
|
||||||
|
|
||||||
|
## Repo Truth Anchoring
|
||||||
|
|
||||||
|
- [x] The package reflects that `TenantPanelProvider` currently uses `path('admin/t')` and `tenant(ManagedEnvironment::class, slugAttribute: 'slug')`.
|
||||||
|
- [x] The package reflects that `AdminPanelProvider` already owns `/admin`, `ChooseWorkspace`, `ChooseTenant`, `WorkspaceOverview`, `TenantRequiredPermissions`, and the current operations surface.
|
||||||
|
- [x] The package reflects that `ManagedTenantsLanding` already binds a `Workspace` and lists accessible managed environments.
|
||||||
|
- [x] The package reflects that `Monitoring\Operations` already carries `managed_environment_id` filter state and navigation context, so the cutover reuses the page rather than replacing it.
|
||||||
|
- [x] The package reflects that current middleware and page categorization still special-case `/admin/t` and `/admin/tenants/{environment}`.
|
||||||
|
- [x] The package reflects that `routes/web.php` still exposes the old chooser and operations families that 280 must retire.
|
||||||
|
- [x] The package keeps provider registration in `apps/platform/bootstrap/providers.php`.
|
||||||
|
|
||||||
|
## Feature Readiness
|
||||||
|
|
||||||
|
- [x] Filament v5 and Livewire v4 expectations remain explicit across the package.
|
||||||
|
- [x] Provider registration location, global-search handling, destructive-action requirements, and asset strategy are explicit in the plan.
|
||||||
|
- [x] Routing, chooser flow, breadcrumbs, current-context seams, operations-link semantics, and legacy-route removal are all covered.
|
||||||
|
- [x] Provider extraction, artifact retargeting, RBAC redesign, copy neutralization, and quality-gate work remain explicitly deferred to Specs `281`-`287`.
|
||||||
|
- [x] The package is implementation-ready without reopening Spec `279`.
|
||||||
|
|
||||||
|
## Artifact Alignment
|
||||||
|
|
||||||
|
- [x] `research.md` records the same routing, chooser, operations, and search decisions reflected in `plan.md`.
|
||||||
|
- [x] `data-model.md` models the same workspace dashboard, environment chooser, environment dashboard, operations, and searchable-destination contracts reflected in the plan and contract.
|
||||||
|
- [x] `quickstart.md` uses the same bounded reviewer flow and proof commands as `plan.md`.
|
||||||
|
- [x] `contracts/workspace-tenancy-environment-routing.logical.openapi.yaml` models the canonical workspace-first GET routes and explicit 404 legacy removals.
|
||||||
|
- [x] Canonical proof commands match across `spec.md`, `plan.md`, and `quickstart.md` and cover routes, provider registration, and every retired public route family.
|
||||||
|
|
||||||
|
## Test Governance
|
||||||
|
|
||||||
|
- [x] Planned proof stays bounded to feature coverage, one browser smoke, and explicit grep/guard checks.
|
||||||
|
- [x] No heavy-governance or broad browser family is introduced.
|
||||||
|
- [x] Workspace-plus-managed-environment fixture/context cost is acknowledged instead of hidden.
|
||||||
|
- [x] Reviewer handoff includes explicit minimal validation commands for route ownership, tenancy change, and legacy-route removal.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Reviewed against `.specify/memory/constitution.md`, the Filament v5 documentation results captured for panel configuration, global search, and page/resource testing, `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, `apps/platform/app/Filament/Pages/ChooseWorkspace.php`, `apps/platform/app/Filament/Pages/ChooseTenant.php`, `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantsLanding.php`, `apps/platform/app/Filament/Pages/WorkspaceOverview.php`, `apps/platform/app/Filament/Pages/TenantDashboard.php`, `apps/platform/app/Filament/Pages/TenantRequiredPermissions.php`, `apps/platform/app/Filament/Pages/Monitoring/Operations.php`, `apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php`, `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php`, `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`, `apps/platform/app/Support/Tenants/TenantPageCategory.php`, `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php`, `apps/platform/app/Support/OperationRunLinks.php`, `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php`, `apps/platform/app/Filament/Resources/TenantResource.php`, `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php`, `apps/platform/routes/web.php`, and `apps/platform/bootstrap/providers.php` on 2026-05-07.
|
||||||
|
- No application implementation was performed while preparing this package.
|
||||||
|
|
||||||
|
## Review Outcome
|
||||||
|
|
||||||
|
- **Outcome class**: `implementation-ready`
|
||||||
|
- **Workflow outcome**: `keep`
|
||||||
|
- **Test-governance outcome**: `keep`
|
||||||
|
- **Reason**: The package closes the temporary `/admin/t` shell using the existing workspace and environment surfaces, converges on one workspace-first route language, and leaves the deferred provider/artifact/RBAC/copy/quality-gate work explicitly to Specs `281`-`287`.
|
||||||
|
- **Workflow result**: Ready for implementation planning or execution as the second reserved cutover slice.
|
||||||
@ -0,0 +1,422 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: TenantPilot Admin - Workspace Tenancy & Environment Routing Cutover (Conceptual)
|
||||||
|
version: 0.1.0
|
||||||
|
description: |
|
||||||
|
Conceptual GET-route contract for Spec 280.
|
||||||
|
|
||||||
|
This package removes the temporary `/admin/t/{environment}` public shell,
|
||||||
|
makes `Workspace` the only Filament tenant for operator admin routing, and
|
||||||
|
moves managed-environment work under workspace-first routes rooted at
|
||||||
|
`/admin/workspaces/{workspace}`.
|
||||||
|
servers:
|
||||||
|
- url: /admin
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
summary: Resolve the operator admin entrypoint
|
||||||
|
description: |
|
||||||
|
Direct `/admin` requests resolve to workspace selection or the active
|
||||||
|
workspace dashboard. `/admin` is not a second canonical managed-
|
||||||
|
environment dashboard route after this cutover.
|
||||||
|
responses:
|
||||||
|
'302':
|
||||||
|
description: Redirect to workspace selection or the active workspace dashboard
|
||||||
|
headers:
|
||||||
|
Location:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-outcomes:
|
||||||
|
- route: /admin/choose-workspace
|
||||||
|
when: no valid workspace context exists
|
||||||
|
- route: /admin/workspaces/{workspace}
|
||||||
|
when: a valid workspace context exists
|
||||||
|
/workspaces/{workspace}:
|
||||||
|
get:
|
||||||
|
summary: Open the canonical workspace dashboard
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Workspace dashboard rendered
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-view-model:
|
||||||
|
$ref: '#/components/schemas/WorkspaceDashboardView'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-capability-rules:
|
||||||
|
workspace_membership: required
|
||||||
|
/workspaces/{workspace}/environments:
|
||||||
|
get:
|
||||||
|
summary: Open the canonical workspace-scoped environment chooser
|
||||||
|
description: |
|
||||||
|
Reuses the existing workspace-bound environment-selection surface and
|
||||||
|
replaces any need for a second public chooser route.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Environment chooser rendered
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-view-model:
|
||||||
|
$ref: '#/components/schemas/EnvironmentChooserView'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-capability-rules:
|
||||||
|
workspace_membership: required
|
||||||
|
/workspaces/{workspace}/environments/{environment}:
|
||||||
|
get:
|
||||||
|
summary: Open the canonical managed-environment dashboard
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
- $ref: '#/components/parameters/ManagedEnvironmentIdentifier'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Managed-environment dashboard rendered
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-view-model:
|
||||||
|
$ref: '#/components/schemas/ManagedEnvironmentDashboardView'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-capability-rules:
|
||||||
|
workspace_membership: required
|
||||||
|
managed_environment_membership: required
|
||||||
|
/workspaces/{workspace}/environments/{environment}/required-permissions:
|
||||||
|
get:
|
||||||
|
summary: Open required permissions under workspace-first environment routing
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
- $ref: '#/components/parameters/ManagedEnvironmentIdentifier'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Required permissions page rendered
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-view-model:
|
||||||
|
$ref: '#/components/schemas/ManagedEnvironmentPageView'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-capability-rules:
|
||||||
|
workspace_membership: required
|
||||||
|
managed_environment_membership: required
|
||||||
|
/workspaces/{workspace}/operations:
|
||||||
|
get:
|
||||||
|
summary: Open the canonical workspace operations hub
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
- $ref: '#/components/parameters/ManagedEnvironmentFilter'
|
||||||
|
- $ref: '#/components/parameters/TenantScope'
|
||||||
|
- $ref: '#/components/parameters/ActiveTab'
|
||||||
|
- $ref: '#/components/parameters/ProblemClass'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Workspace operations hub rendered
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-view-model:
|
||||||
|
$ref: '#/components/schemas/WorkspaceOperationsView'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-capability-rules:
|
||||||
|
workspace_membership: required
|
||||||
|
managed_environment_membership: conditional-when-filtered
|
||||||
|
/workspaces/{workspace}/operations/{run}:
|
||||||
|
get:
|
||||||
|
summary: Open the canonical operation run detail viewer
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
- $ref: '#/components/parameters/OperationRunIdentifier'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Operation run detail rendered
|
||||||
|
content:
|
||||||
|
text/html:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
x-logical-view-model:
|
||||||
|
$ref: '#/components/schemas/OperationRunDetailView'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-capability-rules:
|
||||||
|
workspace_membership: required
|
||||||
|
run_visibility: tenant-safe-within-workspace
|
||||||
|
/w/{workspace}/managed-tenants:
|
||||||
|
get:
|
||||||
|
summary: Legacy workspace-scoped managed-tenants chooser route removed
|
||||||
|
description: |
|
||||||
|
Spec 280 removes `/admin/w/{workspace}/managed-tenants` and replaces it
|
||||||
|
with the canonical workspace-first environment chooser at
|
||||||
|
`/admin/workspaces/{workspace}/environments`. No redirect or
|
||||||
|
compatibility alias survives.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/WorkspaceIdentifier'
|
||||||
|
responses:
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-removed-by-spec: 280
|
||||||
|
/operations:
|
||||||
|
get:
|
||||||
|
summary: Legacy workspace operations collection route removed
|
||||||
|
description: |
|
||||||
|
Spec 280 removes `/admin/operations` in favor of
|
||||||
|
`/admin/workspaces/{workspace}/operations`. No redirect or compatibility
|
||||||
|
alias survives.
|
||||||
|
responses:
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-removed-by-spec: 280
|
||||||
|
/operations/{run}:
|
||||||
|
get:
|
||||||
|
summary: Legacy workspace operations detail route removed
|
||||||
|
description: |
|
||||||
|
Spec 280 removes `/admin/operations/{run}` in favor of
|
||||||
|
`/admin/workspaces/{workspace}/operations/{run}`. No redirect or
|
||||||
|
compatibility alias survives.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/OperationRunIdentifier'
|
||||||
|
responses:
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-removed-by-spec: 280
|
||||||
|
/t/{environment}:
|
||||||
|
get:
|
||||||
|
summary: Legacy environment panel route removed
|
||||||
|
description: |
|
||||||
|
Spec 280 removes the public `/admin/t/{environment}` route family. No
|
||||||
|
redirect or compatibility alias survives.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/ManagedEnvironmentIdentifier'
|
||||||
|
responses:
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-removed-by-spec: 280
|
||||||
|
/tenants/{environment}/required-permissions:
|
||||||
|
get:
|
||||||
|
summary: Legacy required-permissions route removed
|
||||||
|
description: |
|
||||||
|
Spec 280 removes `/admin/tenants/{environment}/required-permissions`
|
||||||
|
and replaces it with the workspace-first environment route family.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/ManagedEnvironmentIdentifier'
|
||||||
|
responses:
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
x-removed-by-spec: 280
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
WorkspaceIdentifier:
|
||||||
|
name: workspace
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
ManagedEnvironmentIdentifier:
|
||||||
|
name: environment
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
OperationRunIdentifier:
|
||||||
|
name: run
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
ManagedEnvironmentFilter:
|
||||||
|
name: managed_environment_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
description: Optional explicit environment prefilter inside the active workspace.
|
||||||
|
TenantScope:
|
||||||
|
name: tenant_scope
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [all]
|
||||||
|
ActiveTab:
|
||||||
|
name: activeTab
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
ProblemClass:
|
||||||
|
name: problemClass
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
Forbidden:
|
||||||
|
description: Actor is in scope but lacks the required capability on the workspace or managed environment.
|
||||||
|
NotFound:
|
||||||
|
description: Wrong workspace, wrong managed environment, removed legacy route, or non-member access is hidden as not found.
|
||||||
|
schemas:
|
||||||
|
WorkspaceSummary:
|
||||||
|
type: object
|
||||||
|
required: [id, name, slug]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
slug:
|
||||||
|
type: string
|
||||||
|
ManagedEnvironmentSummary:
|
||||||
|
type: object
|
||||||
|
required: [id, workspace_id, slug, name, lifecycle_status]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
workspace_id:
|
||||||
|
type: integer
|
||||||
|
slug:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
lifecycle_status:
|
||||||
|
type: string
|
||||||
|
BreadcrumbSegment:
|
||||||
|
type: object
|
||||||
|
required: [label, url]
|
||||||
|
properties:
|
||||||
|
label:
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
WorkspaceDashboardView:
|
||||||
|
type: object
|
||||||
|
required: [workspace, overview_payload, environment_chooser_url, operations_url]
|
||||||
|
properties:
|
||||||
|
workspace:
|
||||||
|
$ref: '#/components/schemas/WorkspaceSummary'
|
||||||
|
overview_payload:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
environment_chooser_url:
|
||||||
|
type: string
|
||||||
|
operations_url:
|
||||||
|
type: string
|
||||||
|
EnvironmentChooserItem:
|
||||||
|
type: object
|
||||||
|
required: [id, slug, name, lifecycle_status, open_url]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
slug:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
lifecycle_status:
|
||||||
|
type: string
|
||||||
|
posture_hint:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
open_url:
|
||||||
|
type: string
|
||||||
|
EnvironmentChooserView:
|
||||||
|
type: object
|
||||||
|
required: [workspace, environments]
|
||||||
|
properties:
|
||||||
|
workspace:
|
||||||
|
$ref: '#/components/schemas/WorkspaceSummary'
|
||||||
|
environments:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/EnvironmentChooserItem'
|
||||||
|
switch_workspace_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
ManagedEnvironmentDashboardView:
|
||||||
|
type: object
|
||||||
|
required: [workspace, managed_environment, breadcrumbs, operations_url]
|
||||||
|
properties:
|
||||||
|
workspace:
|
||||||
|
$ref: '#/components/schemas/WorkspaceSummary'
|
||||||
|
managed_environment:
|
||||||
|
$ref: '#/components/schemas/ManagedEnvironmentSummary'
|
||||||
|
breadcrumbs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/BreadcrumbSegment'
|
||||||
|
dashboard_summary:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
operations_url:
|
||||||
|
type: string
|
||||||
|
ManagedEnvironmentPageView:
|
||||||
|
type: object
|
||||||
|
required: [workspace, managed_environment, breadcrumbs, page_label, operations_url]
|
||||||
|
properties:
|
||||||
|
workspace:
|
||||||
|
$ref: '#/components/schemas/WorkspaceSummary'
|
||||||
|
managed_environment:
|
||||||
|
$ref: '#/components/schemas/ManagedEnvironmentSummary'
|
||||||
|
breadcrumbs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/BreadcrumbSegment'
|
||||||
|
page_label:
|
||||||
|
type: string
|
||||||
|
operations_url:
|
||||||
|
type: string
|
||||||
|
WorkspaceOperationsView:
|
||||||
|
type: object
|
||||||
|
required: [workspace]
|
||||||
|
properties:
|
||||||
|
workspace:
|
||||||
|
$ref: '#/components/schemas/WorkspaceSummary'
|
||||||
|
managed_environment_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
active_tab:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
problem_class:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
nav_context:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
additionalProperties: true
|
||||||
|
OperationRunDetailView:
|
||||||
|
type: object
|
||||||
|
required: [workspace, run_id]
|
||||||
|
properties:
|
||||||
|
workspace:
|
||||||
|
$ref: '#/components/schemas/WorkspaceSummary'
|
||||||
|
managed_environment:
|
||||||
|
$ref: '#/components/schemas/ManagedEnvironmentSummary'
|
||||||
|
nullable: true
|
||||||
|
run_id:
|
||||||
|
type: integer
|
||||||
|
back_link_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
192
specs/280-workspace-tenancy-environment-routing/data-model.md
Normal file
192
specs/280-workspace-tenancy-environment-routing/data-model.md
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
# Data Model: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
**Date**: 2026-05-07
|
||||||
|
**Branch**: `280-workspace-tenancy-environment-routing`
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This slice introduces no new persistence. It replaces a temporary routing and panel-tenancy shell with a workspace-first runtime contract built on existing `Workspace` and `ManagedEnvironment` truth. The data model for this package is therefore a set of derived route, view-model, and navigation contracts that implementation must keep aligned across pages, middleware, and shared link builders.
|
||||||
|
|
||||||
|
## Persisted Truth Unchanged
|
||||||
|
|
||||||
|
- `Workspace` remains the workspace-owned root context.
|
||||||
|
- `ManagedEnvironment` remains the environment-scoped managed target inside one workspace.
|
||||||
|
- No table ownership, foreign-key ownership, artifact ownership, or role/capability family changes are introduced in this slice.
|
||||||
|
- Provider registration remains in `apps/platform/bootstrap/providers.php`; no new provider or asset persistence is introduced.
|
||||||
|
|
||||||
|
## Derived Runtime Contracts
|
||||||
|
|
||||||
|
### 1. Workspace Admin Context
|
||||||
|
|
||||||
|
**Persistence**: derived from route parameters plus `WorkspaceContext` session state
|
||||||
|
**Owner**: workspace-first admin shell
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `workspace_id` | int | yes | Canonical workspace scope for the current request |
|
||||||
|
| `workspace_slug` | string | yes | Route-safe workspace identifier |
|
||||||
|
| `workspace_name` | string | yes | Operator-visible workspace label |
|
||||||
|
| `entry_mode` | string | yes | `chooser` or `dashboard` |
|
||||||
|
| `remembered_environment_id` | int | no | Derived only when the remembered environment belongs to the same workspace |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- `/admin` resolves through this contract to either workspace selection or the canonical workspace dashboard.
|
||||||
|
- Non-member workspace access remains `404`.
|
||||||
|
- Switching workspaces invalidates any remembered environment that belongs to another workspace before any environment page renders.
|
||||||
|
|
||||||
|
### 2. Workspace Dashboard View
|
||||||
|
|
||||||
|
**Persistence**: none, derived from `WorkspaceOverviewBuilder`
|
||||||
|
**Owner**: `WorkspaceOverview`
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `workspace` | object | yes | Workspace summary for the active workspace |
|
||||||
|
| `overview_payload` | array | yes | Existing builder output for signal cards, summaries, and quick actions |
|
||||||
|
| `environment_chooser_url` | string | yes | Canonical workspace-scoped environment chooser route |
|
||||||
|
| `operations_url` | string | yes | Canonical workspace operations route |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- The workspace dashboard is the primary decision surface after workspace selection.
|
||||||
|
- It must not silently behave like a second environment dashboard.
|
||||||
|
- It reuses current builder output rather than introducing a new summary system.
|
||||||
|
|
||||||
|
### 3. Environment Chooser View
|
||||||
|
|
||||||
|
**Persistence**: none, derived from `ManagedTenantsLanding` data plus existing `ChooseTenant` selection logic
|
||||||
|
**Owner**: workspace-scoped environment chooser surface
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `workspace` | object | yes | Active workspace summary |
|
||||||
|
| `environments` | list<object> | yes | Selectable managed environments within the workspace |
|
||||||
|
| `open_environment_url` | string | yes | Workspace-first environment dashboard URL per row/card |
|
||||||
|
| `switch_workspace_url` | string | no | Secondary escape hatch back to workspace choice |
|
||||||
|
|
||||||
|
**Environment item fields**:
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `id` | int | yes | ManagedEnvironment key |
|
||||||
|
| `slug` | string | yes | Route-safe environment identifier |
|
||||||
|
| `name` | string | yes | Operator-visible environment label |
|
||||||
|
| `lifecycle_status` | string | yes | Existing operability/lifecycle status |
|
||||||
|
| `posture_hint` | string | no | Existing discoverability/operability summary only |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- Only environments belonging to the active workspace and accessible to the actor appear.
|
||||||
|
- Archived or otherwise non-selectable environments do not appear and do not resolve.
|
||||||
|
- `ChooseTenant` may survive as an implementation seam, but not as a second public chooser contract.
|
||||||
|
|
||||||
|
### 4. Managed Environment Page Context
|
||||||
|
|
||||||
|
**Persistence**: derived from workspace-first route parameters plus `WorkspaceContext`
|
||||||
|
**Owner**: all environment-scoped pages touched by this slice
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `workspace_id` | int | yes | Outer scope boundary |
|
||||||
|
| `workspace_slug` | string | yes | Outer scope route key |
|
||||||
|
| `managed_environment_id` | int | yes | Nested environment scope |
|
||||||
|
| `managed_environment_slug` | string | yes | Nested environment route key |
|
||||||
|
| `managed_environment_name` | string | yes | Operator-visible environment label |
|
||||||
|
| `breadcrumb_segments` | list<object> | yes | `Workspace -> Managed Environment -> page` |
|
||||||
|
| `page_category` | string | yes | Derived route classification after legacy families are removed |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- The `{environment}` parameter must belong to the `{workspace}` parameter or the request is `404`.
|
||||||
|
- Breadcrumb and context-bar ordering must always be `Workspace -> Managed Environment -> page`.
|
||||||
|
- No touched page may keep `/admin/t` or `/admin/tenants/{environment}` as a valid public route.
|
||||||
|
|
||||||
|
### 5. Managed Environment Dashboard View
|
||||||
|
|
||||||
|
**Persistence**: none, derived from `TenantDashboardSummaryBuilder` and current dashboard widgets
|
||||||
|
**Owner**: `TenantDashboard`
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `workspace` | object | yes | Outer workspace summary for breadcrumb/context |
|
||||||
|
| `managed_environment` | object | yes | Active environment summary |
|
||||||
|
| `dashboard_summary` | array | yes | Existing summary-builder payload |
|
||||||
|
| `primary_follow_up_url` | string | no | Existing recommended-action destination |
|
||||||
|
| `operations_url` | string | yes | Canonical workspace operations route with explicit environment filter |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- The surface remains the canonical environment dashboard.
|
||||||
|
- Existing widget and header-action ownership stays intact.
|
||||||
|
- Operations navigation from this surface always enters the workspace operations family with explicit environment context.
|
||||||
|
|
||||||
|
### 6. Workspace Operations Scope
|
||||||
|
|
||||||
|
**Persistence**: derived route/query/session state already modeled by `Monitoring\Operations`
|
||||||
|
**Owner**: canonical workspace operations hub and detail viewer
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `workspace_id` | int | yes | Required workspace scope |
|
||||||
|
| `managed_environment_id` | int | no | Optional environment prefilter |
|
||||||
|
| `tenant_scope` | string | no | Existing workspace-wide versus narrowed state flag |
|
||||||
|
| `active_tab` | string | no | Existing operations tab state |
|
||||||
|
| `problem_class` | string | no | Existing scoped deep-link filter |
|
||||||
|
| `nav_context` | object | no | Back-link label and URL for environment return flow |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- The collection route is `/admin/workspaces/{workspace}/operations`.
|
||||||
|
- The detail route is `/admin/workspaces/{workspace}/operations/{run}`.
|
||||||
|
- Explicit environment filters outside the current workspace or actor entitlement are `404`.
|
||||||
|
- Stale remembered environment filters may be discarded, but explicit hostile filters may not widen scope silently.
|
||||||
|
|
||||||
|
### 7. Searchable Destination Contract
|
||||||
|
|
||||||
|
**Persistence**: none, derived from Filament resource configuration
|
||||||
|
**Owner**: touched globally searchable resources
|
||||||
|
|
||||||
|
| Field | Type | Required | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `resource_key` | string | yes | `workspace` or `managed_environment` |
|
||||||
|
| `record_title_attribute` | string | yes | Existing Filament record title attribute |
|
||||||
|
| `destination_kind` | string | yes | `view` or `edit` |
|
||||||
|
| `destination_route` | string | yes | Valid route after the workspace-first cutover |
|
||||||
|
| `global_search_enabled` | bool | yes | Search remains enabled only if destination stays valid |
|
||||||
|
|
||||||
|
**Rules**:
|
||||||
|
|
||||||
|
- `WorkspaceResource` remains searchable only if its view/edit destination stays valid.
|
||||||
|
- `TenantResource` remains searchable only if its view/edit destination stays valid after the environment route move.
|
||||||
|
- Touched surfaces that cannot satisfy Filament’s view/edit rule must be disabled from global search in the same slice.
|
||||||
|
|
||||||
|
## Route Invariants
|
||||||
|
|
||||||
|
- Public operator route families after the cutover are rooted at `/admin/workspaces/{workspace}`.
|
||||||
|
- `/admin/t/{environment}` is removed, not redirected.
|
||||||
|
- `/admin/tenants/{environment}/required-permissions` is removed, not redirected.
|
||||||
|
- `/admin/w/{workspace}/managed-tenants` is removed, not redirected.
|
||||||
|
- `/admin/operations` and `/admin/operations/{run}` are removed, not redirected.
|
||||||
|
- Shared builders and helpers must stop emitting `panel: 'tenant'` for touched operator destinations.
|
||||||
|
- `Workspace` is the only Filament tenant for operator routing; `ManagedEnvironment` is nested route context only.
|
||||||
|
|
||||||
|
## State Transitions
|
||||||
|
|
||||||
|
1. `NoWorkspaceSelected` -> `ChooseWorkspace`
|
||||||
|
2. `WorkspaceSelected` -> `WorkspaceDashboard`
|
||||||
|
3. `WorkspaceDashboard` -> `EnvironmentChooser`
|
||||||
|
4. `EnvironmentChooser` -> `ManagedEnvironmentDashboard`
|
||||||
|
5. `ManagedEnvironmentDashboard` -> `EnvironmentScopedPage` or `WorkspaceOperationsScope`
|
||||||
|
6. `WorkspaceSwitch` -> clear cross-workspace environment context before rendering the new workspace dashboard or chooser
|
||||||
|
7. `LegacyEnvironmentRouteRequested` -> `NotFound`
|
||||||
|
8. `LegacyWorkspaceChooserRequested` -> `NotFound`
|
||||||
|
9. `LegacyOperationsRouteRequested` -> `NotFound`
|
||||||
|
|
||||||
|
## Deferred Boundaries
|
||||||
|
|
||||||
|
- No new provider connection/profile entity or abstraction is introduced here.
|
||||||
|
- No artifact ownership retargeting is introduced here.
|
||||||
|
- No RBAC role or capability family change is introduced here.
|
||||||
|
- No UI copy neutralization or localization rewrite is introduced here.
|
||||||
|
- No quality-gate pack or no-legacy automation beyond the local grep/guard proof is introduced here.
|
||||||
294
specs/280-workspace-tenancy-environment-routing/plan.md
Normal file
294
specs/280-workspace-tenancy-environment-routing/plan.md
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
# Implementation Plan: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
**Branch**: `280-workspace-tenancy-environment-routing` | **Date**: 2026-05-07 | **Spec**: [spec.md](./spec.md)
|
||||||
|
**Input**: Feature specification from `specs/280-workspace-tenancy-environment-routing/spec.md`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Prepare the second reserved workspace-first cutover slice that removes the temporary `/admin/t` operator shell, makes `Workspace` the only Filament tenant, and moves environment work under the canonical workspace-first route family rooted at `/admin/workspaces/{workspace}/environments/{environment}`. The narrow implementation path reuses the existing `AdminPanelProvider`, `WorkspaceOverview`, `ManagedTenantsLanding` and `ChooseTenant` chooser flow, `TenantDashboard`, `Monitoring\Operations`, `WorkspaceOverviewBuilder`, `TenantDashboardSummaryBuilder`, `OperationRunLinks`, and related navigation/context helpers while explicitly deferring Specs `281` through `287`.
|
||||||
|
|
||||||
|
This plan stays intentionally bounded. Filament remains v5 on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, no new persistence or provider extraction is introduced, no compatibility route or second panel survives, no artifact retargeting or RBAC redesign is absorbed, and no broad quality-gate or copy-neutralization work moves into this slice.
|
||||||
|
|
||||||
|
## Inherited Baseline / Explicit Delta
|
||||||
|
|
||||||
|
### Inherited baseline
|
||||||
|
|
||||||
|
- Spec `279` already replaced the core managed-target noun with `ManagedEnvironment` and kept the `/admin/t/{environment}` shell as a temporary bridge.
|
||||||
|
- `apps/platform/app/Providers/Filament/AdminPanelProvider.php` currently owns the default `admin` panel, `/admin`, `ChooseWorkspace`, `ChooseTenant`, `WorkspaceOverview`, `TenantRequiredPermissions`, and the canonical workspace-scoped operations surface.
|
||||||
|
- `apps/platform/app/Providers/Filament/TenantPanelProvider.php` still owns `id('tenant')`, `path('admin/t')`, and `tenant(ManagedEnvironment::class, slugAttribute: 'slug')`, so the operator runtime still has a second panel and second route language.
|
||||||
|
- `apps/platform/bootstrap/providers.php` still registers both `AdminPanelProvider` and `TenantPanelProvider`.
|
||||||
|
- `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php` still branches to `ChooseTenant` or `TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant)`.
|
||||||
|
- `apps/platform/app/Filament/Pages/ChooseTenant.php`, `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantsLanding.php`, `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php`, `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`, `apps/platform/app/Support/Tenants/TenantPageCategory.php`, and `apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php` still encode `/admin/t`, `/admin/tenants/{environment}`, or explicit tenant-panel assumptions.
|
||||||
|
- `apps/platform/app/Support/OperationRunLinks.php`, `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php`, and `apps/platform/app/Filament/Pages/Monitoring/Operations.php` still emit or expect tenant-panel destinations and workspace-canonical operations routes under `/admin/operations`.
|
||||||
|
- Route ownership in `apps/platform/routes/web.php` still exposes `/admin`, `/admin/operations`, `/admin/operations/{run}`, `/admin/w/{workspace}/managed-tenants`, `/admin/tenants/{tenant}/required-permissions`, and the Filament-generated `/admin/t/{tenant}/...` family at the same time.
|
||||||
|
|
||||||
|
### Explicit delta in this plan
|
||||||
|
|
||||||
|
- Collapse the operator-facing `tenant` panel into the existing `admin` panel and make `Workspace` the only Filament tenant.
|
||||||
|
- Replace the public `/admin/t/{environment}` and `/admin/tenants/{environment}/required-permissions` route families with one canonical workspace-first environment family.
|
||||||
|
- Remove the legacy chooser route `/admin/w/{workspace}/managed-tenants` and the legacy operations routes `/admin/operations` plus `/admin/operations/{run}` with no redirects, aliases, or hidden fallback readers.
|
||||||
|
- Re-home the existing workspace dashboard, environment chooser, environment dashboard, and operations hub under `/admin/workspaces/{workspace}/...` without creating replacement dashboards or new panel abstractions.
|
||||||
|
- Update middleware, route categorization, current-context resolution, breadcrumbs, context-bar signals, and page/deep-link builders as one route-contract change rather than page-by-page exceptions.
|
||||||
|
- Keep all provider extraction, governance-artifact retargeting, provider-neutral capability work, RBAC redesign, copy-neutralization, and broad no-legacy/quality-gate follow-through deferred to Specs `281` through `287`.
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
**Language/Version**: PHP 8.4.15, Laravel 12.52
|
||||||
|
**Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, existing workspace and environment authorization/context helpers, existing Filament panel providers and page/resource action-surface contracts
|
||||||
|
**Storage**: PostgreSQL, no new persistence or schema change in this slice
|
||||||
|
**Testing**: Pest feature tests, one Pest browser smoke, and focused grep/guard checks for legacy route language
|
||||||
|
**Validation Lanes**: fast-feedback, confidence, browser
|
||||||
|
**Target Platform**: Laravel monolith in `apps/platform`
|
||||||
|
**Project Type**: web application
|
||||||
|
**Performance Goals**: preserve current dashboard and operations responsiveness while changing only route ownership, panel tenancy, and context resolution; no new queue, polling, or asset load path
|
||||||
|
**Constraints**: no compatibility redirects or aliases, no second Filament tenant at the environment level, no new persistence, no provider extraction, no artifact retargeting, no RBAC redesign, no broad copy neutralization, no quality-gate pack work, provider registration stays in `apps/platform/bootstrap/providers.php`, and Filament remains v5 on Livewire v4
|
||||||
|
**Scale/Scope**: one operator-panel tenancy flip, one canonical workspace-first environment route family, one workspace-first operations route family, and one bounded set of chooser/middleware/link/breadcrumb/current-context updates
|
||||||
|
|
||||||
|
## Likely Affected Repo Surfaces
|
||||||
|
|
||||||
|
- `apps/platform/app/Providers/Filament/AdminPanelProvider.php`
|
||||||
|
- `apps/platform/app/Providers/Filament/TenantPanelProvider.php`
|
||||||
|
- `apps/platform/bootstrap/providers.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/ChooseWorkspace.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/ChooseTenant.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantsLanding.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/WorkspaceOverview.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/TenantDashboard.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/TenantRequiredPermissions.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/Monitoring/Operations.php`
|
||||||
|
- `apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php`
|
||||||
|
- `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php`
|
||||||
|
- `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`
|
||||||
|
- `apps/platform/app/Support/Tenants/TenantPageCategory.php`
|
||||||
|
- `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php`
|
||||||
|
- `apps/platform/app/Support/OperationRunLinks.php`
|
||||||
|
- `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/TenantResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php`
|
||||||
|
- `apps/platform/routes/web.php`
|
||||||
|
- feature, browser, and guard coverage under `apps/platform/tests/Feature`, `apps/platform/tests/Browser`, and existing grep/guard patterns
|
||||||
|
|
||||||
|
## Filament v5 / Panel Notes
|
||||||
|
|
||||||
|
- **Livewire v4.0+ compliance**: this slice keeps Filament v5 on Livewire v4 and changes only panel tenancy, route ownership, and current-context handling.
|
||||||
|
- **Provider registration location**: any provider registration change stays in `apps/platform/bootstrap/providers.php`; nothing moves to `bootstrap/app.php`.
|
||||||
|
- **Global search rule**: touched searchable resources must keep a valid view or edit destination or be disabled from global search in the same implementation slice. Current repo truth shows `WorkspaceResource` and `TenantResource` as the search-eligible surfaces that need explicit review.
|
||||||
|
- **Destructive actions**: no new destructive actions are introduced. Any touched existing destructive actions must preserve `->requiresConfirmation()` plus current server-side authorization.
|
||||||
|
- **Asset strategy**: no new asset registration or deployment step is planned. Existing admin theme/asset handling remains unchanged.
|
||||||
|
|
||||||
|
## Workspace-first Routing Fit
|
||||||
|
|
||||||
|
- Reuse the existing `admin` panel instead of creating a third panel or a nested-environment Filament tenancy model.
|
||||||
|
- Treat `WorkspaceOverview` as the canonical workspace dashboard under `/admin/workspaces/{workspace}` rather than leaving `/admin` as a second environment-like dashboard route.
|
||||||
|
- Treat the current chooser stack as two reuse candidates: `ChooseWorkspace` remains the workspace chooser, while `ManagedTenantsLanding` plus `ChooseTenant` form the environment-selection seams to retarget to `/admin/workspaces/{workspace}/environments`.
|
||||||
|
- Treat `TenantDashboard` as the surviving managed-environment dashboard surface, but move its route ownership and breadcrumb/context shell under the workspace-first family.
|
||||||
|
- Treat `Monitoring\Operations` as the only operations hub, but move its public collection and detail routes under the workspace-first family and preserve explicit `managed_environment_id` filter context when entered from environment pages.
|
||||||
|
- Update `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `TenantPageCategory`, and `ResolvesPanelTenantContext` together so the same route family defines tenant-bound versus workspace-scoped behavior.
|
||||||
|
- Remove `/admin/t` and `/admin/tenants/{environment}/required-permissions` outright in line with LEAN-001 instead of adding redirects or dual readers.
|
||||||
|
- Remove `/admin/w/{workspace}/managed-tenants` and `/admin/operations` plus `/admin/operations/{run}` outright in line with LEAN-001 instead of leaving a second chooser or operations route language behind.
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Plan
|
||||||
|
|
||||||
|
- **Guardrail scope**: changed surfaces
|
||||||
|
- **Native vs custom classification summary**: native Filament
|
||||||
|
- **Shared-family relevance**: workspace dashboard, environment chooser, managed-environment dashboard, operations hub, related navigation builders, breadcrumbs, context bars
|
||||||
|
- **State layers in scope**: shell, page, detail, URL-query
|
||||||
|
- **Audience modes in scope**: operator-MSP, support-platform
|
||||||
|
- **Decision/diagnostic/raw hierarchy plan**: decision-first on workspace dashboard and environment chooser, diagnostics-second on environment dashboard and operations detail, raw/support evidence remains on downstream pages only
|
||||||
|
- **Raw/support gating plan**: unchanged capability-gated downstream evidence; this slice changes route truth and scope signals, not evidence disclosure
|
||||||
|
- **One-primary-action / duplicate-truth control**: workspace dashboard stays the workspace decision surface, environment chooser stays the one dominant selection step, and environment dashboard keeps one primary follow-up action while breadcrumbs/context bars carry scope once instead of duplicating summaries
|
||||||
|
- **Handling modes by drift class or surface**: review-mandatory
|
||||||
|
- **Repository-signal treatment**: review-mandatory until grep/guard proof shows `/admin/t` and `panel: 'tenant'` are gone from touched operator surfaces
|
||||||
|
- **Special surface test profiles**: standard-native-filament, global-context-shell, monitoring-state-page
|
||||||
|
- **Required tests or manual smoke**: functional-core, state-contract, manual-smoke
|
||||||
|
- **Exception path and spread control**: none; this slice removes the prior shell exception rather than introducing a new one
|
||||||
|
- **Active feature PR close-out entry**: Guardrail
|
||||||
|
|
||||||
|
## Shared Pattern & System Fit
|
||||||
|
|
||||||
|
- **Cross-cutting feature marker**: yes
|
||||||
|
- **Systems touched**: Filament panel providers, chooser pages, workspace dashboard, environment dashboard, operations page, context-bar/navigation builders, middleware/current-context helpers, route classifiers, global-search destinations
|
||||||
|
- **Shared abstractions reused**: `WorkspaceOverviewBuilder`, `TenantDashboardSummaryBuilder`, `WorkspaceContext`, `WorkspaceRedirectResolver`, `OperationRunLinks`, `RelatedNavigationResolver`, existing Filament page/resource action-surface declarations
|
||||||
|
- **New abstraction introduced? why?**: none planned; this slice should replace temporary routing/panel ownership in place
|
||||||
|
- **Why the existing abstraction was sufficient or insufficient**: existing builders and dashboards already own the correct operator truth, but the surrounding panel and route shell still speak a temporary environment-panel language that must be removed
|
||||||
|
- **Bounded deviation / spread control**: none; implementation must converge on the shared builders and one route language rather than growing another local escape hatch
|
||||||
|
|
||||||
|
## OperationRun UX Impact
|
||||||
|
|
||||||
|
- **Touches OperationRun start/completion/link UX?**: yes, shared link semantics only
|
||||||
|
- **Central contract reused**: `OperationRunLinks`, `RelatedNavigationResolver`, `Monitoring\Operations`, `CanonicalNavigationContext`
|
||||||
|
- **Delegated UX behaviors**: workspace-safe URL resolution for the operations collection/detail routes, explicit environment prefiltering via `managed_environment_id`, and environment-to-operations back-link context
|
||||||
|
- **Surface-owned behavior kept local**: environment dashboards and environment-scoped pages supply only the filter context and back-navigation label for the existing operations surface
|
||||||
|
- **Queued DB-notification policy**: `N/A`
|
||||||
|
- **Terminal notification path**: `N/A`
|
||||||
|
- **Exception path**: none
|
||||||
|
|
||||||
|
## Provider Boundary & Portability Fit
|
||||||
|
|
||||||
|
- **Shared provider/platform boundary touched?**: yes
|
||||||
|
- **Provider-owned seams**: provider-specific copy or metadata left on untouched classes such as `TenantResource`, provider connection details, Microsoft-specific profile/permission semantics deferred to later specs
|
||||||
|
- **Platform-core seams**: panel tenancy, workspace-first route nouns, chooser wording, breadcrumb/context hierarchy, current-context resolution, workspace/environment-safe deep-link builders
|
||||||
|
- **Neutral platform terms / contracts preserved**: `workspace`, `managed environment`, `environment`, `operations`, `findings`, `backup sets`, `evidence`, `required permissions`
|
||||||
|
- **Retained provider-specific semantics and why**: untouched provider-owned class names or copy may still use `tenant` until Specs `281` and `286`; this slice only makes the public routing and context contract truthful now
|
||||||
|
- **Bounded extraction or follow-up path**: Specs `281`, `282`, `285`, `286`, and `287`
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
*GATE: Must pass before implementation begins and again after the design artifacts are complete.*
|
||||||
|
|
||||||
|
- Inventory-first / snapshot truth: PASS. This slice changes routing, tenancy, and navigation only; inventory and snapshot semantics remain untouched.
|
||||||
|
- Read/write separation: PASS. No new write workflow or remote-call behavior is introduced.
|
||||||
|
- Graph contract path: PASS. No Graph call, endpoint, or contract-registry work is introduced.
|
||||||
|
- Deterministic capabilities: PASS. Current capability derivation remains; the slice only changes route and panel ownership around existing checks.
|
||||||
|
- RBAC-UX plane separation: PASS. `/admin` and `/system` remain separate.
|
||||||
|
- Workspace isolation: PASS. Workspace membership remains the first route boundary and must stay deny-as-not-found for non-members.
|
||||||
|
- Managed-environment isolation: PASS. Wrong-workspace or inaccessible environment resolution remains `404`, including explicit hostile filter hints.
|
||||||
|
- Destructive action discipline: PASS by non-expansion. Touched destructive actions keep existing `->requiresConfirmation()` and server-side authorization.
|
||||||
|
- Global search safety: PASS with implementation condition. `WorkspaceResource` and `TenantResource` must keep valid view/edit destinations or be disabled from global search.
|
||||||
|
- OperationRun / Ops-UX: PASS. The slice reuses the shared operations hub and changes only route/link semantics.
|
||||||
|
- Data minimization: PASS. No new data or provider-specific persistence is introduced.
|
||||||
|
- Test governance: PASS. Proof stays bounded to focused feature coverage, one browser smoke, and grep/guard checks.
|
||||||
|
- Proportionality / no premature abstraction: PASS. The slice removes a temporary second panel and route family instead of adding framework machinery.
|
||||||
|
- Persisted truth / behavioral state: PASS. No new persistence, state family, DTO layer, taxonomy, or abstraction is introduced.
|
||||||
|
- UI semantics / shared pattern first / Filament-native UI: PASS. Existing native Filament surfaces and shared builders remain the first path.
|
||||||
|
- Provider boundary: PASS. The plan removes provider-shaped routing from platform-core navigation instead of deepening it.
|
||||||
|
|
||||||
|
**Gate evaluation**: PASS.
|
||||||
|
|
||||||
|
**Post-design re-check**: PASS while `research.md`, `data-model.md`, `quickstart.md`, `contracts/workspace-tenancy-environment-routing.logical.openapi.yaml`, and `checklists/requirements.md` remain aligned and continue to defer Specs `281` through `287` explicitly.
|
||||||
|
|
||||||
|
## Test Governance Check
|
||||||
|
|
||||||
|
- **Test purpose / classification by changed surface**: Feature, Browser
|
||||||
|
- **Affected validation lanes**: fast-feedback, confidence, browser
|
||||||
|
- **Why this lane mix is the narrowest sufficient proof**: the cutover changes public routes, panel tenancy, chooser flow, breadcrumbs, navigation helpers, and deny-as-not-found semantics. Feature tests prove route ownership and authorization boundaries, while one browser smoke proves the surviving admin-panel flow from workspace selection to environment dashboard to workspace operations.
|
||||||
|
- **Narrowest proving command(s)**:
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php)`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php)`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail bin pint --dirty --format agent)`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/t/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/tenants/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/w/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/operations' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings "panel: 'tenant'" "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings 'TenantPanelProvider::class' "$REPO_ROOT/apps/platform/bootstrap/providers.php"`
|
||||||
|
- **Fixture / helper / factory / seed / context cost risks**: moderate because proof needs workspace membership, managed-environment membership, and remembered-context behaviors without widening helper defaults
|
||||||
|
- **Expensive defaults or shared helper growth introduced?**: no; reuse current workspace/environment helpers and keep any new fixture setup opt-in
|
||||||
|
- **Heavy-family additions, promotions, or visibility changes**: none
|
||||||
|
- **Surface-class relief / special coverage rule**: standard-native-filament relief for most pages; one browser smoke is still required because the entry flow crosses chooser, dashboard, and operations surfaces
|
||||||
|
- **Closing validation and reviewer handoff**: rerun the exact commands above, verify that `TenantPanelProvider` no longer owns public operator routing, confirm `/admin/t`, `/admin/tenants/{environment}/required-permissions`, `/admin/w/{workspace}/managed-tenants`, and `/admin/operations` plus `/admin/operations/{run}` are gone with no redirect/alias fallback, confirm `ChooseTenant`, `ManagedTenantsLanding`, and `WorkspaceRedirectResolver` open workspace-first destinations, confirm `OperationRunLinks` and `RelatedNavigationResolver` emit workspace-first operations URLs with explicit environment context, confirm `TenantPageCategory`, `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, and `ResolvesPanelTenantContext` no longer treat the removed route families as active language, confirm `WorkspaceResource` and `TenantResource` still satisfy the global-search destination rule or are disabled, confirm destructive actions touched by the move still require confirmation, and confirm no new asset or deploy step appears
|
||||||
|
- **Budget / baseline / trend follow-up**: contained feature-local increase only
|
||||||
|
- **Review-stop questions**: did a compatibility route or dual-panel fallback appear, did legacy `panel: 'tenant'` links survive, did `/admin/t`, `/admin/tenants/{environment}`, `/admin/w/{workspace}/managed-tenants`, or `/admin/operations` remain reachable, did wrong-workspace or hostile filter access stop being `404`, did the operations hub lose explicit environment context, did the slice absorb deferred Spec `281`-`287` work
|
||||||
|
- **Escalation path**: `reject-or-split` if the implementation introduces compatibility routes, provider extraction, RBAC redesign, or broad copy/quality-gate work
|
||||||
|
- **Active feature PR close-out entry**: Guardrail
|
||||||
|
- **Why no dedicated follow-up spec is needed**: the remaining adjacent work is already reserved as Specs `281` through `287`; routine routing/tenancy proof stays inside this feature
|
||||||
|
|
||||||
|
## Review Checklist Status
|
||||||
|
|
||||||
|
- **Review checklist artifact**: `checklists/requirements.md`
|
||||||
|
- **Review outcome class**: `implementation-ready`
|
||||||
|
- **Workflow outcome**: `keep`
|
||||||
|
- **Test-governance outcome**: `keep`
|
||||||
|
- **Escalation rule**: if implementation keeps `/admin/t`, keeps `panel: 'tenant'` links, adds compatibility redirects, or absorbs deferred provider/artifact/RBAC/copy/quality-gate work, flip the workflow outcome to `split` or `reject-or-split`
|
||||||
|
|
||||||
|
## Rollout Considerations
|
||||||
|
|
||||||
|
- Land the panel-tenancy flip, route-family rewrite, and current-context helper update as one bounded implementation slice so workspace/environment truth changes atomically.
|
||||||
|
- Remove the temporary `tenant` panel from operator routing instead of leaving a dormant compatibility shell behind.
|
||||||
|
- Retarget chooser flow and shared link builders before polishing individual environment pages so route truth changes at the shared seams first.
|
||||||
|
- Keep direct route guards and grep checks in the same change set so `/admin/t`, `/admin/tenants/{environment}`, `/admin/w/{workspace}/managed-tenants`, `/admin/operations`, and `panel: 'tenant'` do not re-enter the codebase quietly.
|
||||||
|
|
||||||
|
## Risk Controls
|
||||||
|
|
||||||
|
- Reject any implementation that leaves `/admin/t/{environment}`, `/admin/tenants/{environment}/required-permissions`, `/admin/w/{workspace}/managed-tenants`, or `/admin/operations` plus `/admin/operations/{run}` reachable through redirects, aliases, or hidden fallback readers.
|
||||||
|
- Reject any implementation that introduces nested or dual Filament tenancy rather than making `Workspace` the only Filament tenant.
|
||||||
|
- Reject any implementation that rebuilds dashboard signal systems instead of reusing `WorkspaceOverviewBuilder` and `TenantDashboardSummaryBuilder`.
|
||||||
|
- Reject any implementation that retargets operations links page by page instead of through `OperationRunLinks` and related navigation helpers.
|
||||||
|
- Reject any implementation that widens scope into provider extraction, artifact retargeting, RBAC redesign, copy neutralization, or no-legacy quality-gate work reserved for Specs `281` through `287`.
|
||||||
|
|
||||||
|
## Research & Design Outputs
|
||||||
|
|
||||||
|
- `research.md` records the surviving panel choice, workspace-only Filament tenancy, chooser-flow reuse plan, shared operations-link strategy, middleware/current-context convergence plan, global-search treatment, and proof strategy.
|
||||||
|
- `data-model.md` captures the route-context and page-view-model contracts for workspace dashboard, environment chooser, managed-environment dashboard, workspace operations, and searchable destinations.
|
||||||
|
- `quickstart.md` gives reviewers the bounded cutover flow and exact validation commands.
|
||||||
|
- `contracts/workspace-tenancy-environment-routing.logical.openapi.yaml` captures the conceptual GET-route and removed-route contract for the workspace-first runtime.
|
||||||
|
- `checklists/requirements.md` records package readiness, boundedness, and outcome state.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/280-workspace-tenancy-environment-routing/
|
||||||
|
├── checklists/
|
||||||
|
│ └── requirements.md
|
||||||
|
├── contracts/
|
||||||
|
│ └── workspace-tenancy-environment-routing.logical.openapi.yaml
|
||||||
|
├── data-model.md
|
||||||
|
├── plan.md
|
||||||
|
├── quickstart.md
|
||||||
|
├── research.md
|
||||||
|
├── spec.md
|
||||||
|
└── tasks.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (expected implementation surfaces)
|
||||||
|
|
||||||
|
```text
|
||||||
|
apps/platform/
|
||||||
|
├── app/
|
||||||
|
│ ├── Filament/
|
||||||
|
│ │ ├── Concerns/
|
||||||
|
│ │ │ └── ResolvesPanelTenantContext.php
|
||||||
|
│ │ ├── Pages/
|
||||||
|
│ │ │ ├── ChooseTenant.php
|
||||||
|
│ │ │ ├── ChooseWorkspace.php
|
||||||
|
│ │ │ ├── TenantDashboard.php
|
||||||
|
│ │ │ ├── TenantRequiredPermissions.php
|
||||||
|
│ │ │ ├── WorkspaceOverview.php
|
||||||
|
│ │ │ ├── Monitoring/
|
||||||
|
│ │ │ │ └── Operations.php
|
||||||
|
│ │ │ └── Workspaces/
|
||||||
|
│ │ │ └── ManagedTenantsLanding.php
|
||||||
|
│ │ └── Resources/
|
||||||
|
│ │ ├── TenantResource.php
|
||||||
|
│ │ └── Workspaces/
|
||||||
|
│ │ └── WorkspaceResource.php
|
||||||
|
│ ├── Providers/Filament/
|
||||||
|
│ │ ├── AdminPanelProvider.php
|
||||||
|
│ │ └── TenantPanelProvider.php
|
||||||
|
│ ├── Http/Middleware/
|
||||||
|
│ │ └── EnsureWorkspaceSelected.php
|
||||||
|
│ └── Support/
|
||||||
|
│ ├── Middleware/
|
||||||
|
│ │ └── EnsureFilamentTenantSelected.php
|
||||||
|
│ ├── Navigation/
|
||||||
|
│ │ └── RelatedNavigationResolver.php
|
||||||
|
│ ├── Tenants/
|
||||||
|
│ │ └── TenantPageCategory.php
|
||||||
|
│ ├── Workspaces/
|
||||||
|
│ │ └── WorkspaceRedirectResolver.php
|
||||||
|
│ └── OperationRunLinks.php
|
||||||
|
├── bootstrap/
|
||||||
|
│ └── providers.php
|
||||||
|
├── routes/
|
||||||
|
│ └── web.php
|
||||||
|
└── tests/
|
||||||
|
├── Browser/
|
||||||
|
└── Feature/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Structure decision**: keep the documentation package self-contained under `specs/280-workspace-tenancy-environment-routing/`; later implementation should modify the existing `apps/platform` panel, route, middleware, and navigation seams directly instead of introducing a parallel routing subsystem.
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
No constitution violation or bloat exception is introduced by the plan. The slice removes a temporary panel and route family and adds no new persistence, abstraction, state family, or taxonomy.
|
||||||
|
|
||||||
|
## Proportionality Review
|
||||||
|
|
||||||
|
- **Current operator problem**: the operator runtime still communicates two public truths by splitting workspace work under `/admin` and environment work under `/admin/t`.
|
||||||
|
- **Existing structure is insufficient because**: the temporary second panel is encoded in providers, chooser redirects, middleware, route categorization, breadcrumbs, and shared deep-link builders, so a local redirect fix would preserve the same structural drift.
|
||||||
|
- **Narrowest correct implementation**: reuse the existing `admin` panel, dashboard builders, chooser surfaces, and operations hub while replacing only the panel-tenancy and route-contract shell around them.
|
||||||
|
- **Ownership cost created**: focused route, middleware, link-builder, and browser/feature proof work across several shared seams.
|
||||||
|
- **Alternative intentionally rejected**: keeping `/admin/t` with redirects or dual-panel ownership, because it preserves the temporary shell and contradicts the pre-production no-legacy doctrine.
|
||||||
|
- **Release truth**: current-release truth; this is a bounded cutover slice, not future-framework preparation.
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
# Quickstart: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
## Reviewer Flow
|
||||||
|
|
||||||
|
1. Read [spec.md](./spec.md), [plan.md](./plan.md), [research.md](./research.md), and [data-model.md](./data-model.md) together.
|
||||||
|
2. Confirm the package stays on reserved slot `280` only and treats Spec `279` as prerequisite context rather than reopened scope.
|
||||||
|
3. Confirm the verified current shell still exists today: `TenantPanelProvider` owns `path('admin/t')` and `tenant(ManagedEnvironment::class, slugAttribute: 'slug')`.
|
||||||
|
4. Confirm the cutover removes the public `/admin/t/{environment}`, `/admin/tenants/{environment}/required-permissions`, `/admin/w/{workspace}/managed-tenants`, and `/admin/operations` plus `/admin/operations/{run}` families with no redirects, aliases, or dual-panel fallback.
|
||||||
|
5. Confirm the final route contract is workspace-first: `/admin` for chooser/dashboard entry only, `/admin/workspaces/{workspace}` for the workspace dashboard, `/admin/workspaces/{workspace}/environments` for environment selection, `/admin/workspaces/{workspace}/environments/{environment}` for the environment dashboard, and `/admin/workspaces/{workspace}/operations` for the operations hub.
|
||||||
|
6. Confirm the plan reuses existing shared ownership points instead of inventing replacements: `WorkspaceOverviewBuilder`, `TenantDashboardSummaryBuilder`, `ManagedTenantsLanding`, `ChooseTenant`, `WorkspaceRedirectResolver`, `OperationRunLinks`, and `RelatedNavigationResolver`.
|
||||||
|
7. Confirm current-context and route-classification seams are updated together in the implementation plan: `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `TenantPageCategory`, and `ResolvesPanelTenantContext`.
|
||||||
|
8. Confirm the global-search rule stays explicit for `WorkspaceResource` and `TenantResource`.
|
||||||
|
9. Confirm Specs `281` through `287` remain explicitly deferred.
|
||||||
|
10. Confirm no application implementation is included in this prep package.
|
||||||
|
|
||||||
|
## Planned Validation Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php)
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php)
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail bin pint --dirty --format agent)
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/t/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/tenants/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/w/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/operations' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings "panel: 'tenant'" "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"
|
||||||
|
|
||||||
|
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings 'TenantPanelProvider::class' "$REPO_ROOT/apps/platform/bootstrap/providers.php"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Review Questions
|
||||||
|
|
||||||
|
- Does the package clearly remove the temporary `/admin/t` shell instead of hiding it behind redirects or aliases?
|
||||||
|
- Does the package also remove `/admin/w/{workspace}/managed-tenants` and `/admin/operations` plus `/admin/operations/{run}` instead of leaving a second chooser or operations route language behind?
|
||||||
|
- Does the plan keep `Workspace` as the only Filament tenant and `ManagedEnvironment` as nested route context only?
|
||||||
|
- Does the plan make the workspace-scoped environment chooser the canonical public environment entry surface instead of preserving two chooser routes?
|
||||||
|
- Do `OperationRunLinks`, `RelatedNavigationResolver`, and `Monitoring\Operations` remain the sole shared ownership path for operations deep links and back-navigation context?
|
||||||
|
- Are `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `TenantPageCategory`, and `ResolvesPanelTenantContext` treated as one combined current-context seam rather than independent local tweaks?
|
||||||
|
- Do `WorkspaceResource` and `TenantResource` either keep valid view/edit search destinations or get disabled from global search in the same slice?
|
||||||
|
- Does the package keep Filament on Livewire v4, keep provider registration in `apps/platform/bootstrap/providers.php`, and avoid new asset/deploy steps?
|
||||||
|
- Do Specs `281` through `287` remain explicitly deferred rather than silently absorbed?
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This prep package changes only planning artifacts under `specs/280-workspace-tenancy-environment-routing/`.
|
||||||
|
- No application implementation, tests, or runtime validation were executed while preparing the package.
|
||||||
78
specs/280-workspace-tenancy-environment-routing/research.md
Normal file
78
specs/280-workspace-tenancy-environment-routing/research.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Research: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
**Date**: 2026-05-07
|
||||||
|
**Branch**: `280-workspace-tenancy-environment-routing`
|
||||||
|
|
||||||
|
## Decision 1: Collapse public operator routing into the existing `admin` panel
|
||||||
|
|
||||||
|
- **Decision**: `AdminPanelProvider` remains the only operator-facing Filament panel, and `Workspace` becomes the only Filament tenant for operator admin routing. `TenantPanelProvider` stops owning public operator routes.
|
||||||
|
- **Rationale**: verified repo seams show `AdminPanelProvider` already owns `/admin`, `ChooseWorkspace`, `ChooseTenant`, `WorkspaceOverview`, and the current workspace-scoped operations hub, while `TenantPanelProvider` exists only to keep the temporary `/admin/t` shell alive. Filament v5 panel configuration on Laravel 12 still expects provider registration in `apps/platform/bootstrap/providers.php`, so the narrowest truthful cutover is to remove the second operator panel rather than preserve it as a redirect shell.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Keep `TenantPanelProvider` as a redirect-only shell: rejected because it preserves the same split public truth and adds compatibility behavior the spec forbids.
|
||||||
|
- Introduce nested environment Filament tenancy: rejected because `ManagedEnvironment` should remain nested route context, not a second panel tenancy.
|
||||||
|
|
||||||
|
## Decision 2: Make the workspace-scoped environment chooser the canonical public environment entry surface
|
||||||
|
|
||||||
|
- **Decision**: the canonical environment chooser route lives at `/admin/workspaces/{workspace}/environments`, reusing the current workspace-bound `ManagedTenantsLanding` surface. `ChooseTenant` remains an implementation seam to absorb or delegate selection logic, but it should not survive as a second canonical public chooser route, and `/admin/w/{workspace}/managed-tenants` is retired with no redirect or alias.
|
||||||
|
- **Rationale**: `ManagedTenantsLanding` already binds a `Workspace` route parameter and lists accessible `ManagedEnvironment` records for that workspace. `ChooseTenant` currently mixes workspace recovery, selection, and redirection into the temporary tenant panel. Keeping the workspace-scoped landing as the public chooser keeps the route language aligned with the new workspace-first contract.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Keep both `ChooseTenant` and `ManagedTenantsLanding` public: rejected because it preserves two chooser URLs for the same operator decision.
|
||||||
|
- Continue auto-branching from workspace selection directly into one environment dashboard: rejected because the spec treats the workspace dashboard and environment chooser as explicit decision surfaces, not hidden branching behavior.
|
||||||
|
|
||||||
|
## Decision 3: Keep `/admin` tied to workspace selection or the workspace dashboard
|
||||||
|
|
||||||
|
- **Decision**: direct `/admin` requests resolve only to workspace selection or the canonical workspace dashboard. Environment entry becomes explicit through the workspace dashboard and environment chooser instead of using `/admin` as a hidden second environment dashboard route.
|
||||||
|
- **Rationale**: the spec requires `/admin` to remain the operator entrypoint only, not a second canonical environment route. This keeps the workspace dashboard as the primary decision surface and removes the need to infer operator scope from a remembered environment.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Keep `/admin` auto-redirecting into one environment when possible: rejected because it weakens the workspace-first shell and makes route truth dependent on remembered tenant context.
|
||||||
|
- Leave `/admin` ambiguous between workspace and environment dashboards: rejected because it preserves the current split truth.
|
||||||
|
|
||||||
|
## Decision 4: Reuse the existing workspace and environment dashboards under new route ownership
|
||||||
|
|
||||||
|
- **Decision**: `WorkspaceOverview` plus `WorkspaceOverviewBuilder` remain the workspace dashboard, and `TenantDashboard` plus `TenantDashboardSummaryBuilder` remain the managed-environment dashboard. The slice changes route ownership, breadcrumbs, context-bar signals, and deep links only.
|
||||||
|
- **Rationale**: current repo truth already has the signal builders and widget families needed for this slice. The missing piece is not new dashboard content, but the temporary panel and route shell around it.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Build new dashboard pages for workspace-first routing: rejected because it would duplicate existing signal ownership and absorb UI work the spec explicitly defers.
|
||||||
|
- Refactor builder ownership now: rejected because provider extraction and copy neutralization are deferred to later specs.
|
||||||
|
|
||||||
|
## Decision 5: Move operations into a workspace-first route family through shared navigation builders
|
||||||
|
|
||||||
|
- **Decision**: `/admin/workspaces/{workspace}/operations` and `/admin/workspaces/{workspace}/operations/{run}` become the canonical operations routes, and `OperationRunLinks`, `RelatedNavigationResolver`, and `Monitoring\Operations` remain the only shared seams for retargeting collection/detail URLs, environment filters, and back-navigation context. The legacy `/admin/operations` family is retired with no redirect or alias.
|
||||||
|
- **Rationale**: `Monitoring\Operations` already models `managed_environment_id`, `tenant_scope`, `problemClass`, `activeTab`, and navigation context as workspace-scoped state. The current defect is the public route family and back-link language, not the monitoring page itself.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Keep `/admin/operations` as the canonical route: rejected because the spec explicitly requires the workspace-first operations family.
|
||||||
|
- Add environment-local operations pages: rejected because the operations hub is already the canonical shared monitoring surface.
|
||||||
|
|
||||||
|
## Decision 6: Update middleware, route categorization, and panel-context resolution together
|
||||||
|
|
||||||
|
- **Decision**: `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `TenantPageCategory`, `ResolvesPanelTenantContext`, and `WorkspaceRedirectResolver` must move to the workspace-first environment family in the same implementation slice.
|
||||||
|
- **Rationale**: those seams currently special-case `/admin/t`, `/admin/tenants/{environment}`, workspace-canonical `/admin/operations`, and current panel ID. Partial updates would leave stale remembered environment context, wrong 404/403 handling, or mixed breadcrumb language.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Route redirects only: rejected because redirects would preserve the old route language and leave classifier logic stale.
|
||||||
|
- Query-parameter-only environment context: rejected because the spec requires canonical workspace-first environment routes, not implicit query-driven context.
|
||||||
|
|
||||||
|
## Decision 7: Keep global search truthful only where destinations remain valid
|
||||||
|
|
||||||
|
- **Decision**: the touched search-eligible surfaces are `WorkspaceResource` and `TenantResource`. Each remains globally searchable only if it still has a valid view or edit destination under the workspace-first route contract; otherwise search is disabled in the same slice.
|
||||||
|
- **Rationale**: Filament v5 global search requires a resource to have a view or edit page in order to produce valid results. Current repo truth shows most other touched resources are already `isGloballySearchable = false`, so the search review surface is small and explicit.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Leave current global search behavior unchanged and fix later: rejected because it can leave broken results or inaccurate hints immediately after the route cutover.
|
||||||
|
|
||||||
|
## Decision 8: Prove the cutover with focused feature coverage, one browser smoke, and grep/guard checks
|
||||||
|
|
||||||
|
- **Decision**: use focused feature tests for routing and authorization, one browser smoke for the workspace-to-environment-to-operations path, and explicit grep/guard checks for `/admin/t` and `panel: 'tenant'` regressions.
|
||||||
|
- **Rationale**: Filament documentation emphasizes testing authorization and Livewire page behavior end to end, and the riskiest regression here is the live entry flow across chooser, dashboard, and operations surfaces. One narrow browser smoke is enough; broader browser or governance suites would be disproportionate.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- Feature tests only: rejected because they would not prove the live chooser-to-dashboard flow on the surviving panel.
|
||||||
|
- Broad browser suite: rejected because it would create a new heavy cost center for a routing cutover slice.
|
||||||
|
|
||||||
|
## Final Research Outcome
|
||||||
|
|
||||||
|
- The final operator runtime should have one public panel and one public route language rooted in workspace scope.
|
||||||
|
- The workspace-scoped environment chooser should become the public environment entry surface, with `ChooseTenant` absorbed into the same flow rather than preserved as a second public chooser URL.
|
||||||
|
- The legacy chooser route `/admin/w/{workspace}/managed-tenants` and the legacy operations family `/admin/operations` must not survive as redirects, aliases, or hidden fallback readers.
|
||||||
|
- The workspace dashboard and managed-environment dashboard should be reused as-is under new route ownership.
|
||||||
|
- Operations routing should move through the shared builders and preserve explicit environment scope via `managed_environment_id` and back-link context.
|
||||||
|
- Middleware, page categorization, and panel-context resolution must change together so deny-as-not-found and remembered-context semantics stay correct.
|
||||||
|
- Global search must remain truthful for `WorkspaceResource` and `TenantResource` or be disabled in the same slice.
|
||||||
|
- The narrowest honest proof is feature coverage, one browser smoke, and grep/guard checks.
|
||||||
394
specs/280-workspace-tenancy-environment-routing/spec.md
Normal file
394
specs/280-workspace-tenancy-environment-routing/spec.md
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
# Feature Specification: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
**Feature Branch**: `280-workspace-tenancy-environment-routing`
|
||||||
|
**Created**: 2026-05-07
|
||||||
|
**Status**: Ready
|
||||||
|
**Input**: User description: "Work only in /Users/ahmeddarrazi/Documents/projects/wt-plattform on branch 280-workspace-tenancy-environment-routing. Update only spec-prep artifacts, no application code. Fill /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/280-workspace-tenancy-environment-routing/spec.md as the implementation-ready spec for candidate `280 - Filament Workspace Tenancy & Environment Routing Cutover` from docs/product/spec-candidates.md and docs/product/roadmap.md."
|
||||||
|
|
||||||
|
## Spec Candidate Check
|
||||||
|
|
||||||
|
- **Problem**: TenantPilot's operator runtime is still split between the workspace-scoped admin panel under `/admin` and the temporary ManagedEnvironment-scoped Filament panel under `/admin/t`. That leaves the public route contract, breadcrumbs, chooser flow, middleware, and deep-link builders behind the core model cutover completed in Spec `279`.
|
||||||
|
- **Today's failure**: Verified repo seams such as `TenantPanelProvider`, `ChooseTenant`, `WorkspaceRedirectResolver`, `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `TenantPageCategory`, `OperationRunLinks`, and many `panel: 'tenant'` links still route operators into `/admin/t/{environment}` or `/admin/tenants/{environment}`. Operators move from a workspace surface into a second panel with a different route language, while current operations remain canonical at workspace scope. The product therefore still communicates two different truths about where environment work actually lives.
|
||||||
|
- **User-visible improvement**: Operators get one workspace-first admin runtime: workspace dashboard, workspace-scoped environment chooser, managed-environment dashboard, workspace-scoped operations hub, and breadcrumbs that consistently read `Workspace -> Managed Environment -> domain page`.
|
||||||
|
- **Smallest enterprise-capable version**: Collapse the temporary `/admin/t` panel into the existing `/admin` admin panel, configure `Workspace` as the only Filament tenant, move environment pages under `/admin/workspaces/{workspace}/environments/{environment}`, make `/admin/workspaces/{workspace}/operations` the canonical operations route, reuse the existing workspace and environment dashboard builders, and remove the old route families without adding aliases or compatibility redirects.
|
||||||
|
- **Explicit non-goals**: No provider extraction, no governance-artifact retargeting, no provider-neutral capability registry, no provider-neutral artifact taxonomy, no RBAC redesign, no UI copy neutralization beyond route/breadcrumb/context truth needed by this slice, no quality-gate pack beyond feature-local routing/tenancy proof, no customer-portal cutover, no second Filament tenancy at the environment level, and no compatibility routes or dual-panel end state.
|
||||||
|
- **Permanent complexity imported**: One admin-panel tenancy flip from temporary environment-panel ownership to workspace tenancy, one canonical workspace-first environment route family, one route/breadcrumb/current-context contract update across chooser and deep-link seams, and focused feature/browser plus guard coverage. No new persistence, enum family, abstraction layer, or asset system is introduced.
|
||||||
|
- **Why now**: Spec `279` already changed the core managed-target noun to `ManagedEnvironment`, and the verified controlling seam still shows the old shell in `TenantPanelProvider` with `path('admin/t')` and `tenant(ManagedEnvironment::class, slugAttribute: 'slug')`. Until `280` closes that shell, every later reserved cutover slice (`281`-`287`) sits on an intentionally temporary panel and route contract.
|
||||||
|
- **Why not local**: This is not one page or one redirect. The temporary shell is encoded in provider registration, chooser flow, middleware, route categorization, current-context resolution, dashboard links, operations links, and resource URLs. A local fix would create the same hidden compatibility layer that the candidate and constitution reject.
|
||||||
|
- **Approval class**: Core Enterprise
|
||||||
|
- **Red flags triggered**: Many touched operator surfaces and a foundation-sounding cutover. Defense: the slice explicitly reuses existing workspace and environment dashboard builders, removes a temporary panel instead of adding framework machinery, forbids compatibility routes, and defers the remaining provider/artifact/RBAC/copy/quality follow-up to reserved Specs `281`-`287`.
|
||||||
|
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
|
||||||
|
- **Decision**: approve
|
||||||
|
|
||||||
|
## Spec Scope Fields
|
||||||
|
|
||||||
|
- **Scope**: workspace
|
||||||
|
- **Primary Routes**:
|
||||||
|
- `/admin` remains the operator entrypoint only and resolves to workspace chooser or the currently selected workspace dashboard; it is not a second canonical environment dashboard route
|
||||||
|
- `/admin/workspaces/{workspace}` becomes the canonical workspace dashboard route for the active workspace
|
||||||
|
- `/admin/workspaces/{workspace}/environments` becomes the canonical workspace-scoped environment chooser route
|
||||||
|
- `/admin/workspaces/{workspace}/environments/{environment}` becomes the canonical managed-environment dashboard route
|
||||||
|
- `/admin/workspaces/{workspace}/environments/{environment}/{domain...}` becomes the canonical family for environment-scoped resources, diagnostics, and detail pages that today resolve through `panel: 'tenant'` or `/admin/tenants/{environment}`
|
||||||
|
- `/admin/workspaces/{workspace}/operations` and `/admin/workspaces/{workspace}/operations/{run}` become the canonical operations collection/detail routes
|
||||||
|
- `/admin/w/{workspace}/managed-tenants`, `/admin/operations`, and `/admin/operations/{run}` are removed rather than retained as redirects, aliases, or hidden fallback readers
|
||||||
|
- `/admin/t/{environment}` and `/admin/tenants/{environment}/required-permissions` are removed rather than retained as aliases
|
||||||
|
- **Data Ownership**:
|
||||||
|
- `Workspace` remains the workspace-owned root context and becomes the only Filament tenant for operator admin routing
|
||||||
|
- `ManagedEnvironment` remains the managed target nested inside a workspace and is resolved from workspace-first route parameters and current-context helpers, not from a second Filament tenant registration
|
||||||
|
- no table ownership, artifact ownership, provider-profile ownership, or RBAC ownership changes are introduced in this slice
|
||||||
|
- **RBAC**:
|
||||||
|
- workspace membership remains the first isolation boundary
|
||||||
|
- managed-environment access remains the second isolation boundary
|
||||||
|
- wrong-workspace or wrong-environment access remains `404`
|
||||||
|
- in-scope actors missing capability remain `403`
|
||||||
|
- no new role family, capability family, or authorization plane is introduced
|
||||||
|
|
||||||
|
Canonical-view handling for workspace-scoped monitoring surfaces:
|
||||||
|
|
||||||
|
- **Default filter behavior when tenant-context is active**: when an operator enters a workspace-scoped canonical view from a managed-environment dashboard or environment-scoped page, the canonical view opens prefiltered to that `managed_environment_id` inside the current workspace; widening to all environments remains explicit.
|
||||||
|
- **Explicit entitlement checks preventing cross-tenant leakage**: route-bound `{workspace}` and `{environment}` values must match each other and the current actor's entitlements. Explicit environment query hints outside the current workspace or outside the actor's membership resolve as `404`, while stale remembered filters are discarded instead of leaking or cross-loading another environment.
|
||||||
|
|
||||||
|
## Cross-Cutting / Shared Pattern Reuse
|
||||||
|
|
||||||
|
- **Cross-cutting feature?**: yes
|
||||||
|
- **Interaction class(es)**: navigation entry points, context selection, dashboard signals, breadcrumbs, page context bars, workspace-to-environment drillthroughs, operations deep links, and route builders
|
||||||
|
- **Systems touched**: `AdminPanelProvider`, `TenantPanelProvider`, `ChooseWorkspace`, `ChooseTenant`, `WorkspaceOverview`, `TenantDashboard`, `Monitoring\Operations`, `WorkspaceRedirectResolver`, `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `ResolvesPanelTenantContext`, `TenantPageCategory`, `WorkspaceOverviewBuilder`, `TenantDashboardSummaryBuilder`, `OperationRunLinks`, and `RelatedNavigationResolver`
|
||||||
|
- **Existing pattern(s) to extend**: the existing `/admin` workspace panel, current workspace chooser flow, current `WorkspaceOverview` dashboard signals, current `TenantDashboard` environment signals, and the current tenant-safe operations link builders
|
||||||
|
- **Shared contract / presenter / builder / renderer to reuse**: `WorkspaceOverviewBuilder`, `TenantDashboardSummaryBuilder`, `WorkspaceContext`, `WorkspaceRedirectResolver`, `OperationRunLinks`, `RelatedNavigationResolver`, and the existing Filament page/resource action-surface contracts
|
||||||
|
- **Why the existing shared path is sufficient or insufficient**: these shared seams already own the user-facing signals and links that the final routing contract needs. What is insufficient today is not the dashboard or monitoring content itself, but the temporary second-panel route ownership and the mixed `/admin/t` versus `/admin/tenants` public path language.
|
||||||
|
- **Allowed deviation and why**: none. This slice removes the temporary panel and route family instead of introducing another exception.
|
||||||
|
- **Consistency impact**: workspace chooser, environment chooser, dashboard links, operations links, breadcrumbs, context bar signals, and page-level deep links must all speak the same workspace-first route language and the same `Workspace -> Managed Environment -> domain page` hierarchy.
|
||||||
|
- **Review focus**: reviewers must verify that `WorkspaceOverview` and `TenantDashboard` are reused rather than replaced, that `OperationRunLinks` and related builders stop emitting `panel: 'tenant'` or `/admin/t` URLs, and that no second route language or compatibility redirect remains.
|
||||||
|
|
||||||
|
## OperationRun UX Impact
|
||||||
|
|
||||||
|
- **Touches OperationRun start/completion/link UX?**: yes, link semantics only
|
||||||
|
- **Shared OperationRun UX contract/layer reused**: `OperationRunLinks`, `RelatedNavigationResolver`, the existing `Monitoring\Operations` page, and the existing canonical navigation context helpers
|
||||||
|
- **Delegated start/completion UX behaviors**: workspace-safe route resolution for operations index/detail, workspace-to-environment back-link context, and environment-prefiltered operations drillthrough via `managed_environment_id`; no new toast, queue, dedupe, terminal-notification, or artifact-link behavior is introduced
|
||||||
|
- **Local surface-owned behavior that remains**: environment dashboards and environment-scoped pages only supply the filter context and back-navigation label for the existing operations surface
|
||||||
|
- **Queued DB-notification policy**: `N/A`
|
||||||
|
- **Terminal notification path**: `N/A`
|
||||||
|
- **Exception required?**: none
|
||||||
|
|
||||||
|
## Provider Boundary / Platform Core Check
|
||||||
|
|
||||||
|
- **Shared provider/platform boundary touched?**: yes
|
||||||
|
- **Boundary classification**: mixed
|
||||||
|
- **Seams affected**: panel tenancy, route nouns, chooser wording, page-category classification, breadcrumb context, current-context resolution, and deep-link builders that still encode temporary environment-panel ownership
|
||||||
|
- **Neutral platform terms preserved or introduced**: `workspace`, `managed environment`, `environment`, `operations`, `findings`, `backup sets`, `evidence`, and `required permissions`
|
||||||
|
- **Provider-specific semantics retained and why**: provider-owned Microsoft terms may remain in untouched provider copy, metadata, or class names such as `TenantResource` until Specs `281` and `286` handle provider extraction and copy neutralization. This slice only requires that routing, breadcrumbs, panel tenancy, chooser labels, and context signals touched by the cutover become workspace/environment truthful.
|
||||||
|
- **Why this does not deepen provider coupling accidentally**: the slice removes a provider-shaped public shell instead of entrenching it. `Workspace` becomes the Filament tenant, `ManagedEnvironment` remains nested context, and no provider-profile fields or Microsoft-specific routing semantics are added to the platform-core route contract.
|
||||||
|
- **Follow-up path**: Spec `281` for provider-profile extraction, Spec `282` for artifact retargeting, Spec `285` for workspace-first RBAC scope follow-through, Spec `286` for copy neutralization, and Spec `287` for explicit no-legacy enforcement
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Impact
|
||||||
|
|
||||||
|
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| Workspace dashboard under the canonical workspace route | yes | Native Filament page plus shared dashboard builders | navigation, dashboard signals, body CTA links | page, URL, summary state | no | Reuses `WorkspaceOverview` and `WorkspaceOverviewBuilder` rather than introducing a second workspace summary surface |
|
||||||
|
| Workspace-scoped environment chooser | yes | Native Filament simple page | context selection, navigation entry point | page, URL, remembered context | no | Reuses the existing chooser flow instead of adding a second environment selector |
|
||||||
|
| Managed-environment dashboard under the workspace-first route | yes | Native Filament dashboard plus shared widgets/builders | dashboard signals, header actions, page CTAs | page, URL, detail state | no | Reuses `TenantDashboard` and its current widget/action family; routing and breadcrumbs change, not the signal source |
|
||||||
|
| Workspace operations hub under `/admin/workspaces/{workspace}/operations` | yes | Native Filament monitoring page | operations links, back links, filter context | page, table, URL/query | no | Reuses the existing operations surface and moves only the public route ownership/context contract |
|
||||||
|
| Environment-scoped page family migrated off `/admin/t` and `/admin/tenants/{environment}` | yes | Native Filament resources/pages | navigation, breadcrumbs, current-context shell | page, detail, URL/query | no | Local action contracts stay intact; this slice changes route ownership and breadcrumb shell only |
|
||||||
|
|
||||||
|
## Decision-First Surface Role
|
||||||
|
|
||||||
|
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| Workspace dashboard under the canonical workspace route | Primary Decision Surface | Operator scans one workspace to decide which environment or workflow needs attention next | workspace-wide signal counts, attention items, recent operations, and quick actions | deeper environment detail remains one click away on the environment dashboard or operations hub | Primary because it is the workspace landing surface after workspace selection | Follows workspace-first operating flow instead of dropping straight into one environment panel | Removes the current panel split and makes workspace scope explicit before environment work begins |
|
||||||
|
| Workspace-scoped environment chooser | Primary Decision Surface | Operator chooses the managed environment they intend to act in | environment identity, lifecycle posture, and current workspace context | deeper environment diagnostics stay on the environment dashboard | Primary because environment selection is the next explicit decision after workspace selection | Keeps selection inside the same workspace panel instead of jumping into a second panel | Removes the cognitive break between workspace selection and environment work |
|
||||||
|
| Managed-environment dashboard under the workspace-first route | Secondary Context Surface | Operator confirms the chosen environment and decides which domain page to open next | environment posture, chips, recommended action, and dashboard signals | raw/support detail stays where it already belongs on downstream pages | Secondary because it is an environment workbench, not the workspace-wide landing decision surface | Aligns with today's dashboard-driven environment workflow, but under truthful workspace-first routing | Reduces wrong-environment actions by keeping workspace and environment context visible together |
|
||||||
|
| Workspace operations hub under `/admin/workspaces/{workspace}/operations` | Primary Decision Surface | Operator reviews queued and completed operations for one workspace, optionally narrowed to one environment | current workspace, active environment filter if present, operation status/outcome, and canonical back link | deeper run detail stays on the run viewer | Primary because operations already serve as the canonical monitoring surface | Preserves the existing monitoring workflow while removing the need to infer environment context from a second panel | Reduces duplicate monitoring entry points and keeps widening/narrowing explicit |
|
||||||
|
| Environment-scoped page family migrated off `/admin/t` and `/admin/tenants/{environment}` | Secondary Context Surface | Operator performs the chosen domain task inside one managed environment | breadcrumb/context shell plus the existing page-local operational truth | existing diagnostics remain page-local and unchanged | Secondary because the decision is made on dashboard or operations surfaces; these pages carry the work forward | Preserves each page's current workflow contract while making its scope truthful in navigation | Removes path-language drift without creating new page-local decision systems |
|
||||||
|
|
||||||
|
## Audience-Aware Disclosure
|
||||||
|
|
||||||
|
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| Workspace dashboard under the canonical workspace route | operator-MSP, support-platform | workspace-wide attention, recent operations, and one next-action entry point | per-environment follow-up remains secondary | raw evidence stays on downstream pages | `Open environment` or `Open operations` depending on the top signal | raw evidence and low-level diagnostics remain downstream | workspace summary stays at workspace level and does not restate environment dashboards |
|
||||||
|
| Workspace-scoped environment chooser | operator-MSP, support-platform | current workspace plus selectable managed environments | lifecycle posture only | provider/raw detail hidden | `Open environment` | provider-specific raw metadata | chooser states selection truth once, then hands off to the environment dashboard |
|
||||||
|
| Managed-environment dashboard under the workspace-first route | operator-MSP, support-platform | environment summary, posture, and recommended next action | page-local diagnostics, support request detail, and additional widgets | raw evidence stays on dedicated downstream surfaces | primary follow-up action | support/raw evidence remains downstream and capability-gated where already required | dashboard shows the environment summary once and uses links for deeper evidence instead of repeating it |
|
||||||
|
| Workspace operations hub under `/admin/workspaces/{workspace}/operations` | operator-MSP, support-platform | workspace scope, optional environment filter, and operation follow-up state | run-level diagnostics remain on the run viewer | raw payloads remain on detail pages or related evidence surfaces | `Open operation` or `Back to environment` | low-level run evidence stays in detail | the hub states scope once and uses detail pages for deep evidence |
|
||||||
|
| Environment-scoped page family migrated off `/admin/t` and `/admin/tenants/{environment}` | operator-MSP, support-platform | breadcrumb scope plus each page's existing core truth | existing diagnostics stay on the page | support/raw evidence remains unchanged and page-local | the page's existing primary action | unrelated raw/support detail remains gated as today | the new shell adds context instead of duplicating page-local summaries |
|
||||||
|
|
||||||
|
## UI/UX Surface Classification
|
||||||
|
|
||||||
|
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| Workspace dashboard under the canonical workspace route | Dashboard / Context / Summary | Workspace landing dashboard | Open one environment or the operations hub | body CTA cards and quick actions | forbidden | body-level quick actions only | none | `/admin/workspaces/{workspace}` | `/admin/workspaces/{workspace}` | active workspace | Workspace | workspace-wide attention and recent operations | none |
|
||||||
|
| Workspace-scoped environment chooser | Selector / Context / Entry | Workspace environment selector | Open the intended managed environment | environment card/button | allowed | workspace switch remains secondary | none | `/admin/workspaces/{workspace}/environments` | `/admin/workspaces/{workspace}/environments/{environment}` | active workspace plus environment identity | Managed Environment | selectable environment identity and posture | none |
|
||||||
|
| Managed-environment dashboard under the workspace-first route | Dashboard / Detail / Workflow hub | Environment dashboard | Follow the recommended domain action | dashboard CTA from widget or header action | forbidden | grouped header secondary actions plus body links | none introduced by this slice | `/admin/workspaces/{workspace}/environments` | `/admin/workspaces/{workspace}/environments/{environment}` | active workspace and active managed environment | Managed Environment | environment posture, next action, and dashboard signals | none |
|
||||||
|
| Workspace operations hub under `/admin/workspaces/{workspace}/operations` | List / Table / Monitoring | Workspace monitoring hub | Inspect a run or widen/narrow environment scope | clickable row to run detail | required | header back-link and filter actions | none | `/admin/workspaces/{workspace}/operations` | `/admin/workspaces/{workspace}/operations/{run}` | active workspace and optional managed-environment filter | Operations / Operation | current scope, run state, and follow-up truth | none |
|
||||||
|
| Environment-scoped page family migrated off `/admin/t` and `/admin/tenants/{environment}` | List / Detail / Resource family | Workspace-first environment resources and diagnostics | Continue the chosen domain workflow | preserve each surface's current inspect model | preserve current contract | preserve current contract | preserve current contract | `/admin/workspaces/{workspace}/environments/{environment}/{domain}` | `/admin/workspaces/{workspace}/environments/{environment}/{domain}/{record?}` | breadcrumb `Workspace -> Managed Environment -> page` | Existing domain noun | active workspace/environment context plus each page's existing truth | none |
|
||||||
|
|
||||||
|
## Operator Surface Contract
|
||||||
|
|
||||||
|
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| Workspace dashboard under the canonical workspace route | Workspace operator | Decide which environment or workspace-wide workflow needs attention first | Dashboard | What in this workspace needs my attention next? | workspace-wide attention, recent operations, quick actions | deeper environment evidence stays downstream | workspace attention, run activity, recovery posture | none | Open environment, Open operations | none |
|
||||||
|
| Workspace-scoped environment chooser | Workspace operator | Decide which managed environment to enter | Context-selection page | Which managed environment am I about to work in? | environment identity, posture, workspace context | deeper diagnostics remain on the dashboard | lifecycle posture, access posture | none | Open environment | none |
|
||||||
|
| Managed-environment dashboard under the workspace-first route | Environment operator | Decide which environment-scoped page to open next | Dashboard | What in this environment needs action now? | posture, dashboard chips, recommended action, summary widgets | support diagnostics and deeper evidence remain downstream | environment posture, readiness, attention state | existing page-local semantics only | Primary follow-up, More, Request support | none added by this slice |
|
||||||
|
| Workspace operations hub under `/admin/workspaces/{workspace}/operations` | Monitoring operator | Review runs across the workspace or one environment | Monitoring table/detail | Which operations need follow-up, and for which environment? | current scope, optional environment filter, run status/outcome, back link | run detail, evidence, and related links remain secondary | execution status, outcome, scope | read-only | Open operation, Back to environment, Show all environments | none |
|
||||||
|
| Environment-scoped page family migrated off `/admin/t` and `/admin/tenants/{environment}` | Environment operator | Perform the selected domain workflow in one environment | Resource/detail family | Am I acting in the correct workspace and managed environment? | breadcrumb scope and existing page truth | page-local diagnostics unchanged | preserve current domain-specific status families | preserve current page-local mutation semantics | preserve current page-local primary action | preserve current page-local dangerous actions |
|
||||||
|
|
||||||
|
## Proportionality Review
|
||||||
|
|
||||||
|
- **New source of truth?**: no
|
||||||
|
- **New persisted entity/table/artifact?**: no
|
||||||
|
- **New abstraction?**: no
|
||||||
|
- **New enum/state/reason family?**: no
|
||||||
|
- **New cross-domain UI framework/taxonomy?**: no
|
||||||
|
- **Current operator problem**: the operator runtime still communicates two conflicting truths: workspace-wide work begins on `/admin`, but environment work still lives on a temporary `/admin/t` panel and a second `/admin/tenants/{environment}` route family.
|
||||||
|
- **Existing structure is insufficient because**: the current structure requires special-casing `/admin/t` and `/admin/tenants` in provider setup, middleware, route categorization, chooser flow, and deep-link builders. As long as that split remains, later provider/artifact/RBAC cutovers would be forced to build on a known temporary shell.
|
||||||
|
- **Narrowest correct implementation**: reuse the current admin panel, workspace dashboard, environment dashboard, chooser flow, and operations surface, and change only the tenancy and public route ownership needed to remove the temporary shell.
|
||||||
|
- **Ownership cost**: route and deep-link retargeting across chooser, middleware, builders, breadcrumbs, and tests; removal of one panel provider from the operator runtime; and focused browser/feature coverage to keep the route contract honest.
|
||||||
|
- **Alternative intentionally rejected**: leaving the `/admin/t` panel in place with redirects, keeping `/admin/tenants/{environment}` as a second route family, or adding nested/dual Filament tenancy. Those options preserve the temporary shell instead of closing it.
|
||||||
|
- **Release truth**: current-release truth; this slice removes temporary runtime structure rather than preparing future framework flexibility
|
||||||
|
|
||||||
|
### Compatibility posture
|
||||||
|
|
||||||
|
This feature assumes a pre-production environment.
|
||||||
|
|
||||||
|
Backward compatibility, legacy aliases, redirect shims, dual-panel ownership, dual-context restore paths, and compatibility-specific tests are out of scope unless explicitly required by this spec.
|
||||||
|
|
||||||
|
Canonical replacement is preferred over preservation.
|
||||||
|
|
||||||
|
## Testing / Lane / Runtime Impact
|
||||||
|
|
||||||
|
- **Test purpose / classification**: Feature, Browser
|
||||||
|
- **Validation lane(s)**: fast-feedback, confidence, browser
|
||||||
|
- **Why this classification and these lanes are sufficient**: this slice changes panel tenancy, public routes, chooser flow, breadcrumbs, deep-link builders, and workspace/environment authorization semantics. Feature tests prove route ownership, filter context, and deny-as-not-found behavior; one narrow browser smoke proves the chooser-to-dashboard-to-operations flow still works end to end on the surviving admin panel.
|
||||||
|
- **New or expanded test families**: one workspace-tenancy/routing feature family, one environment-routing/breadcrumb feature family, one operations canonical-context feature family, one legacy-route-removal or grep/guard family, and one narrow browser smoke for the workspace-to-environment flow
|
||||||
|
- **Fixture / helper cost impact**: moderate. Tests must seed a workspace plus managed environments and reuse existing workspace/member/environment context helpers. The slice should not introduce new default-heavy fixtures, providers, or session helpers beyond what current workspace and managed-environment tests already require.
|
||||||
|
- **Heavy-family visibility / justification**: one browser smoke only. No new heavy-governance or broad browser family is justified.
|
||||||
|
- **Special surface test profile**: standard-native-filament, global-context-shell, monitoring-state-page
|
||||||
|
- **Standard-native relief or required special coverage**: standard feature coverage is sufficient for provider registration, tenancy switch, route removal, and breadcrumb semantics. One browser smoke is required because the cutover changes the live operator entry flow across chooser, dashboard, and operations surfaces.
|
||||||
|
- **Reviewer handoff**: reviewers must verify that `AdminPanelProvider` becomes the only operator panel, any provider-registration change remains in `apps/platform/bootstrap/providers.php`, `TenantPanelProvider` and `/admin/t` cease to own public operator routing, `ChooseTenant` and `WorkspaceRedirectResolver` stop emitting `panel: 'tenant'` destinations, `OperationRunLinks` and related navigation open the workspace operations hub with explicit environment filters, `TenantPageCategory` and the two middleware seams no longer treat `/admin/t` or `/admin/tenants/{environment}` as active route languages, `WorkspaceOverview` and `TenantDashboard` are reused rather than replaced, `WorkspaceResource` and `TenantResource` keep valid view/edit search destinations or disable search if that contract cannot be kept, destructive actions touched by the route move still require confirmation and server authorization, Livewire remains v4, and no new asset registration/deploy step appears.
|
||||||
|
- **Budget / baseline / trend impact**: moderate feature-local increase only
|
||||||
|
- **Escalation needed**: none
|
||||||
|
- **Active feature PR close-out entry**: Guardrail
|
||||||
|
- **Planned validation commands**:
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php)`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php)`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail bin pint --dirty --format agent)`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/t/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/tenants/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/w/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/operations' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings "panel: 'tenant'" "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`
|
||||||
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings 'TenantPanelProvider::class' "$REPO_ROOT/apps/platform/bootstrap/providers.php"`
|
||||||
|
|
||||||
|
## Scope Boundaries *(required for this slice)*
|
||||||
|
|
||||||
|
### In Scope
|
||||||
|
|
||||||
|
- make `Workspace` the only Filament tenant for the operator admin runtime
|
||||||
|
- collapse the operator-facing `/admin/t` panel into the existing `/admin` admin panel
|
||||||
|
- remove the public `/admin/t/{environment}` route family with no compatibility alias
|
||||||
|
- remove the public `/admin/tenants/{environment}/required-permissions` family in favor of the workspace-first environment route family
|
||||||
|
- introduce the canonical workspace-first environment route family rooted at `/admin/workspaces/{workspace}/environments/{environment}`
|
||||||
|
- reuse `WorkspaceOverview` as the workspace dashboard and `TenantDashboard` as the managed-environment dashboard under the new route ownership
|
||||||
|
- make `/admin/workspaces/{workspace}/operations` the canonical operations route and thread environment pages into it through explicit filters and back-navigation context
|
||||||
|
- retarget chooser flow, middleware, route categorization, deep-link builders, breadcrumbs, and page-level context signals to the new route contract
|
||||||
|
- ensure touched globally searchable resources preserve a valid view/edit destination or are disabled from global search in the same slice
|
||||||
|
|
||||||
|
### Non-Goals
|
||||||
|
|
||||||
|
- provider connection, provider scope, or Microsoft profile extraction owned by Spec `281`
|
||||||
|
- governance artifact retargeting to `ManagedEnvironment` owned by Spec `282`
|
||||||
|
- provider capability registry or provider-neutral artifact taxonomy owned by Specs `283` and `284`
|
||||||
|
- workspace-first RBAC redesign or capability-family changes owned by Spec `285`
|
||||||
|
- UI copy, IA, and localization neutralization beyond the route/breadcrumb/context truth needed here, owned by Spec `286`
|
||||||
|
- cutover quality-gate pack work beyond the routing/tenancy slice proof needed here, owned by Spec `287`
|
||||||
|
- customer portal, website, package-engine, or guided-operations work
|
||||||
|
- compatibility routes, aliases, redirects that preserve `/admin/t`, dual panel ownership, dual-read/dual-write route logic, or hidden absorption of later reserved specs
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
- Spec `279` already completed the core `ManagedEnvironment` cutover and remains prerequisite context only.
|
||||||
|
- `AdminPanelProvider` remains the long-lived operator panel under `/admin`, and removing `TenantPanelProvider` from operator routing is acceptable in this pre-production environment.
|
||||||
|
- The current workspace and environment dashboards already provide the operator signal content needed for the final route contract; `280` relocates and rekeys them instead of redesigning their signal models.
|
||||||
|
- Internal class names or untouched provider copy may still use `tenant` until later reserved slices, but routing, breadcrumbs, chooser labels, and operator-facing context signals touched by this slice must be workspace/environment truthful.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Hidden `/admin/t` or `panel: 'tenant'` references in builders, widgets, or tests could leave a partial second route language behind after the cutover.
|
||||||
|
- Operations deep links and back links could lose environment scope or workspace safety if they are retargeted page by page instead of through the shared link builders.
|
||||||
|
- Collapsing panel ownership could break current context bootstrap if middleware and route categorization are not updated together.
|
||||||
|
- The slice could sprawl into copy cleanup or RBAC redesign if reviewers do not hold the scope boundary against Specs `285` and `286`.
|
||||||
|
|
||||||
|
## Candidate Selection Gate Summary
|
||||||
|
|
||||||
|
- **Selected candidate**: `280 - Filament Workspace Tenancy & Environment Routing Cutover`
|
||||||
|
- **Source locations**:
|
||||||
|
- `docs/product/spec-candidates.md` under the reserved workspace-first / provider-neutral cutover pack
|
||||||
|
- `docs/product/roadmap.md` under the same reserved cutover ordering and sequencing note
|
||||||
|
- **Why selected now**: Spec `279` already changed the core managed-target noun, and the verified controlling seam still shows the temporary panel shell in `TenantPanelProvider`. `280` is the second reserved slot and closes that temporary public routing/tenancy contract before later provider and artifact follow-up work begins.
|
||||||
|
- **Why close alternatives were deferred**:
|
||||||
|
- `281` belongs to provider connection/profile extraction, not to route collapse
|
||||||
|
- `282` belongs to artifact retargeting, not to public route ownership
|
||||||
|
- `283` and `284` belong to provider-neutral packaging follow-through, not to the immediate shell removal
|
||||||
|
- `285`, `286`, and `287` are explicit follow-up guardrail slices for RBAC, copy, and no-legacy enforcement
|
||||||
|
- the already-existing mid-260/270 packages remain context only and are not the target of this reserved cutover slot
|
||||||
|
- **Smallest viable implementation slice**: one surviving workspace-tenanted admin panel, one canonical workspace-first environment route family, one canonical workspace operations route family, and one bounded set of chooser/middleware/link/breadcrumb updates with no aliases
|
||||||
|
- **Documented deviation from raw candidate wording**: `add a workspace dashboard` and `add a managed-environment dashboard` are satisfied by re-homing the existing `WorkspaceOverview` and `TenantDashboard` surfaces under their final route ownership rather than creating new summary systems.
|
||||||
|
|
||||||
|
## Completed-Spec Guardrail Result
|
||||||
|
|
||||||
|
- `specs/265-decision-register-approval/` already exists with `Status: Ready for implementation` and remains separate context only
|
||||||
|
- `specs/267-artifact-lifecycle-retention/` already exists with `Status: Ready for implementation` and remains separate context only
|
||||||
|
- `specs/274-billing-subscription-truth/` already exists with `Status: Ready for implementation` and remains separate context only
|
||||||
|
- `specs/275-customer-facing-localization-adoption/` already exists with `Status: Draft` and remains separate context only
|
||||||
|
- `specs/276-support-access-governance/` already exists with `Status: Ready for implementation` and remains a separate prepared package
|
||||||
|
- `specs/277-stored-reports-surface/` already exists with `Status: Ready for implementation` and remains a separate prepared package
|
||||||
|
- `specs/278-cross-domain-indicator-audit/` already exists with `Status: Implemented docs-only audit artifact; ready for manual review` and remains separate historical context only
|
||||||
|
- `specs/279-workspace-managed-environment-core/` already exists with `Status: Ready with approved feature-local exception`, carries implementation-close-out history in `tasks.md`, and is prerequisite context only for this package
|
||||||
|
|
||||||
|
## Deferred Adjacent Candidates
|
||||||
|
|
||||||
|
- `281 - Provider Connection, Provider Scope & Microsoft Profile Extraction`
|
||||||
|
- `282 - Governance Artifact Retargeting to ManagedEnvironment`
|
||||||
|
- `283 - Provider Capability Registry v1`
|
||||||
|
- `284 - Provider-neutral Artifact Source Taxonomy v1`
|
||||||
|
- `285 - Workspace-first RBAC & Environment Access Scoping`
|
||||||
|
- `286 - UI Copy, IA & Localization Neutralization`
|
||||||
|
- `287 - Cutover Quality Gates & No-Legacy Enforcement`
|
||||||
|
|
||||||
|
## User Scenarios & Testing
|
||||||
|
|
||||||
|
### User Story 1 - Enter an environment without leaving the workspace admin panel (Priority: P1)
|
||||||
|
|
||||||
|
As an operator, I want to choose a workspace and then open one managed environment inside the same admin panel so I never have to cross into a second panel or learn a second route language just to work on that environment.
|
||||||
|
|
||||||
|
**Why this priority**: this is the core cutover outcome. If the chooser still lands on `/admin/t`, the temporary shell remains active and the candidate is not complete.
|
||||||
|
|
||||||
|
**Independent Test**: select a workspace, open the environment chooser, choose one managed environment, and confirm the destination is the canonical workspace-first environment dashboard rather than `/admin/t`.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** an operator has selected a workspace, **When** they choose a managed environment, **Then** they land on `/admin/workspaces/{workspace}/environments/{environment}` inside the surviving admin panel.
|
||||||
|
2. **Given** an operator enters a workspace/environment URL where the environment belongs to a different workspace or is not accessible, **When** the route resolves, **Then** the app returns `404` and reveals no environment data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 2 - Move from environment work into workspace operations with explicit scope (Priority: P1)
|
||||||
|
|
||||||
|
As an operator, I want environment dashboards and environment pages to open the workspace operations hub with an explicit environment filter so operations stay workspace-canonical without losing the narrower environment context I came from.
|
||||||
|
|
||||||
|
**Why this priority**: the candidate explicitly requires `/admin/workspaces/{workspace}/operations` to be canonical and environment pages to link into it via filters.
|
||||||
|
|
||||||
|
**Independent Test**: open one environment dashboard or environment page, follow its operations link, and confirm the workspace operations hub opens with the correct environment filter and return context.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** an operator is on one managed-environment dashboard, **When** they open operations, **Then** the destination is `/admin/workspaces/{workspace}/operations` with that environment's filter active.
|
||||||
|
2. **Given** the operations hub is filtered to one environment, **When** the operator chooses to widen scope, **Then** the route stays workspace-canonical and the environment filter clears explicitly rather than switching panels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 3 - Read workspace-wide and environment-scoped signals on the correct surfaces (Priority: P2)
|
||||||
|
|
||||||
|
As an operator, I want the workspace dashboard to summarize workspace-wide signals and the managed-environment dashboard to summarize one environment's signals so I can decide at the right scope without reconstructing context from mixed pages.
|
||||||
|
|
||||||
|
**Why this priority**: the candidate requires both dashboards, and reusing the current builders is the narrowest honest way to deliver them.
|
||||||
|
|
||||||
|
**Independent Test**: open a workspace dashboard and an environment dashboard for the same workspace, then verify the workspace view aggregates workspace-wide signals while the environment view remains scoped to one environment and shows the full breadcrumb hierarchy.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a workspace has multiple accessible environments, **When** the operator opens the workspace dashboard, **Then** it shows workspace-wide signals and recent operations rather than one environment's dashboard state.
|
||||||
|
2. **Given** the operator opens one managed-environment dashboard, **When** the page renders, **Then** the breadcrumb and context shell show `Workspace -> Managed Environment -> page` and the signals remain scoped to that one environment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### User Story 4 - Keep search and authorization truthful after the route cutover (Priority: P3)
|
||||||
|
|
||||||
|
As an operator, I want globally searchable resources and direct URLs to open truthful destinations or disappear from search altogether so the route migration does not leave broken search results or unsafe hints behind.
|
||||||
|
|
||||||
|
**Why this priority**: `WorkspaceResource` and `TenantResource` are already search-eligible by repo truth, and route changes can quietly break that contract if the spec does not pin it down.
|
||||||
|
|
||||||
|
**Independent Test**: open touched global-search results and direct URLs for workspace and managed-environment surfaces, then confirm that authorized results open valid destinations and unauthorized paths remain not found.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a touched searchable resource remains globally searchable, **When** an operator opens a search result, **Then** the destination is a valid page under the canonical workspace-first route family.
|
||||||
|
2. **Given** a touched searchable surface cannot preserve a valid view or edit destination after the route move, **When** the cutover ships, **Then** that surface no longer appears in global search.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- A direct request to `/admin/t/{environment}`, `/admin/tenants/{environment}/required-permissions`, `/admin/w/{workspace}/managed-tenants`, `/admin/operations`, or `/admin/operations/{run}` must fail as not found rather than redirecting, aliasing, or silently restoring the old path family.
|
||||||
|
- If a remembered environment belongs to a different workspace than the newly selected workspace, the environment context must clear before the operator is shown any environment-bound page.
|
||||||
|
- Archived or otherwise non-selectable managed environments must not appear in the workspace-scoped chooser or resolve through the new route family.
|
||||||
|
- Explicit environment filters on workspace-canonical pages must reject inaccessible environments as `404`; stale remembered filters may be discarded, but explicit hostile hints may not quietly widen scope.
|
||||||
|
- Any touched globally searchable resource without an intact view/edit destination under the new route contract must be disabled from global search in the same implementation slice.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
**Constitution alignment (required):** This slice changes routing, Filament tenancy, chooser flow, navigation, and deep-link behavior. It does not introduce Microsoft Graph calls, a new `OperationRun` lifecycle, or new persistence.
|
||||||
|
|
||||||
|
**Constitution alignment (XCUT-001 / PROV-001 / UI-FIL-001):** The slice must reuse the existing shared workspace dashboard, managed-environment dashboard, chooser flow, operations hub, and deep-link builders. No second panel, second route language, or local dashboard framework may appear.
|
||||||
|
|
||||||
|
**Constitution alignment (RBAC-UX):** Workspace membership stays the first boundary and managed-environment access stays the second boundary. Non-members remain `404`, in-scope capability denials remain `403`, and any touched destructive actions continue to require confirmation and server-side authorization.
|
||||||
|
|
||||||
|
**Constitution alignment (TEST-GOV-001):** Proof stays bounded to feature coverage, one narrow browser smoke, and explicit route/URL guard checks. No heavy-governance family is introduced.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-001**: The operator admin runtime MUST configure `Workspace` as the only Filament tenant after this cutover.
|
||||||
|
- **FR-002**: The system MUST stop using `ManagedEnvironment` as a Filament tenant and MUST remove the public `/admin/t/{environment}` route family rather than retaining it as a redirect, alias, or hidden fallback.
|
||||||
|
- **FR-003**: The surviving `/admin` admin panel MUST own the canonical workspace-first environment route family rooted at `/admin/workspaces/{workspace}/environments/{environment}`.
|
||||||
|
- **FR-004**: The admin entrypoint `/admin` MUST resolve to workspace selection or the current workspace dashboard only; it MUST NOT remain a second canonical environment dashboard route.
|
||||||
|
- **FR-005**: The system MUST reuse the existing workspace overview surface as the canonical workspace dashboard and the existing managed-environment dashboard surface as the canonical environment dashboard, rather than creating replacement dashboard systems.
|
||||||
|
- **FR-006**: The workspace-scoped chooser flow MUST open the new canonical environment dashboard route and MUST clear stale cross-workspace environment context before resolving any environment-bound page.
|
||||||
|
- **FR-007**: The workspace dashboard, environment dashboard, and all environment-scoped pages touched by this slice MUST present navigation and breadcrumbs in the order `Workspace -> Managed Environment -> domain page`.
|
||||||
|
- **FR-008**: `/admin/workspaces/{workspace}/operations` MUST be the canonical public operations collection route for the active workspace.
|
||||||
|
- **FR-009**: Environment dashboards and environment-scoped pages MUST open the workspace operations hub through explicit filter/query context, not through environment-panel ownership.
|
||||||
|
- **FR-010**: The operations detail route MUST remain inside the workspace route family so run detail does not depend on the removed `/admin/t` shell.
|
||||||
|
- **FR-011**: Route builders, deep links, and related-navigation helpers that currently emit `panel: 'tenant'`, `/admin/t`, or `/admin/tenants/{environment}` URLs MUST emit only the new canonical workspace-first routes.
|
||||||
|
- **FR-012**: `TenantPageCategory`, `EnsureWorkspaceSelected`, `EnsureFilamentTenantSelected`, `ResolvesPanelTenantContext`, and related current-context seams MUST treat the workspace-first environment family as the only active environment-bound route language.
|
||||||
|
- **FR-013**: The slice MUST preserve the current `WorkspaceOverviewBuilder` and `TenantDashboardSummaryBuilder` ownership of workspace-wide and environment-scoped summary signals.
|
||||||
|
- **FR-014**: Any touched globally searchable resource MUST preserve a valid view or edit destination under the new route contract or be disabled from global search in the same slice.
|
||||||
|
- **FR-015**: `WorkspaceResource` and `TenantResource` MUST remain globally searchable only if their view/edit destinations stay valid under the cutover route ownership.
|
||||||
|
- **FR-016**: The slice MUST NOT introduce compatibility routes, aliases, dual-panel ownership, dual route readers, hidden `/admin/t` absorption, or spillover into reserved Specs `281`-`287`.
|
||||||
|
- **FR-017**: The legacy chooser route `/admin/w/{workspace}/managed-tenants` MUST be removed rather than preserved as a redirect, alias, or hidden fallback reader.
|
||||||
|
- **FR-018**: The legacy operations routes `/admin/operations` and `/admin/operations/{run}` MUST be removed in favor of `/admin/workspaces/{workspace}/operations` and `/admin/workspaces/{workspace}/operations/{run}` with no redirects, aliases, or dual-route ownership.
|
||||||
|
|
||||||
|
### Authorization and Safety Requirements
|
||||||
|
|
||||||
|
- **AR-001**: Workspace membership MUST remain the first access boundary for `/admin/workspaces/{workspace}/...` routes.
|
||||||
|
- **AR-002**: Managed-environment access MUST remain the second access boundary for `/admin/workspaces/{workspace}/environments/{environment}/...` routes.
|
||||||
|
- **AR-003**: A mismatched `{workspace}` and `{environment}` pair MUST resolve as `404` even if the actor has access to one of them individually.
|
||||||
|
- **AR-004**: Workspace-canonical pages that accept explicit environment filters MUST reject inaccessible environment hints as `404` and must not silently widen scope.
|
||||||
|
- **AR-005**: Any destructive action touched while retargeting dashboard or page links MUST remain server-authorized and require confirmation.
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
|
||||||
|
- **NFR-001**: Filament remains v5 on Livewire v4.
|
||||||
|
- **NFR-002**: Provider registration remains in `apps/platform/bootstrap/providers.php`; if `TenantPanelProvider` is removed from operator routing, no registration is moved to `bootstrap/app.php`.
|
||||||
|
- **NFR-003**: Existing admin theme and asset registration remain unchanged; the slice introduces no new asset pipeline or deployment step beyond the current Filament asset practice.
|
||||||
|
- **NFR-004**: The cutover must preserve Action Surface Contract, UI-FIL-001, and UX-001 expectations for touched Filament pages/resources without introducing ad hoc Blade/Tailwind design systems or redundant inspect actions.
|
||||||
|
- **NFR-005**: The route cutover must stay reviewable as one bounded slice and must not silently absorb provider extraction, artifact retargeting, RBAC redesign, copy neutralization, or quality-gate work reserved for later specs.
|
||||||
|
|
||||||
|
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||||
|
|
||||||
|
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| Workspace dashboard | `WorkspaceOverview` under `/admin/workspaces/{workspace}` | none | body CTA cards and quick actions only | none | none | existing workspace zero-state CTA family only | none | `N/A` | no new audit behavior | Reuses `WorkspaceOverviewBuilder`; action-surface exemptions remain intact |
|
||||||
|
| Workspace-scoped environment chooser | chooser surface under `/admin/workspaces/{workspace}/environments` | none | environment card/button opens the environment dashboard | none | none | workspace switch or no-access guidance only | none | `N/A` | no new audit behavior required by this slice | Reuses the existing chooser pattern; selection must not jump into a second panel |
|
||||||
|
| Managed-environment dashboard | `TenantDashboard` under `/admin/workspaces/{workspace}/environments/{environment}` | `Primary follow-up`, `More`, `Request support`, `Open support diagnostics` | `N/A` | none | none | existing dashboard empty-state behavior only | same as header actions | `N/A` | existing support-request audit behavior only | Action hierarchy and support flows stay intact; route and breadcrumb ownership change only |
|
||||||
|
| Workspace operations hub | `Monitoring\Operations` under `/admin/workspaces/{workspace}/operations` | `Scope operations`, `Back to ...`, `Show all environments` | clickable row to run detail | none | none | existing operations empty-state only | existing run-viewer contract only | `N/A` | no new audit behavior | Filter and back-link context replace environment-panel routing; no new bulk or destructive actions |
|
||||||
|
|
||||||
|
All other environment-scoped resources and diagnostic pages that today live under `panel: 'tenant'` or `/admin/tenants/{environment}` keep their current local action contracts. This slice changes their route ownership and breadcrumb shell only and must not introduce new redundant View actions, placeholder action groups, or new destructive actions.
|
||||||
|
|
||||||
|
### Key Entities *(include if feature involves data)*
|
||||||
|
|
||||||
|
- **Workspace**: the operator's primary admin context and the only Filament tenant after this cutover; owns the canonical workspace dashboard, environment chooser, and workspace operations hub.
|
||||||
|
- **Managed Environment**: the environment-scoped managed target inside one workspace; owns the canonical environment dashboard route and remains the nested context for environment-scoped pages and explicit environment filters.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-001**: All touched operator entry and drillthrough flows open workspace-first environment URLs, and no operator-visible `/admin/t/` link remains on touched admin surfaces.
|
||||||
|
- **SC-002**: An operator can go from workspace selection to one managed-environment dashboard and then into the workspace operations hub with preserved scope in three interactions or fewer.
|
||||||
|
- **SC-003**: All touched environment-scoped pages present breadcrumb/context order `Workspace -> Managed Environment -> page`, and direct wrong-workspace or wrong-environment access reveals no data.
|
||||||
|
- **SC-004**: Touched globally searchable resources either open a valid destination on first attempt under the new route family or no longer appear in global search.
|
||||||
221
specs/280-workspace-tenancy-environment-routing/tasks.md
Normal file
221
specs/280-workspace-tenancy-environment-routing/tasks.md
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
---
|
||||||
|
description: "Task list for Filament Workspace Tenancy & Environment Routing Cutover"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tasks: Filament Workspace Tenancy & Environment Routing Cutover
|
||||||
|
|
||||||
|
**Input**: Design documents from `specs/280-workspace-tenancy-environment-routing/`
|
||||||
|
**Prerequisites**: `specs/280-workspace-tenancy-environment-routing/spec.md`, `specs/280-workspace-tenancy-environment-routing/plan.md`, `specs/280-workspace-tenancy-environment-routing/checklists/requirements.md`, `specs/280-workspace-tenancy-environment-routing/research.md`, `specs/280-workspace-tenancy-environment-routing/data-model.md`, `specs/280-workspace-tenancy-environment-routing/quickstart.md`, `specs/280-workspace-tenancy-environment-routing/contracts/workspace-tenancy-environment-routing.logical.openapi.yaml`
|
||||||
|
|
||||||
|
**Tests**: REQUIRED (Pest). Keep proof bounded to `apps/platform/tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php`, `apps/platform/tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php`, `apps/platform/tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php`, `apps/platform/tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php`, `apps/platform/tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php`, and `apps/platform/tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php`.
|
||||||
|
**Operations**: No new `OperationRun` family. Reuse `apps/platform/app/Support/OperationRunLinks.php`, `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php`, and `apps/platform/app/Filament/Pages/Monitoring/Operations.php` for workspace-first operations route ownership.
|
||||||
|
**RBAC**: Workspace membership remains the first `404` boundary, managed-environment access remains the second `404` boundary, and in-scope capability denials stay `403`.
|
||||||
|
**Shared Pattern Reuse**: Reuse `WorkspaceOverviewBuilder`, `TenantDashboardSummaryBuilder`, `ManagedTenantsLanding`, `ChooseTenant`, `WorkspaceRedirectResolver`, `OperationRunLinks`, and `RelatedNavigationResolver`. Do not add compatibility routes, dual-panel fallbacks, or replacement dashboards.
|
||||||
|
**Filament / Panel Guardrails**: Filament remains v5 on Livewire v4. Provider registration remains in `apps/platform/bootstrap/providers.php`. `Workspace` becomes the only Filament tenant for operator routing. Any touched searchable resource must keep a valid view/edit destination or disable global search in the same slice. Any touched destructive action must preserve `->requiresConfirmation()` plus current server authorization. Asset strategy stays unchanged.
|
||||||
|
**Compatibility Posture**: Reject `/admin/t` compatibility routes, `/admin/tenants/{environment}` aliases, redirect shims, dual-panel ownership, and hidden route fallbacks. Keep Specs `281` through `287` deferred.
|
||||||
|
**Organization**: Tasks are grouped by user story so panel tenancy, workspace-first environment routing, workspace-first operations routing, dashboard/context rebinding, and search/legacy guardrails remain independently testable.
|
||||||
|
**Review Outcome**: `implementation-ready`
|
||||||
|
**Workflow Outcome**: `keep`
|
||||||
|
**Test-governance Outcome**: `keep`
|
||||||
|
|
||||||
|
## Test Governance Checklist
|
||||||
|
|
||||||
|
- [x] Lane assignment stays `fast-feedback`, `confidence`, and one narrow `browser` lane.
|
||||||
|
- [x] New or changed tests stay in the smallest honest families under `apps/platform/tests/Feature/` plus one browser smoke file only.
|
||||||
|
- [x] Workspace and managed-environment fixtures remain explicit; no compatibility fixtures, dual-panel defaults, or hidden route fallbacks become shared setup.
|
||||||
|
- [x] Planned validation commands match `spec.md`, `plan.md`, and `quickstart.md` exactly.
|
||||||
|
- [x] `standard-native-filament`, `global-context-shell`, and `monitoring-state-page` expectations stay explicit for touched surfaces.
|
||||||
|
- [x] Any attempt to absorb Specs `281` through `287` resolves as `split` or `reject-or-split`, not hidden scope.
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Context)
|
||||||
|
|
||||||
|
**Purpose**: Confirm the bounded cutover inventory, the proving files, and the explicit no-compatibility posture before runtime edits begin.
|
||||||
|
|
||||||
|
- [ ] T001 Review `specs/280-workspace-tenancy-environment-routing/spec.md`, `plan.md`, `checklists/requirements.md`, `research.md`, `data-model.md`, `quickstart.md`, and `contracts/workspace-tenancy-environment-routing.logical.openapi.yaml` together so implementation stays on Spec 280 only.
|
||||||
|
- [ ] T002 [P] Confirm the current panel-provider and registration seams in `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, and `apps/platform/bootstrap/providers.php` before changing operator tenancy.
|
||||||
|
- [ ] T003 [P] Confirm the current entry, chooser, and route-language seams in `apps/platform/routes/web.php`, `apps/platform/app/Filament/Pages/ChooseWorkspace.php`, `apps/platform/app/Filament/Pages/ChooseTenant.php`, `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantsLanding.php`, and `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php`.
|
||||||
|
- [ ] T004 [P] Confirm the current context-classification seams in `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php`, `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`, `apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php`, and `apps/platform/app/Support/Tenants/TenantPageCategory.php`.
|
||||||
|
- [ ] T005 [P] Confirm the current dashboard and operations link owners in `apps/platform/app/Filament/Pages/WorkspaceOverview.php`, `apps/platform/app/Filament/Pages/TenantDashboard.php`, `apps/platform/app/Filament/Pages/TenantRequiredPermissions.php`, `apps/platform/app/Filament/Pages/Monitoring/Operations.php`, `apps/platform/app/Support/OperationRunLinks.php`, and `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php`.
|
||||||
|
- [ ] T006 [P] Confirm the touched global-search and deferred-scope surfaces in `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php`, `apps/platform/app/Filament/Resources/TenantResource.php`, and `specs/280-workspace-tenancy-environment-routing/checklists/requirements.md` so Specs `281` through `287` remain explicitly out of scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Establish the proving suite and the shared workspace-first route skeleton that all stories depend on.
|
||||||
|
|
||||||
|
**Critical**: No user-story work should begin until this phase is complete.
|
||||||
|
|
||||||
|
- [ ] T007 [P] Add failing coverage in `apps/platform/tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php` for `Workspace` as the only Filament tenant, `/admin` entry ownership, `TenantPanelProvider` retirement from public operator routing, and provider registration expectations in `apps/platform/bootstrap/providers.php`.
|
||||||
|
- [ ] T008 [P] Add failing coverage in `apps/platform/tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php` for `/admin/workspaces/{workspace}/environments`, `/admin/workspaces/{workspace}/environments/{environment}`, stale cross-workspace environment clearing, archived-environment exclusion, and wrong-workspace `404` behavior.
|
||||||
|
- [ ] T009 [P] Add failing coverage in `apps/platform/tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php` for `/admin/workspaces/{workspace}/operations`, `managed_environment_id` filtering, `Show all environments` widening, workspace-safe run detail routes, and hostile filter `404` behavior.
|
||||||
|
- [ ] T010 [P] Add failing coverage in `apps/platform/tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php` for workspace-dashboard versus environment-dashboard signal ownership and `Workspace -> Managed Environment -> page` breadcrumb/context ordering.
|
||||||
|
- [ ] T011 [P] Add failing guard coverage in `apps/platform/tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php` for `/admin/t`, `/admin/tenants/{environment}/required-permissions`, `/admin/w/{workspace}/managed-tenants`, `/admin/operations`, `/admin/operations/{run}`, `panel: 'tenant'`, `TenantPanelProvider::class` registration in `apps/platform/bootstrap/providers.php`, compatibility redirects, aliases, dual-panel fallbacks, and the searchable-destination rule for touched resources.
|
||||||
|
- [ ] T012 [P] Add the narrow browser smoke in `apps/platform/tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php` for workspace selection, workspace-scoped environment choice, managed-environment dashboard entry, and workspace-operations drillthrough on the surviving admin panel.
|
||||||
|
- [ ] T013 Establish the one-panel workspace-first route skeleton in `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, `apps/platform/bootstrap/providers.php`, and `apps/platform/routes/web.php` with no compatibility aliases, redirect shims, or dual-panel fallback.
|
||||||
|
- [ ] T014 Update `apps/platform/app/Filament/Pages/ChooseWorkspace.php` and `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php` so `/admin` resolves only to workspace selection or `/admin/workspaces/{workspace}` before story-specific environment routing work begins.
|
||||||
|
|
||||||
|
**Checkpoint**: The proving files exist, `/admin` entry ownership is workspace-first, and the implementation has a single admin-panel route skeleton to extend.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 - Enter an environment without leaving the workspace admin panel (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Operators choose a workspace, then open one managed environment through the surviving admin panel and land on the canonical workspace-first environment dashboard.
|
||||||
|
|
||||||
|
**Independent Test**: Select a workspace, open `/admin/workspaces/{workspace}/environments`, choose one managed environment, and confirm the destination is `/admin/workspaces/{workspace}/environments/{environment}` while `/admin/t/{environment}` stays not found.
|
||||||
|
|
||||||
|
### Tests for User Story 1
|
||||||
|
|
||||||
|
- [ ] T015 [P] [US1] Extend `apps/platform/tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php` after T013-T014 to prove the public chooser and environment entry stay on the `admin` panel and direct `/admin/t/{environment}` requests return `404`.
|
||||||
|
- [ ] T016 [P] [US1] Extend `apps/platform/tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php` after T013-T014 to prove chooser submission, managed-environment dashboard resolution, and wrong-workspace route binding remain `404`.
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [ ] T017 [US1] Rework `apps/platform/app/Filament/Pages/ChooseTenant.php` and `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantsLanding.php` so `/admin/workspaces/{workspace}/environments` is the only public environment chooser and stale cross-workspace remembered environment context is cleared before resolution.
|
||||||
|
- [ ] T018 [US1] Move managed-environment dashboard and required-permissions route ownership in `apps/platform/app/Filament/Pages/TenantDashboard.php`, `apps/platform/app/Filament/Pages/TenantRequiredPermissions.php`, and `apps/platform/routes/web.php` to `/admin/workspaces/{workspace}/environments/{environment}` with no `/admin/tenants/{environment}` compatibility reader.
|
||||||
|
- [ ] T019 [US1] Update workspace-to-environment URL generation in `apps/platform/app/Filament/Pages/ChooseTenant.php`, `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php`, `apps/platform/app/Filament/Pages/TenantDashboard.php`, and any touched environment page classes under `apps/platform/app/Filament/Pages/` so no entry flow emits `panel: 'tenant'` or `/admin/t` destinations.
|
||||||
|
|
||||||
|
**Checkpoint**: Workspace selection, environment chooser entry, and managed-environment dashboard routing all stay inside one workspace-first admin panel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 - Move from environment work into workspace operations with explicit scope (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Environment dashboards and environment pages open the workspace operations hub through canonical workspace routes while preserving explicit environment context.
|
||||||
|
|
||||||
|
**Independent Test**: From a managed-environment dashboard or touched environment page, open operations and confirm the destination is `/admin/workspaces/{workspace}/operations` with the correct `managed_environment_id` filter and workspace-safe back-navigation.
|
||||||
|
|
||||||
|
### Tests for User Story 2
|
||||||
|
|
||||||
|
- [ ] T020 [P] [US2] Extend `apps/platform/tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php` to prove environment dashboards and touched environment pages open `/admin/workspaces/{workspace}/operations` with explicit `managed_environment_id`, preserve run-detail ownership under `/admin/workspaces/{workspace}/operations/{run}`, widen scope only through explicit user action, and keep `/admin/operations` plus `/admin/operations/{run}` unavailable.
|
||||||
|
|
||||||
|
### Implementation for User Story 2
|
||||||
|
|
||||||
|
- [ ] T021 [US2] Retarget `apps/platform/app/Support/OperationRunLinks.php` and `apps/platform/app/Support/Navigation/RelatedNavigationResolver.php` so operations collection/detail links emit only workspace-first routes with explicit environment filter and return-context data.
|
||||||
|
- [ ] T022 [US2] Update `apps/platform/app/Filament/Pages/Monitoring/Operations.php` so workspace collection/detail ownership, `managed_environment_id` hydration, `Show all environments` behavior, and hostile filter handling match the new workspace-first route contract.
|
||||||
|
- [ ] T023 [US2] Update operations entry actions in `apps/platform/app/Filament/Pages/TenantDashboard.php`, `apps/platform/app/Filament/Pages/TenantRequiredPermissions.php`, and any touched environment-scoped page classes under `apps/platform/app/Filament/Pages/` so they delegate through the shared workspace operations builders instead of local tenant-panel URLs.
|
||||||
|
|
||||||
|
**Checkpoint**: Operations links, run-detail links, and return context are all workspace-canonical while preserving explicit environment scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 - Read workspace-wide and environment-scoped signals on the correct surfaces (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Workspace-wide signals stay on the workspace dashboard, environment-scoped signals stay on the managed-environment dashboard, and breadcrumbs/context bars reflect the new route ownership.
|
||||||
|
|
||||||
|
**Independent Test**: Open `/admin/workspaces/{workspace}` and `/admin/workspaces/{workspace}/environments/{environment}` for the same workspace and verify the workspace dashboard shows workspace-wide signals while the environment dashboard stays scoped to one environment with `Workspace -> Managed Environment -> page` context.
|
||||||
|
|
||||||
|
### Tests for User Story 3
|
||||||
|
|
||||||
|
- [ ] T024 [P] [US3] Extend `apps/platform/tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php` to prove workspace-wide dashboard signals remain on `WorkspaceOverview`, environment-scoped signals remain on `TenantDashboard`, and breadcrumb/context ordering becomes `Workspace -> Managed Environment -> page`.
|
||||||
|
|
||||||
|
### Implementation for User Story 3
|
||||||
|
|
||||||
|
- [ ] T025 [US3] Rebind `apps/platform/app/Filament/Pages/WorkspaceOverview.php` and `apps/platform/app/Filament/Pages/TenantDashboard.php` to the canonical `/admin/workspaces/{workspace}` and `/admin/workspaces/{workspace}/environments/{environment}` routes while preserving `WorkspaceOverviewBuilder` and `TenantDashboardSummaryBuilder` ownership.
|
||||||
|
- [ ] T026 [US3] Update `apps/platform/app/Http/Middleware/EnsureWorkspaceSelected.php`, `apps/platform/app/Support/Middleware/EnsureFilamentTenantSelected.php`, `apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php`, and `apps/platform/app/Support/Tenants/TenantPageCategory.php` so workspace-first environment routes are the only active environment-bound language and remembered cross-workspace environment context cannot leak.
|
||||||
|
- [ ] T027 [US3] Update context bars, breadcrumbs, and chooser/dashboard CTA links in `apps/platform/app/Filament/Pages/ChooseTenant.php`, `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantsLanding.php`, `apps/platform/app/Filament/Pages/WorkspaceOverview.php`, and `apps/platform/app/Filament/Pages/TenantDashboard.php` so the new route ownership reads `Workspace -> Managed Environment -> domain page` everywhere this slice touches.
|
||||||
|
|
||||||
|
**Checkpoint**: Workspace dashboard, managed-environment dashboard, and current-context shells all present the correct scope and breadcrumb truth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: User Story 4 - Keep search and authorization truthful after the route cutover (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: Searchable resources, direct URLs, and legacy-route guards stay truthful after the cutover, with no broken search results and no hidden compatibility routes.
|
||||||
|
|
||||||
|
**Independent Test**: Open touched global-search results and direct workspace/environment URLs, confirm authorized destinations resolve under the workspace-first routes, and confirm `/admin/t/{environment}` plus `/admin/tenants/{environment}/required-permissions` remain `404`.
|
||||||
|
|
||||||
|
### Tests for User Story 4
|
||||||
|
|
||||||
|
- [ ] T028 [P] [US4] Extend `apps/platform/tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php` to prove no compatibility routes, aliases, redirects, or dual-panel fallbacks survive for `/admin/t`, `/admin/tenants/{environment}/required-permissions`, `/admin/w/{workspace}/managed-tenants`, or `/admin/operations` plus `/admin/operations/{run}`.
|
||||||
|
- [ ] T029 [P] [US4] Extend `apps/platform/tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php` and `apps/platform/tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php` to cover `WorkspaceResource` and `TenantResource` global-search destinations plus `404` versus `403` behavior for direct workspace/environment URLs.
|
||||||
|
|
||||||
|
### Implementation for User Story 4
|
||||||
|
|
||||||
|
- [ ] T030 [US4] Update `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php` and `apps/platform/app/Filament/Resources/TenantResource.php` so each touched resource keeps a valid view/edit destination under workspace-first routing or disables global search in the same slice.
|
||||||
|
- [ ] T031 [US4] Remove remaining legacy-route ownership and panel-language fallbacks from `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, `apps/platform/bootstrap/providers.php`, `apps/platform/routes/web.php`, `apps/platform/app/Support/Workspaces/WorkspaceRedirectResolver.php`, `apps/platform/app/Support/OperationRunLinks.php`, and any touched helpers under `apps/platform/tests/` so Specs `281` through `287` remain deferred instead of absorbed.
|
||||||
|
|
||||||
|
**Checkpoint**: Search, direct URLs, and no-legacy route guards all reflect the final workspace-first contract with no hidden fallback path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Polish & Cross-Cutting Validation
|
||||||
|
|
||||||
|
**Purpose**: Run the exact bounded proof set, perform the final Filament review, and close the cutover without reopening deferred specs.
|
||||||
|
|
||||||
|
- [ ] T032 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Feature/Workspace/WorkspaceFilamentTenancyCutoverTest.php tests/Feature/ManagedEnvironment/WorkspaceFirstEnvironmentRoutingTest.php tests/Feature/Monitoring/WorkspaceOperationsEnvironmentContextTest.php tests/Feature/Navigation/WorkspaceEnvironmentBreadcrumbsTest.php tests/Feature/Guards/LegacyAdminTenantRouteRemovalGuardTest.php)`.
|
||||||
|
- [ ] T033 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php)`.
|
||||||
|
- [ ] T034 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail bin pint --dirty --format agent)`.
|
||||||
|
- [ ] T035 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/t/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`, `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/tenants/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`, `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/w/' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"`, and `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings '/admin/operations' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"` and confirm only intentional removal-guard output remains.
|
||||||
|
- [ ] T036 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings "panel: 'tenant'" "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/routes" "$REPO_ROOT/apps/platform/bootstrap"` and `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n --fixed-strings 'TenantPanelProvider::class' "$REPO_ROOT/apps/platform/bootstrap/providers.php"` and confirm only intentional removal-guard output remains.
|
||||||
|
- [ ] T037 [P] Review `apps/platform/app/Providers/Filament/AdminPanelProvider.php`, `apps/platform/app/Providers/Filament/TenantPanelProvider.php`, `apps/platform/bootstrap/providers.php`, `apps/platform/app/Filament/Resources/Workspaces/WorkspaceResource.php`, `apps/platform/app/Filament/Resources/TenantResource.php`, and touched Filament pages/actions to confirm Filament v5 / Livewire v4 compliance, provider registration stays in `apps/platform/bootstrap/providers.php`, the global-search destination rule is satisfied, touched destructive actions still preserve `->requiresConfirmation()` plus authorization, and no asset strategy or deploy-step change was introduced.
|
||||||
|
- [ ] T038 [P] Record the implementation close-out in `specs/280-workspace-tenancy-environment-routing/checklists/requirements.md` or the active PR notes confirming no compatibility routes, aliases, redirects, or dual-panel fallback shipped and Specs `281` through `287` remain explicitly deferred.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- **Phase 1 (Setup)**: no dependencies; start immediately.
|
||||||
|
- **Phase 2 (Foundational)**: depends on Phase 1 and blocks all story work.
|
||||||
|
- **Phase 3 (US1)**: depends on Phase 2 and establishes the canonical environment-entry contract.
|
||||||
|
- **Phase 4 (US2)**: depends on Phase 2 and should follow US1 route ownership so operations links target the finished workspace-first environment family.
|
||||||
|
- **Phase 5 (US3)**: depends on US1 and should land with or immediately after US2 so dashboards, breadcrumbs, and middleware all reflect the same route contract.
|
||||||
|
- **Phase 6 (US4)**: depends on US1 through US3 so search, authorization, and no-legacy guards prove the final route language rather than an intermediate state.
|
||||||
|
- **Phase 7 (Polish)**: depends on all desired user stories being complete.
|
||||||
|
|
||||||
|
### User Story Dependencies
|
||||||
|
|
||||||
|
- **US1 (P1)**: independently testable after Phase 2 and is the first required implementation increment.
|
||||||
|
- **US2 (P1)**: independently testable after Phase 2, but should ship after or with US1 because operations links rely on the canonical workspace-first environment family.
|
||||||
|
- **US3 (P2)**: independently testable after US1 and should land before US4 so breadcrumb and context truth are final.
|
||||||
|
- **US4 (P3)**: independently testable after US1 through US3 and closes search, authorization, and legacy-route honesty.
|
||||||
|
|
||||||
|
### Within Each User Story
|
||||||
|
|
||||||
|
- Write or extend the listed Pest coverage first and make it fail for the intended gap.
|
||||||
|
- Apply the smallest shared-seam changes needed to satisfy the story without reopening Specs `281` through `287`.
|
||||||
|
- Re-run the narrowest relevant validation command for that story before moving to the next story.
|
||||||
|
|
||||||
|
## Parallel Execution Examples
|
||||||
|
|
||||||
|
- **Setup**: T002 through T006 can run in parallel once T001 sets the bounded scope.
|
||||||
|
- **Foundational**: T007 through T012 can run in parallel before T013 and T014 converge the route skeleton.
|
||||||
|
- **US1**: T015 and T016 can run in parallel; T017 through T019 should merge serially around chooser and route-owner files.
|
||||||
|
- **US2**: T020 can run alongside T021, then T022 and T023 should follow once shared operations builders are updated.
|
||||||
|
- **US3**: T024 can run in parallel with T025, then T026 and T027 should merge serially around the shared middleware and page-context seams.
|
||||||
|
- **US4**: T028 and T029 can run in parallel; T030 and T031 should follow once the final route contract is stable.
|
||||||
|
- **Polish**: T032 through T037 can run in parallel after implementation is complete; T038 should close out last.
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### Suggested MVP Scope
|
||||||
|
|
||||||
|
- MVP = **US1**. Land the single surviving admin panel plus workspace-first environment entry before widening into operations, dashboard-context hardening, and search/no-legacy enforcement.
|
||||||
|
|
||||||
|
### Incremental Delivery
|
||||||
|
|
||||||
|
1. Complete Phase 1 and Phase 2.
|
||||||
|
2. Deliver US1 so environment entry no longer depends on `/admin/t`.
|
||||||
|
3. Deliver US2 so operations links become workspace-canonical with explicit environment scope.
|
||||||
|
4. Deliver US3 so dashboard ownership, breadcrumbs, and middleware all converge on the same workspace-first context.
|
||||||
|
5. Deliver US4 to lock search, authorization, and legacy-route honesty.
|
||||||
|
6. Finish with the exact validation commands and the final Filament review in Phase 7.
|
||||||
|
|
||||||
|
### Team Strategy
|
||||||
|
|
||||||
|
1. Parallelize the failing test work first.
|
||||||
|
2. Serialize merges around `apps/platform/app/Providers/Filament/`, `apps/platform/routes/web.php`, and the middleware/context helpers to avoid conflicting route-language edits.
|
||||||
|
3. Reject any implementation branch that introduces compatibility routes, dual-panel ownership, or hidden follow-up work from Specs `281` through `287`.
|
||||||
|
|
||||||
|
## Deferred Follow-Ups / Non-Goals
|
||||||
|
|
||||||
|
- Spec `281` provider connection, provider scope, and Microsoft profile extraction
|
||||||
|
- Spec `282` governance artifact retargeting to `ManagedEnvironment`
|
||||||
|
- Spec `283` provider capability registry work
|
||||||
|
- Spec `284` provider-neutral artifact source taxonomy work
|
||||||
|
- Spec `285` workspace-first RBAC and environment access redesign
|
||||||
|
- Spec `286` UI copy, IA, and localization neutralization beyond route/context truth
|
||||||
|
- Spec `287` cutover quality gates and broader no-legacy enforcement beyond this feature-local proof
|
||||||
Loading…
Reference in New Issue
Block a user