TenantAtlas/specs/279-workspace-managed-environment-core/spec.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

319 lines
34 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.

# Feature Specification: Workspace-first Managed Environment Core Cutover
**Feature Branch**: `279-workspace-managed-environment-core`
**Created**: 2026-05-06
**Status**: Ready with approved feature-local exception
**Input**: User description: "Use the next unspecced reserved cutover slot from the roadmap/spec-candidate pack and prepare the `Workspace-first Managed Environment Core Cutover` package as the next bounded implementation-ready spec."
## Spec Candidate Check
- **Problem**: TenantPilot still treats `Tenant` as the active managed-target core across models, route binding, Filament tenancy, memberships, query helpers, and operator context. That blocks the planned workspace-first / provider-neutral evolution because the platform core still assumes the managed target is a Microsoft-shaped tenant instead of a generic managed environment inside a workspace.
- **Today's failure**: Current repo seams such as `TenantPanelProvider`, `Tenant`, `ProviderConnection::tenant()`, tenant-owned query helpers, `Filament::getTenant()` usage, and many `tenant_id` foreign keys force every new workspace-first or provider-neutral change to either deepen `Tenant` coupling or add compatibility shims the constitution explicitly rejects.
- **User-visible improvement**: Operators and future features get one consistent managed-target identity inside a workspace. Context selection, authorization boundaries, route binding, and current environment-scoped surfaces stop depending on `Tenant` as platform-core truth.
- **Smallest enterprise-capable version**: Replace `Tenant` as the active managed-target core with `ManagedEnvironment`, retarget current core foreign-key anchors and context helpers to `managed_environment_id`, keep the existing panel bootable by temporarily rebinding the current `/admin/t/{environment}` path family to `ManagedEnvironment`, and defer the public workspace-first route/IA rewrite, provider-profile extraction, artifact retargeting refinements, and RBAC scope redesign to follow-up specs `280`-`287`.
- **Explicit non-goals**: No dual-read or dual-write compatibility layer, no legacy `Tenant` alias model, no production-data backfill strategy, no full workspace-first public route-family rewrite, no provider-profile extraction, no artifact-language rewrite, no package engine, no guided-operations layer, and no speculative multi-provider framework.
- **Permanent complexity imported**: One new managed-target root entity, one breaking schema cutover from `tenant_id` to `managed_environment_id`, one renamed environment-membership seam, existing context-helper replacement, and focused unit/feature/browser plus guard-test coverage.
- **Why now**: `279` is the first unspecced reserved slot in the roadmap-backed cutover pack, and every later cutover spec (`280`-`287`) depends on this core replacement. Waiting adds more `Tenant`- and Microsoft-specific coupling to the platform core.
- **Why not local**: The problem spans schema, Eloquent relations, route binding, Filament tenancy, current-context resolution, authorization helpers, commands, and tests. A local rename or adapter would immediately drift.
- **Approval class**: Core Enterprise
- **Red flags triggered**: New persisted truth, new state family, many touched seams, and a foundation-sounding cutover. Defense: this is current-release architecture truth, not speculative extensibility; it explicitly forbids compatibility shims and defers broader route, copy, provider, and artifact follow-through to the next reserved specs.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexitaet: 1 | Produktnaehe: 1 | Wiederverwendung: 2 | **Gesamt: 9/12**
- **Decision**: approve
## Spec Scope Fields
- **Scope**: workspace
- **Primary Routes**:
- existing workspace chooser route under `/admin`
- existing tenant chooser surface, retargeted to managed-environment selection
- current environment-scoped panel routes under `/admin/t/{environment}` as a temporary bounded exception until Spec `280` replaces the public path family
- current admin resource/detail routes that bind the managed target model directly or derive environment context from it
- **Data Ownership**:
- `Workspace` remains the primary SaaS and organization context
- `ManagedEnvironment` becomes the new managed-target root record inside a workspace
- current core-owned managed-target anchors and context-owned relations retarget from `tenant_id` to `managed_environment_id`; provider-connection extraction and broader governance-artifact retargeting remain follow-up work for Specs `281` and `282`
- **RBAC**:
- workspace membership remains the first isolation boundary
- current tenant-membership semantics are retargeted to managed-environment membership semantics without widening role scope in this spec
- wrong-workspace and non-member access remain `404`
- in-scope actors missing a capability remain `403`
Canonical-view handling for current admin surfaces:
- **Default filter behavior when tenant-context is active**: current canonical admin views continue to prefilter to the active managed environment whenever they currently prefilter to the active tenant; broader workspace-first canonical-view semantics remain Spec `280` follow-up work
- **Explicit entitlement checks preventing cross-tenant leakage**: any route, query helper, or global-search path that currently resolves tenant scope must resolve the active managed environment plus current workspace membership before revealing environment-bound records
## Cross-Cutting / Shared Pattern Reuse
- **Cross-cutting feature?**: yes
- **Interaction class(es)**: context selection, route links, global-search scoping, current-target chips, resource query helpers, and environment-bound deep links
- **Systems touched**: `TenantPanelProvider`, `WorkspaceContext`, `ChooseTenant`, `ResolvesPanelTenantContext`, `InteractsWithTenantOwnedRecords`, `ScopesGlobalSearchToTenant`, `TenantOwnedModelFamilies`, `OperationRunLinks`, current tenant widgets, and tenant-scoped resources/pages
- **Existing pattern(s) to extend**: workspace-first context storage, current Filament panel-provider seams, current deny-as-not-found membership enforcement, and existing environment-bound route builders
- **Shared contract / presenter / builder / renderer to reuse**: existing `WorkspaceContext`, panel-provider registration, current resource query scoping helpers, `OperationRunLinks`, and the existing workspace capability resolver
- **Why the existing shared path is sufficient or insufficient**: those seams are sufficient as cutover points, but insufficient because they still encode `Tenant` as the platform-core managed target
- **Exception type**: `Cross-panel Canonical Route Exception`
- **Allowed deviation and why**: one bounded deviation is allowed: the current `/admin/t/{environment}` shell may remain temporarily while binding `ManagedEnvironment` instead of `Tenant`, even though the source-pack end state moves Filament tenancy to `Workspace` in Spec `280`. This temporary bridge exists only to keep the runtime operable while `Tenant` is removed from the core. No second compatibility route family is allowed.
- **Reason block**: only one canonical surface makes sense during the cutover. Keeping the existing shell avoids a second public route family, keeps shell transition explicit, preserves truthful scope signals, and leaves final canonical Workspace tenancy to Spec `280`.
- **Dedicated proof**: `apps/platform/tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php`, `apps/platform/tests/Browser/Spec279ManagedEnvironmentCoreCutoverSmokeTest.php`, and `specs/279-workspace-managed-environment-core/checklists/constitution-scope-001-exception.md`
- **Consistency impact**: route keys, chooser labels, context bars, global-search scope resolution, tenant-owned query helpers, and deep-link builders must all agree on `ManagedEnvironment` as the active managed target
- **Review focus**: reviewers must verify that the cutover replaces current shared seams in place, that the temporary `/admin/t` path is the only documented exception, and that no alias model or second context stack appears
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, link semantics only
- **Shared OperationRun UX contract/layer reused**: `OperationRunLinks` and the existing OperationRun URL-resolution helpers
- **Delegated start/completion UX behaviors**: tenant/workspace-safe environment link resolution only; no new queued toasts, start events, completion notifications, or dedupe messaging are added in this slice
- **Local surface-owned behavior that remains**: none; existing surfaces continue to use the central OperationRun link helpers after their context model is retargeted
- **Queued DB-notification policy**: `N/A`
- **Terminal notification path**: `N/A`
- **Exception required?**: yes - the temporary `/admin/t/{environment}` path retention described above. This is a bounded route exception only and does not permit a second compatibility family.
## Provider Boundary / Platform Core Check
- **Shared provider/platform boundary touched?**: yes
- **Boundary classification**: mixed
- **Seams affected**: managed-target identity, provider-connection foreign-key anchors, current context model, operator vocabulary, route binding, and current target selection
- **Neutral platform terms preserved or introduced**: `workspace`, `managed environment`, `provider connection`, `target scope`, `operation`, `finding`, `review`, and `governance artifact`
- **Provider-specific semantics retained and why**: Microsoft-specific identifiers such as `entra_tenant_id`, Graph consent details, and portal metadata may remain in provider-owned tables or provider metadata until Spec `281` extracts and normalizes them. They must not live on `ManagedEnvironment`.
- **Why this does not deepen provider coupling accidentally**: the core cutover explicitly forbids Microsoft-specific identity, Graph, or Intune fields on `ManagedEnvironment` and replaces `Tenant` as the shared core noun
- **Follow-up path**: Spec `281` for provider-profile extraction, Spec `284` for provider-neutral artifact-source taxonomy, and Spec `286` for copy/localization cleanup
## 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 |
|---|---|---|---|---|---|---|
| Managed-environment chooser replacing current tenant chooser | yes | Native Filament page | context selection, navigation entry point | page, URL/query | no | Keeps the existing chooser family and retargets it to managed environments |
| Current environment-scoped panel routes under `/admin/t/{environment}` | yes | Native Filament panel shell | route links, context bar, global search, resource scope | shell, page, detail, URL/query | yes | Temporary path retention only until Spec `280` owns the public route-family rewrite |
| Admin directory/resource surfaces that currently list `Tenant` records | yes | Native Filament resources/pages | detail links, current-target summaries | page, detail | no | Core noun and route binding change here; broader IA and copy polish remain deferred |
## 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 |
|---|---|---|---|---|---|---|---|
| Managed-environment chooser | Primary Decision Surface | Operator selects the current managed target inside the active workspace | environment name, display name, lifecycle posture, and current workspace | provider-specific diagnostics stay secondary and remain follow-up work | Primary because current environment selection is the first explicit decision before any environment-scoped workflow begins | Keeps the existing current-target selection workflow intact | Removes ambiguity between workspace scope and managed-target scope |
| Current environment-scoped panel shell | Secondary Context Surface | Operator verifies they are acting in the correct managed environment while using existing pages | active workspace plus active managed-environment identity | deeper provider or governance detail remains on the current pages | Not primary because it carries context while work happens elsewhere | Aligns with existing panel-shell behavior instead of inventing a new workbench | Reduces wrong-target actions during the cutover |
## 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 |
|---|---|---|---|---|---|---|---|
| Managed-environment chooser | operator-MSP, support-platform | environment identity, display name, lifecycle posture, and workspace context | provider-health detail and migration notes remain secondary | raw provider metadata stays hidden | `Enter environment` | provider-specific raw metadata | chooser cards show selection truth once and defer diagnostics |
| Current environment-scoped panel shell | operator-MSP, support-platform | active workspace and active managed-environment context | deeper per-page diagnostics remain where they already live | raw payloads remain page-local and secondary | existing page action | any migration/debug hints stay lower-priority | shell surfaces should not repeat full page-level 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Managed-environment chooser | Selector / Context / Entry | Context-selection page | Enter the intended managed environment | card/list selection | optional current pattern | workspace switch remains secondary | none | `/admin/choose-environment` (logical) | environment entry path | active workspace plus environment identity | Managed environment | selected target identity and lifecycle posture | none |
| Current environment-scoped panel shell | Shell / Context / Navigation | Filament panel shell | Continue into the current environment page | current shell context | forbidden | page-local links stay secondary | none | `/admin/t/{environment}` (temporary) | existing page routes under the shell | active workspace plus active managed environment | Managed environment | current environment identity | temporary route retention until Spec `280` |
## 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Managed-environment chooser | TenantPilot operator | Decide which managed environment inside the current workspace should become active | Context-selection page | Which managed environment am I about to act in? | environment identity, workspace context, lifecycle posture | provider-specific diagnostics and migration detail | lifecycle, access posture | none | Enter environment | none |
| Current environment-scoped panel shell | TenantPilot operator | Confirm that all current environment-bound pages resolve the right managed target | Panel shell | Am I in the correct workspace and managed environment? | active workspace and active managed-environment identity | page-local diagnostics only | access posture, lifecycle | none | existing page action | none |
## Proportionality Review
- **New source of truth?**: yes
- **New persisted entity/table/artifact?**: yes
- **New abstraction?**: no new generic abstraction; existing tenant-context seams are replaced or neutralized in place
- **New enum/state/reason family?**: yes, bounded managed-environment kind and lifecycle posture fields
- **New cross-domain UI framework/taxonomy?**: no
- **Current operator problem**: the product cannot become workspace-first or provider-neutral while the core managed target, current context, and route binding still assume `Tenant`
- **Existing structure is insufficient because**: the current `Tenant` model mixes managed-target identity, provider-specific metadata, current-context behavior, and Filament tenant binding across the app
- **Narrowest correct implementation**: a single breaking core cutover to `ManagedEnvironment` with one documented temporary route-path exception and no compatibility aliases
- **Ownership cost**: broad schema and relation retargeting, command and test updates, temporary exception tracking for `/admin/t/{environment}`, and explicit follow-up ownership for Specs `280`-`287`
- **Alternative intentionally rejected**: dual-read/dual-write aliases, a `Tenant` wrapper around `ManagedEnvironment`, or one giant combined route/IA/provider/artifact rewrite. Those options either violate the constitution or make the first cutover slice too broad to review safely.
- **Release truth**: current-release truth with explicit follow-up slices for the remaining cutover pack
### Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, legacy aliases, dual-read/dual-write shims, historical fixtures, 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**: Unit, Feature, Browser
- **Validation lane(s)**: fast-feedback, confidence, browser
- **Why this classification and these lanes are sufficient**: the cutover changes core model truth, route/model binding, and Filament context selection. Unit tests prove model and context rules, feature tests prove scope and routing semantics, and one browser smoke proves the workspace-to-environment selection flow stays operable after the cutover.
- **New or expanded test families**: one managed-environment unit family, one managed-environment feature family, and one narrow browser smoke
- **Fixture / helper cost impact**: moderate; existing tenant fixtures and helper families must be replaced with managed-environment equivalents rather than layered adapters
- **Heavy-family visibility / justification**: none
- **Special surface test profile**: standard-native-filament, global-context-shell, exception-coded-surface
- **Standard-native relief or required special coverage**: standard Filament feature coverage is sufficient for chooser and panel-context semantics; one browser smoke is required because the cutover changes end-to-end context selection and panel bootstrapping
- **Reviewer handoff**: reviewers must verify that `ManagedEnvironment` replaces `Tenant` in core-owned paths, the temporary `/admin/t` exception is the only path deviation, the legacy-core guard proves `tenant_id` removal inside the declared core-owned cutover inventory while keeping Specs `281` and `282` exclusions explicit, `ManagedEnvironment` carries no Microsoft-specific identity fields, any touched globally searchable resource still satisfies Filaments edit/view eligibility rule or remains out of global search, no new asset registration or deployment-step change appears, and provider registration remains in `apps/platform/bootstrap/providers.php`
- **Budget / baseline / trend impact**: moderate feature-local increase only
- **Escalation needed**: `document-in-feature` for the temporary `/admin/t` path exception; `follow-up-spec` already assigned for the remaining cutover pack
- **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/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"`
## Scope Boundaries *(required for this slice)*
### In Scope
- introduce `ManagedEnvironment` as the active managed-target root inside a workspace
- introduce the breaking schema/model cutover away from `Tenant` and `tenant_id` in core-owned paths
- retarget current context selection, route binding, query helpers, memberships, and the current tenant-panel shell bridge to `ManagedEnvironment` while keeping the final `Workspace` Filament-tenancy switch deferred to Spec `280`
- keep the current environment-scoped panel operable through the documented temporary `/admin/t/{environment}` exception until Spec `280`
- update factories, seeders, policies, guards, route binding, and tests so core-owned paths no longer rely on `App\Models\Tenant`
- ensure `ManagedEnvironment` carries no Microsoft-specific identity, Graph, or Intune fields
### Non-Goals
- final workspace-first public route family and navigation IA rewrite
- provider-profile extraction and provider-scope normalization
- governance artifact naming and route retargeting polish
- RBAC scope redesign or new capability families
- copy/localization neutralization beyond the minimum core cutover terms
- quality-gate automation beyond the feature-local guard tests needed for this slice
- package execution, guided operations, virtual consultant, or any new provider implementation
## Assumptions
- the repo remains pre-production, so destructive in-place replacement is acceptable and compatibility shims are not
- the temporary `/admin/t/{environment}` path retention is the only allowed path exception for this slice
- broader workspace-first IA, provider extraction, artifact retargeting, and RBAC scoping remain explicitly deferred to Specs `280`-`287`
- current workspace membership and capability semantics stay materially unchanged in this slice; only the managed-target noun and foreign-key anchors move
## Risks
- the implementation could accidentally combine the core cutover with the public route/IA rewrite, which would make review and rollback reasoning too broad
- lingering `Tenant` references in commands, tests, or helper traits could hide cutover drift and produce mixed context behavior
- provider-specific identity could accidentally land on `ManagedEnvironment` if current `Tenant` fields are copied forward without discipline
- the temporary `/admin/t` exception could become permanent if Spec `280` is not treated as mandatory follow-up
## Candidate Selection Gate Summary
- **Selected candidate**: `279 - Workspace-first Managed Environment Core Cutover`
- **Source locations**:
- `docs/product/spec-candidates.md` under the `Workspace-first / ManagedEnvironment Core Cutover candidate pack`
- `docs/product/roadmap.md` under the planned cutover pack ordering
- **Why selected now**: `279` is the first unspecced reserved slot in the cutover pack and is the prerequisite for the remaining reserved follow-ups
- **Why close alternatives were deferred**:
- `280` depends on the core entity and context replacement from `279`
- `281` depends on the new managed-target root before provider-profile extraction makes sense
- `282` depends on `managed_environment_id` becoming the core anchor first
- `283`-`287` are later normalization and guardrail layers, not the initial breaking cutover
- **Smallest viable implementation slice**: core entity replacement, foreign-key retargeting, current-context replacement, and one bounded path exception only
- **Documented deviation from raw candidate wording**: this package defers the public workspace-first route-family rewrite and final `Workspace` Filament-tenancy end state to Spec `280`. To keep the implementation bounded and the runtime operable, `279` allows the current `/admin/t/{environment}` shell to remain temporarily while rebinding it to `ManagedEnvironment`.
## Completed-Spec Guardrail Result
- `specs/276-support-access-governance/` already exists and remains a separate prepared package
- `specs/277-stored-reports-surface/` already exists and remains a separate prepared package
- `specs/278-cross-domain-indicator-audit/` already exists and carries completed docs-only close-out history
- `specs/247-plans-entitlements-billing-readiness/`, `specs/251-commercial-entitlements-billing-state/`, `specs/252-platform-localization-v1/`, `specs/262-lifecycle-governance-taxonomy/`, and `specs/264-cross-tenant-promotion-execution/` remain context only and are not rewritten by this package
## Deferred Adjacent Candidates
- `280 - Filament Workspace Tenancy & Environment Routing Cutover`
- `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 - Resolve managed environments as the active target inside a workspace (Priority: P1)
As a platform operator, I want the active managed target to be a `ManagedEnvironment` inside my current workspace so the platform core stops depending on `Tenant` as its primary identity model.
**Why this priority**: every later cutover slice depends on the managed-target root being replaced first.
**Independent Test**: Seed a workspace plus one managed environment, resolve the record through route binding and current-context helpers, and confirm the core paths no longer require `App\Models\Tenant`.
**Acceptance Scenarios**:
1. **Given** a workspace has a managed environment, **When** the current environment is resolved by route key, **Then** the app returns the `ManagedEnvironment` record and current workspace context instead of a `Tenant` model.
2. **Given** a wrong-workspace or non-member actor, **When** they try to resolve the same managed environment, **Then** the app responds with `404` and leaks no environment-bound data.
---
### User Story 2 - Keep current environment-scoped surfaces operable through the cutover (Priority: P1)
As an operator using the current admin panel, I want the existing environment-scoped shell to keep working while the core noun changes so the cutover is reviewable without bundling the full route/IA rewrite.
**Why this priority**: the cutover is not implementation-ready unless the current panel still boots on the new core entity.
**Independent Test**: Select a workspace, enter a managed environment from the retargeted chooser, and open the current environment dashboard through the temporary `/admin/t/{environment}` shell.
**Acceptance Scenarios**:
1. **Given** an operator selected a workspace, **When** they choose a managed environment, **Then** the current panel shell loads with that managed environment as the active bound model.
2. **Given** the same environment-scoped shell, **When** a page or deep link resolves the active target, **Then** route builders, query helpers, and current-target chips use `ManagedEnvironment` consistently.
---
### User Story 3 - Remove active tenant-core drift from schema, policies, and tests (Priority: P2)
As a maintainer, I want core-owned schema, policies, factories, and guard tests to use `ManagedEnvironment` and `managed_environment_id` so later specs do not inherit mixed core nouns.
**Why this priority**: lingering tenant-core drift would make the follow-up cutover pack unstable and ambiguous.
**Independent Test**: Run the managed-environment unit/feature suite and the legacy-core guard checks, then confirm no active `App\Models\Tenant` or `->tenant(Tenant::class)` binding remains in core-owned paths.
**Acceptance Scenarios**:
1. **Given** the cutover implementation is complete, **When** factories, policies, and tests create the managed target, **Then** they use `ManagedEnvironment` and managed-environment memberships rather than `Tenant`.
2. **Given** the final core-owned paths are searched or guard-tested, **When** forbidden legacy core patterns are checked, **Then** no active `Tenant` core model or Filament tenant binding remains.
### Edge Cases
- The current public `/admin/t/{environment}` path may remain temporarily, but it must bind `ManagedEnvironment` and must not coexist with a second compatibility route family.
- Microsoft-specific external terms such as `Microsoft Entra Tenant ID` may remain only in provider-owned metadata or provider profiles, not on `ManagedEnvironment`.
- Archived or inactive managed environments must not become selectable current context by default.
- Old tenant-specific factories, seeders, and fixtures must be removed or replaced in the same cutover PR rather than kept for convenience.
## Requirements *(mandatory)*
**Constitution alignment (required):** This feature changes core data ownership, route binding, context selection, and existing operator-facing shell behavior. It adds no Microsoft Graph calls and no new `OperationRun` family. Any security-relevant mutation that is not represented by `OperationRun` still uses existing audit behavior where applicable.
**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** The feature introduces a new persisted root entity because the current product truth needs a provider-neutral managed-target core now. A narrower alias or adapter would preserve the wrong truth and would violate the repos pre-production lean doctrine.
**Constitution alignment (XCUT-001):** Current context-selection, route-building, query-scoping, and shell-context seams must be replaced in place. No second local environment-context language is allowed.
**Constitution alignment (PROV-001):** `ManagedEnvironment` must remain provider-neutral. Provider-specific identity stays in provider-owned seams and follow-up Spec `281`.
**Constitution alignment (SCOPE-001 exception/update prerequisite):** The current constitution still requires tenant-owned tables to use `workspace_id` plus `tenant_id` as `NOT NULL`. This package intentionally replaces active core-owned managed-target keys with `managed_environment_id`. Implementation is gated by the explicit exception record in `specs/279-workspace-managed-environment-core/checklists/constitution-scope-001-exception.md` until the constitution itself is amended.
### Functional Requirements
- **FR-001**: The package MUST introduce `ManagedEnvironment` as the active managed-target root record inside a workspace.
- **FR-002**: The package MUST remove active core dependence on `App\Models\Tenant` and retarget core-owned foreign keys from `tenant_id` to `managed_environment_id`.
- **FR-003**: `ManagedEnvironment` MUST NOT contain Microsoft-specific identity, Graph, or Intune fields.
- **FR-004**: Current workspace-plus-target context resolution MUST bind `ManagedEnvironment` instead of `Tenant`.
- **FR-005**: Current environment membership and capability checks MUST continue to enforce `404` for non-members and `403` for in-scope capability denials.
- **FR-006**: The current environment-scoped panel shell may retain the `/admin/t/{environment}` path temporarily, but it MUST bind `ManagedEnvironment` and MUST remain the only documented path exception in this slice.
- **FR-007**: Current tenant-scoped query helpers, global-search scoping, route builders, and current-target UI seams MUST resolve `ManagedEnvironment` consistently.
- **FR-008**: Tests, factories, policies, and seeders in core-owned paths MUST use `ManagedEnvironment` naming and IDs after the cutover.
- **FR-009**: The package MUST not introduce dual columns, alias models, dual-write logic, or compatibility routes.
- **FR-010**: The package MUST explicitly defer the remaining workspace-first route-family rewrite, provider extraction, artifact retargeting, RBAC scope redesign, copy neutralization, and long-lived cutover quality gates to Specs `280`-`287`.
### Authorization and Safety Requirements
- **AR-001**: Workspace membership remains the first access boundary and must still deny non-members as `404`.
- **AR-002**: Managed-environment membership remains the second access boundary and must still deny wrong-environment access as `404`.
- **AR-003**: No new destructive action semantics are introduced. Any touched existing destructive actions must preserve `->requiresConfirmation()` and current authorization checks.
### Non-Functional Requirements
- **NFR-001**: Filament remains v5 on Livewire v4.
- **NFR-002**: Provider registration stays in `apps/platform/bootstrap/providers.php`; no new panel provider registration location is introduced.
- **NFR-003**: Any existing globally searchable resource touched by the cutover must continue to satisfy Filaments edit/view-page requirement or remain out of global search.
- **NFR-004**: No new asset registration or `filament:assets` deployment change is introduced in this slice.
- **NFR-005**: The cutover must remain reviewable as one bounded implementation loop and must not silently absorb the broader Spec `280`-`287` work.