# 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.