TenantAtlas/specs/279-workspace-managed-environment-core/plan.md
ahmido e64bae9cfc feat: cut over tenant core to managed environments (#335)
## 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
2026-05-07 06:38:14 +00:00

259 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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