## Summary - replace the legacy Tenant and TenantMembership core models with ManagedEnvironment and ManagedEnvironmentMembership - propagate the managed environment naming and key changes across Filament resources, pages, controllers, jobs, models, and supporting runtime paths - add feature 279 spec artifacts and focused managed-environment test coverage for model behavior, route binding, panel context, authorization, and legacy guardrails ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentAuthorizationTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentRouteBindingTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentContextResolverTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentModelTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` ## Notes - branch pushed from commit `1123b122` - browser smoke test file was added but not run in this pass Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #335
259 lines
21 KiB
Markdown
259 lines
21 KiB
Markdown
# Implementation Plan: Workspace-first Managed Environment Core Cutover
|
||
|
||
**Branch**: `279-workspace-managed-environment-core` | **Date**: 2026-05-06 | **Spec**: [spec.md](./spec.md)
|
||
**Input**: Feature specification from `specs/279-workspace-managed-environment-core/spec.md`
|
||
|
||
## Summary
|
||
|
||
Prepare the first reserved cutover slice that replaces `Tenant` as the active managed-target core with `ManagedEnvironment`. The narrow implementation path is a breaking in-place cutover: introduce the new root entity, retarget current core foreign keys and memberships to `managed_environment_id`, replace current context seams with `ManagedEnvironment`, and keep the product bootable by temporarily rebinding the existing `/admin/t/{environment}` shell to `ManagedEnvironment` until Spec `280` owns the final Workspace-tenancy and public workspace-first route rewrite.
|
||
|
||
This plan stays intentionally narrower than the full cutover pack. Filament remains v5 on Livewire v4, provider registration remains unchanged in `apps/platform/bootstrap/providers.php`, no compatibility layer is allowed, no new asset strategy is expected, and provider-profile extraction, artifact retargeting, RBAC redesign, copy neutralization, and cutover quality-gate automation remain explicit follow-up specs.
|
||
|
||
## Inherited Baseline / Explicit Delta
|
||
|
||
### Inherited baseline
|
||
|
||
- `Workspace` already exists as the SaaS and organization context.
|
||
- `Tenant` is currently the active managed-target core across models, route binding, memberships, Filament tenancy, current-target selection, and tenant-owned query helpers.
|
||
- `TenantPanelProvider` currently binds Filament tenancy directly to `Tenant::class` on the `/admin/t` path family.
|
||
- Current helper seams such as `WorkspaceContext`, `ResolvesPanelTenantContext`, `InteractsWithTenantOwnedRecords`, `ScopesGlobalSearchToTenant`, and `TenantOwnedModelFamilies` all assume `Tenant` as the active managed target.
|
||
- Current provider and governance tables still use `tenant_id` in some follow-up lanes, but their semantic retargeting remains outside this package.
|
||
|
||
### Explicit delta in this plan
|
||
|
||
- Introduce `ManagedEnvironment` as the new active managed-target root inside a workspace.
|
||
- Replace active core use of `Tenant` and `tenant_id` with `ManagedEnvironment` and `managed_environment_id` in core-owned paths.
|
||
- Replace current environment-membership seams and current-target context helpers in place rather than adding adapters.
|
||
- Rebind the current `/admin/t/{environment}` shell to `ManagedEnvironment` as an explicit temporary bridge while Spec `280` retains ownership of the final `Workspace` Filament-tenancy and public route-family rewrite.
|
||
- Keep provider-specific identity off `ManagedEnvironment`; follow-up Spec `281` owns the remaining provider extraction.
|
||
|
||
## Technical Context
|
||
|
||
**Language/Version**: PHP 8.4, Laravel 12
|
||
**Primary Dependencies**: Filament v5, Livewire v4, Pest v4, current workspace and authorization services, current panel providers, current tenant-owned query helpers
|
||
**Storage**: PostgreSQL with destructive pre-production cutover from `tenants`/`tenant_id` to `managed_environments`/`managed_environment_id` in core-owned paths
|
||
**Testing**: Pest unit, feature, and one browser smoke
|
||
**Validation Lanes**: fast-feedback, confidence, browser
|
||
**Target Platform**: Laravel monolith in `apps/platform`
|
||
**Project Type**: web application
|
||
**Performance Goals**: keep current environment-scoped flows functionally equivalent after the cutover; no new queue or remote-call path
|
||
**Constraints**: no compatibility shims, no dual columns, no legacy alias model, no new provider-specific fields on `ManagedEnvironment`, no second route family, and no broadened route/IA rewrite in this slice
|
||
**Scale/Scope**: one breaking core entity replacement, current context and schema retargeting, and the minimum shell/route binding needed to keep the product operable
|
||
|
||
## Likely Affected Repo Surfaces
|
||
|
||
- `apps/platform/app/Models/Tenant.php`, `Workspace.php`, `TenantMembership.php`, and any current core-owned model family using `tenant_id` as the managed-target key.
|
||
- `apps/platform/app/Providers/Filament/TenantPanelProvider.php` and admin routes/pages that assume `Tenant` route binding or current target semantics.
|
||
- `apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php`, `InteractsWithTenantOwnedRecords.php`, and `ScopesGlobalSearchToTenant.php`.
|
||
- Current chooser/context pages such as `ChooseTenant`, current tenant dashboard pages, and any context bar partials that name the active target.
|
||
- `apps/platform/app/Support/WorkspaceIsolation/TenantOwnedModelFamilies.php` as the authoritative inventory anchor for environment-scoped model families.
|
||
- `apps/platform/app/Support/OperationRunLinks.php` and environment-scoped route builders.
|
||
- Commands, fixtures, factories, and tests that still instantiate `Tenant` or search on `tenant_id` as active core truth.
|
||
|
||
## Filament v5 / Panel Notes
|
||
|
||
- **Livewire v4.0+ compliance**: The cutover keeps Filament on Livewire v4 and retargets existing native panel/provider seams only.
|
||
- **Provider registration location**: Provider registration remains unchanged in `apps/platform/bootstrap/providers.php`.
|
||
- **Global search**: Existing global-search resources touched by the cutover must keep safe environment scoping and continue to satisfy the view/edit-page rule where applicable.
|
||
- **Destructive actions**: No new destructive actions are added. Any touched existing destructive actions must preserve `->requiresConfirmation()` and current authorization.
|
||
- **Asset strategy**: No new asset registration is planned. Deployment remains unchanged.
|
||
|
||
## Managed Environment Cutover Fit
|
||
|
||
- Use one breaking in-place cutover rather than a staged alias or compatibility layer.
|
||
- Prefer replacing existing tenant-context helpers and route binding seams in place over adding `ManagedEnvironment` adapters around `Tenant` behavior.
|
||
- Treat the current `/admin/t/{environment}` public path family as a temporary shell-level exception only. The active bound model, route parameter meaning, and current-target naming all change to `ManagedEnvironment`, while Spec `280` owns the final workspace-first route family and the final Workspace Filament-tenancy end state.
|
||
- Keep `ManagedEnvironment` provider-neutral. Fields such as Entra tenant IDs, Graph identifiers, domains, or consent details stay in provider-owned seams.
|
||
|
||
## RBAC / Data Ownership / Auditability Fit
|
||
|
||
- `Workspace` remains the primary SaaS context and access boundary.
|
||
- `ManagedEnvironment` becomes the environment-scoped root entity under a workspace.
|
||
- Existing tenant-membership semantics are retargeted to managed-environment memberships without expanding role or capability scope in this slice.
|
||
- Existing audit and authorization rules remain in force; the cutover changes nouns and keys, not trust boundaries.
|
||
- Any touched audit or log surface must keep environment scope truthful after the key rename.
|
||
|
||
## UI / Surface Guardrail Plan
|
||
|
||
- **Guardrail scope**: changed surfaces
|
||
- **Native vs custom classification summary**: native Filament
|
||
- **Shared-family relevance**: context selection, route links, context bars, global-search scoping, current-target summaries
|
||
- **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 the chooser, diagnostics-second elsewhere, no new raw/debug surface
|
||
- **Raw/support gating plan**: provider-specific metadata remains off `ManagedEnvironment` and out of default chooser/shell disclosure
|
||
- **One-primary-action / duplicate-truth control**: the chooser keeps one dominant `Enter environment` action; the shell shows current context once and avoids duplicating page-level summaries
|
||
- **Handling modes by drift class or surface**: review-mandatory with one documented route exception
|
||
- **Repository-signal treatment**: temporary `/admin/t` retention is exception-required and must be recorded in the active feature close-out
|
||
- **Special surface test profiles**: standard-native-filament, global-context-shell, exception-coded-surface
|
||
- **Required tests or manual smoke**: functional-core, state-contract, manual/browser smoke
|
||
- **Exception path and spread control**: the `/admin/t/{environment}` path retention is the only allowed route exception and must not grow a second compatibility family
|
||
- **Active feature PR close-out entry**: Guardrail
|
||
|
||
## Shared Pattern & System Fit
|
||
|
||
- **Cross-cutting feature marker**: yes
|
||
- **Systems touched**: panel providers, chooser pages, context helpers, query helpers, route builders, current-target summaries, and environment-bound resources
|
||
- **Shared abstractions reused**: existing `WorkspaceContext`, panel providers, capability resolver, current route builders, `TenantOwnedModelFamilies`
|
||
- **New abstraction introduced? why?**: none planned; prefer replacing or renaming tenant-specific seams in place
|
||
- **Why the existing abstraction was sufficient or insufficient**: the seams are correct cutover anchors but are currently encoded with the wrong core noun
|
||
- **Bounded deviation / spread control**: one route-path exception plus one temporary shell-binding bridge only, both owned by this feature and closed by Spec `280`
|
||
|
||
## OperationRun UX Impact
|
||
|
||
- **Touches OperationRun start/completion/link UX?**: yes, existing deep-link resolution only
|
||
- **Central contract reused**: `OperationRunLinks`
|
||
- **Delegated UX behaviors**: environment-aware URL resolution only
|
||
- **Surface-owned behavior kept local**: none
|
||
- **Queued DB-notification policy**: `N/A`
|
||
- **Terminal notification path**: `N/A`
|
||
- **Exception path**: same temporary `/admin/t/{environment}` shell retention described above
|
||
|
||
## Provider Boundary & Portability Fit
|
||
|
||
- **Shared provider/platform boundary touched?**: yes
|
||
- **Provider-owned seams**: current provider identity, Graph consent, provider metadata, Microsoft-specific identifiers
|
||
- **Platform-core seams**: managed-target root entity, workspace-to-environment relationship, route binding, current-target context, operator vocabulary
|
||
- **Neutral platform terms / contracts preserved**: `workspace`, `managed environment`, `provider connection`, `target scope`
|
||
- **Retained provider-specific semantics and why**: provider-specific identity remains where it already belongs until Spec `281` extracts it cleanly
|
||
- **Bounded extraction or follow-up path**: Specs `281`, `284`, and `286`
|
||
|
||
## Constitution Check
|
||
|
||
*GATE: Must pass before implementation begins and again after the design artifacts are complete.*
|
||
|
||
- Inventory-first / snapshot truth: PASS. The cutover changes the managed-target root, not inventory or snapshot semantics.
|
||
- Read/write separation: PASS. No new operator write workflow or remote call path is introduced.
|
||
- Graph contract path: PASS. No new Graph call or contract registry change is required in this slice.
|
||
- Deterministic capabilities: PASS. Current capability semantics remain and are retargeted to managed-environment membership.
|
||
- Workspace and tenant isolation: EXCEPTION-REQUIRED. Workspace remains the primary boundary and managed-environment membership remains the scoped target boundary, but SCOPE-001 still hardcodes `tenant_id` as the required managed-target key until the constitution is amended or an explicit approved exception is recorded for this cutover inventory.
|
||
- RBAC-UX plane separation: PASS. `/admin` and `/system` remain separate.
|
||
- Destructive action discipline: PASS by non-expansion. Existing destructive actions keep current confirmation requirements.
|
||
- Global search safety: PASS if all touched resources keep environment-safe scoping.
|
||
- OperationRun / Ops-UX: PASS. Only deep-link resolution changes.
|
||
- Data minimization: PASS. Provider-specific identity stays out of `ManagedEnvironment`.
|
||
- Test governance: PASS. Planned proof is bounded to unit, feature, browser, and guard checks.
|
||
- Proportionality / no premature abstraction: PASS. The cutover replaces a wrong root concept instead of layering a framework.
|
||
- Persisted truth: PASS. New persistence is justified because the managed-target root has independent product truth.
|
||
- Behavioral state: PASS. `kind` and lifecycle posture exist only to support current target selection and filtering, not a new semantic framework.
|
||
- UI semantics / shared pattern first / Filament-native UI: PASS. Native panel/provider seams remain the first path.
|
||
- Provider boundary: PASS. Neutral core noun is introduced explicitly.
|
||
|
||
**Gate evaluation**: PASS with approved feature-local exception in place.
|
||
|
||
**Post-design re-check**: PASS while `research.md`, `data-model.md`, `quickstart.md`, `contracts/managed-environment-core-cutover.logical.openapi.yaml`, `checklists/requirements.md`, `checklists/constitution-scope-001-exception.md`, and `tasks.md` remain aligned and the approved exception record stays valid.
|
||
|
||
**Implementation gate reference**: `specs/279-workspace-managed-environment-core/checklists/constitution-scope-001-exception.md` is the current approved-exception artifact for this package. Phase 2 may not begin unless that path remains valid or is replaced by the actual constitution-amendment reference.
|
||
|
||
## Test Governance Check
|
||
|
||
- **Test purpose / classification by changed surface**: Unit, Feature, Browser
|
||
- **Affected validation lanes**: fast-feedback, confidence, browser
|
||
- **Why this lane mix is the narrowest sufficient proof**: the cutover changes core identity, panel binding, and shell context. Unit tests prove model/context rules, feature tests prove authorization and route binding, and one browser smoke proves the end-to-end chooser/shell path remains operable.
|
||
- **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/Unit/ManagedEnvironment/ManagedEnvironmentModelTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentContextResolverTest.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/Feature/ManagedEnvironment/ManagedEnvironmentRouteBindingTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentAuthorizationTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.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/Spec279ManagedEnvironmentCoreCutoverSmokeTest.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 'App\Models\Tenant' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/database"`
|
||
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && rg -n -- '->tenant\(Tenant::class' "$REPO_ROOT/apps/platform/app" "$REPO_ROOT/apps/platform/tests" "$REPO_ROOT/apps/platform/database"`
|
||
- **Fixture / helper / factory / seed / context cost risks**: moderate because current tenant fixtures must be replaced, not wrapped
|
||
- **Expensive defaults or shared helper growth introduced?**: no; keep helper replacement explicit and local
|
||
- **Heavy-family additions, promotions, or visibility changes**: none
|
||
- **Surface-class relief / special coverage rule**: one browser smoke required because the shell/context flow changes end-to-end
|
||
- **Closing validation and reviewer handoff**: rerun the exact commands above, confirm the temporary route exception is documented and bounded, confirm `LegacyTenantCoreGuardTest.php` proves `tenant_id` removal only inside the declared core-owned cutover inventory with explicit Spec `281` and `282` exclusions, confirm no provider-specific fields land on `ManagedEnvironment`, confirm any touched globally searchable resource still satisfies Filament’s edit/view eligibility rule or remains out of global search, confirm no new asset registration or deployment-step change was introduced, and confirm provider registration remains unchanged
|
||
- **Budget / baseline / trend follow-up**: contained feature-local increase only
|
||
- **Review-stop questions**: did a compatibility layer appear, did provider-specific identity leak onto `ManagedEnvironment`, did the route exception spread, did any core-owned `Tenant` references survive, did any unapproved `tenant_id` usage remain inside the declared core-owned cutover inventory, and did any touched destructive action lose `->requiresConfirmation()` or current authorization
|
||
- **Escalation path**: `document-in-feature` for the temporary route exception; `follow-up-spec` already assigned for remaining pack work
|
||
- **Active feature PR close-out entry**: Guardrail
|
||
- **Why no dedicated follow-up spec is needed**: this package already owns the first cutover slice; the remaining strategic steps are already reserved as Specs `280`-`287`
|
||
|
||
## Review Checklist Status
|
||
|
||
- **Review checklist artifact**: `checklists/requirements.md`
|
||
- **Review outcome class**: `documentation-required-exception`
|
||
- **Workflow outcome**: `keep`
|
||
- **Test-governance outcome**: `keep`
|
||
- **Escalation rule**: if implementation adds compatibility layers, a second route family, provider-specific fields on `ManagedEnvironment`, or absorbs Spec `280`-`287` scope, flip the workflow outcome to `split` or `reject-or-split`
|
||
|
||
## Rollout Considerations
|
||
|
||
- Land the schema/entity cutover and current-context replacement before broad page/UI cleanup.
|
||
- Keep the temporary `/admin/t/{environment}` path exception explicit and minimal.
|
||
- Replace fixtures, factories, and guard tests in the same cutover PR so mixed core nouns do not linger.
|
||
- Use the feature-local guard tests and grep checks to keep `Tenant` from re-entering core-owned paths while the rest of the pack is still pending.
|
||
|
||
## Risk Controls
|
||
|
||
- Reject any implementation that introduces an alias `Tenant` model, dual columns, or compatibility writes.
|
||
- Reject any implementation that moves provider-specific identity onto `ManagedEnvironment`.
|
||
- Reject any implementation that keeps both `/admin/t/{environment}` and a new workspace-first route family active in the same slice.
|
||
- Reject any implementation that absorbs the broader Spec `280` route/IA rewrite, Spec `281` provider extraction, or Spec `285` RBAC redesign.
|
||
|
||
## Research & Design Outputs
|
||
|
||
- `research.md` records the no-compatibility cutover, temporary route-path exception, provider-boundary discipline, helper-replacement strategy, and proof strategy.
|
||
- `data-model.md` captures the `ManagedEnvironment` entity, membership retargeting, current-context model, and foreign-key cutover rules.
|
||
- `quickstart.md` provides the bounded reviewer flow and exact validation commands.
|
||
- `contracts/managed-environment-core-cutover.logical.openapi.yaml` captures the logical chooser and temporary environment-shell route contract.
|
||
- `checklists/requirements.md` records the review outcome, workflow outcome, and test-governance outcome.
|
||
- `tasks.md` sequences the cutover work and keeps the remaining pack scope deferred.
|
||
|
||
## Project Structure
|
||
|
||
### Documentation (this feature)
|
||
|
||
```text
|
||
specs/279-workspace-managed-environment-core/
|
||
├── checklists/
|
||
│ ├── constitution-scope-001-exception.md
|
||
│ └── requirements.md
|
||
├── contracts/
|
||
│ └── managed-environment-core-cutover.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/
|
||
│ │ │ ├── InteractsWithTenantOwnedRecords.php
|
||
│ │ │ ├── ResolvesPanelTenantContext.php
|
||
│ │ │ └── ScopesGlobalSearchToTenant.php
|
||
│ │ ├── Pages/
|
||
│ │ │ └── ChooseTenant.php
|
||
│ │ └── Resources/
|
||
│ │ └── TenantResource.php
|
||
│ ├── Models/
|
||
│ │ ├── Workspace.php
|
||
│ │ ├── Tenant.php
|
||
│ │ └── ProviderConnection.php
|
||
│ ├── Providers/Filament/
|
||
│ │ ├── AdminPanelProvider.php
|
||
│ │ └── TenantPanelProvider.php
|
||
│ ├── Support/
|
||
│ │ ├── OperationRunLinks.php
|
||
│ │ ├── WorkspaceIsolation/
|
||
│ │ │ └── TenantOwnedModelFamilies.php
|
||
│ │ └── Workspaces/
|
||
│ │ └── WorkspaceContext.php
|
||
│ └── Services/Auth/
|
||
│ └── WorkspaceCapabilityResolver.php
|
||
├── database/
|
||
│ ├── factories/
|
||
│ └── migrations/
|
||
└── tests/
|
||
├── Browser/
|
||
├── Feature/ManagedEnvironment/
|
||
└── Unit/ManagedEnvironment/
|
||
```
|
||
|
||
**Structure decision**: keep the documentation package self-contained under `specs/279-workspace-managed-environment-core/`; implementation later replaces the core managed-target seams in `apps/platform/` directly instead of introducing a separate cutover package or adapter layer. |