21 KiB
Implementation Plan: Workspace-first Managed Environment Core Cutover
Branch: 279-workspace-managed-environment-core | Date: 2026-05-06 | Spec: 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
Workspacealready exists as the SaaS and organization context.Tenantis currently the active managed-target core across models, route binding, memberships, Filament tenancy, current-target selection, and tenant-owned query helpers.TenantPanelProvidercurrently binds Filament tenancy directly toTenant::classon the/admin/tpath family.- Current helper seams such as
WorkspaceContext,ResolvesPanelTenantContext,InteractsWithTenantOwnedRecords,ScopesGlobalSearchToTenant, andTenantOwnedModelFamiliesall assumeTenantas the active managed target. - Current provider and governance tables still use
tenant_idin some follow-up lanes, but their semantic retargeting remains outside this package.
Explicit delta in this plan
- Introduce
ManagedEnvironmentas the new active managed-target root inside a workspace. - Replace active core use of
Tenantandtenant_idwithManagedEnvironmentandmanaged_environment_idin 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 toManagedEnvironmentas an explicit temporary bridge while Spec280retains ownership of the finalWorkspaceFilament-tenancy and public route-family rewrite. - Keep provider-specific identity off
ManagedEnvironment; follow-up Spec281owns 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 usingtenant_idas the managed-target key.apps/platform/app/Providers/Filament/TenantPanelProvider.phpand admin routes/pages that assumeTenantroute binding or current target semantics.apps/platform/app/Filament/Concerns/ResolvesPanelTenantContext.php,InteractsWithTenantOwnedRecords.php, andScopesGlobalSearchToTenant.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.phpas the authoritative inventory anchor for environment-scoped model families.apps/platform/app/Support/OperationRunLinks.phpand environment-scoped route builders.- Commands, fixtures, factories, and tests that still instantiate
Tenantor search ontenant_idas 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
ManagedEnvironmentadapters aroundTenantbehavior. - 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 toManagedEnvironment, while Spec280owns the final workspace-first route family and the final Workspace Filament-tenancy end state. - Keep
ManagedEnvironmentprovider-neutral. Fields such as Entra tenant IDs, Graph identifiers, domains, or consent details stay in provider-owned seams.
RBAC / Data Ownership / Auditability Fit
Workspaceremains the primary SaaS context and access boundary.ManagedEnvironmentbecomes 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
ManagedEnvironmentand out of default chooser/shell disclosure - One-primary-action / duplicate-truth control: the chooser keeps one dominant
Enter environmentaction; 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/tretention 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
281extracts it cleanly - Bounded extraction or follow-up path: Specs
281,284, and286
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_idas 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.
/adminand/systemremain 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.
kindand 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.phpprovestenant_idremoval only inside the declared core-owned cutover inventory with explicit Spec281and282exclusions, confirm no provider-specific fields land onManagedEnvironment, 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-ownedTenantreferences survive, did any unapprovedtenant_idusage remain inside the declared core-owned cutover inventory, and did any touched destructive action lose->requiresConfirmation()or current authorization - Escalation path:
document-in-featurefor the temporary route exception;follow-up-specalready 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 Spec280-287scope, flip the workflow outcome tosplitorreject-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
Tenantfrom re-entering core-owned paths while the rest of the pack is still pending.
Risk Controls
- Reject any implementation that introduces an alias
Tenantmodel, 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
280route/IA rewrite, Spec281provider extraction, or Spec285RBAC redesign.
Research & Design Outputs
research.mdrecords the no-compatibility cutover, temporary route-path exception, provider-boundary discipline, helper-replacement strategy, and proof strategy.data-model.mdcaptures theManagedEnvironmententity, membership retargeting, current-context model, and foreign-key cutover rules.quickstart.mdprovides the bounded reviewer flow and exact validation commands.contracts/managed-environment-core-cutover.logical.openapi.yamlcaptures the logical chooser and temporary environment-shell route contract.checklists/requirements.mdrecords the review outcome, workflow outcome, and test-governance outcome.tasks.mdsequences the cutover work and keeps the remaining pack scope deferred.
Project Structure
Documentation (this feature)
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)
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.