# Implementation Plan: Cutover Prerequisite Completion **Branch**: `287-cutover-prerequisite-completion` | **Date**: 2026-05-10 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `specs/287-cutover-prerequisite-completion/spec.md` ## Summary Complete the remaining runtime and test-harness seams that still block the later quality-gates / no-legacy package. The narrow implementation path retires the legacy provider-connection route family, finishes provider target-scope core neutralization on the shared provider seams, cleans environment-scope role persistence so workspace membership remains the only role-bearing truth, replaces tenant-panel-era test helpers with post-cutover admin or workspace helpers, and validates only those seams with targeted feature and browser coverage. This plan is intentionally not a no-legacy guard package. Filament remains v5 on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, no new asset or deployment step is introduced, no full-suite baseline is required, and Spec `288` remains the explicit follow-up for quality gates and no-legacy enforcement. ## Inherited Baseline / Explicit Delta ### Inherited baseline - Spec `279` already owns the managed-environment core cutover and remains historical baseline context only. - Spec `280` already owns the workspace-first route and panel-shell convergence, but repo truth still shows a surviving legacy provider-connection route family in `apps/platform/routes/web.php`. - Spec `281` already owns provider boundary groundwork, but repo truth still shows Microsoft-shaped shared target-scope and identity fields on platform-core provider seams. - Spec `282` already owns governance-artifact retargeting and remains adjacent historical context only. - Spec `285` already owns the workspace-first RBAC direction, but repo truth still shows incomplete environment-scope persistence cleanup where managed-environment membership records mirror workspace role values. - Spec `286` already owns UI copy cleanup and remains explicitly out of scope for this package. ### Explicit delta in this plan - Retire the remaining legacy provider-connection route family instead of guarding it. - Finish provider target-scope and identity neutralization on shared provider-core seams instead of leaving that work to a later enforcement slice. - Complete the runtime cleanup that keeps workspace membership role-bearing and environment scope narrowing-only. - Replace tenant-panel-era shared test helpers and the proof-command consumer tests `apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php` and `apps/platform/tests/Feature/Rbac/TriageReviewStateAuthorizationTest.php` with post-cutover admin or workspace context helpers. - Keep proof targeted to changed seams only and hand global enforcement to Spec `288`. ## Technical Context **Language/Version**: PHP 8.4.15, Laravel 12.52 **Primary Dependencies**: Pest 4, Filament 5.2.1, Livewire 4.1.4, existing provider target-scope and access-scope services, shared Pest test helpers **Storage**: no new persistence; the package completes behavior on existing route, provider-core, access-scope, and test-support seams **Testing**: targeted Pest feature tests plus targeted browser validation **Validation Lanes**: fast-feedback, confidence, browser **Target Platform**: Laravel monolith in `apps/platform` **Project Type**: web application **Performance Goals**: keep validation limited to the changed seams; no full-suite or broad guard lane work **Constraints**: no global guard suite, no full-suite baseline, no package execution, no guided operations, no UI copy cleanup, no provider capability expansion **Scale/Scope**: one bounded prerequisite-completion slice immediately preceding Spec `288` ## Likely Affected Repo Surfaces - `apps/platform/routes/web.php` - `apps/platform/app/Services/Providers/ProviderConnectionResolver.php` - `apps/platform/app/Services/Providers/ProviderIdentityResolver.php` - `apps/platform/app/Services/Providers/ProviderIdentityResolution.php` - `apps/platform/app/Services/Providers/PlatformProviderIdentityResolver.php` - `apps/platform/app/Services/Providers/ProviderOperationStartGate.php` - `apps/platform/app/Support/Providers/TargetScope/ProviderConnectionTargetScopeNormalizer.php` - `apps/platform/app/Support/Providers/TargetScope/ProviderConnectionTargetScopeDescriptor.php` - `apps/platform/app/Services/Auth/TenantMembershipManager.php` - `apps/platform/app/Services/Auth/ManagedEnvironmentAccessScopeResolver.php` - `apps/platform/app/Providers/Filament/AdminPanelProvider.php` - `apps/platform/app/Filament/Resources/TenantResource.php` - `apps/platform/app/Filament/Pages/TenantRequiredPermissions.php` - `apps/platform/app/Support/OperationRunLinks.php` - `apps/platform/app/Support/Providers/ProviderReasonTranslator.php` - `apps/platform/app/Support/Verification/VerificationLinkBehavior.php` - `apps/platform/tests/Pest.php` - targeted feature tests in `apps/platform/tests/Feature/ProviderConnections/`, `apps/platform/tests/Feature/Auth/`, `apps/platform/tests/Feature/Rbac/`, `apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php`, and `apps/platform/tests/Feature/Rbac/TriageReviewStateAuthorizationTest.php` - targeted browser tests in `apps/platform/tests/Browser/` ## Filament v5 / Surface Notes - **Livewire v4.0+ compliance**: all touched surfaces remain on Filament v5 with Livewire v4. - **Provider registration location**: provider registration remains in `apps/platform/bootstrap/providers.php`; this package does not add a new panel or change provider registration location. - **Global search rule**: this slice introduces no new globally-searchable resource. Existing provider-connection resource behavior remains unchanged from a global-search standpoint. - **Destructive actions**: no new destructive action is introduced. Any touched existing destructive actions must keep `->action(...)`, `->requiresConfirmation()`, and server authorization. - **Asset strategy**: no new asset registration or deployment step is planned. ## Prerequisite Completion Fit - Complete runtime truth first, then let Spec `288` enforce that truth. - Prefer canonical replacement over compatibility routes, copied role persistence, or tenant-panel test-only fallbacks. - Keep Microsoft-specific detail nested under provider-owned seams instead of the shared provider-core contract. - Keep the package bounded to five named prerequisite areas and reject adjacent feature work. - Use exact targeted validation commands across `spec.md`, `plan.md`, `tasks.md`, and `quickstart.md`. ## UI / Surface Guardrail Plan - **Guardrail scope**: bounded runtime completion over existing provider-connection and provider-backed operator surfaces - **Native vs custom classification summary**: existing native Filament provider-connection surfaces and shared provider summaries only; no new operator-facing page family - **Shared-family relevance**: navigation, provider target-scope summaries, run-launch context, and test harness setup - **State layers in scope**: route, page, detail, shared provider-core payloads, access-scope persistence, and test-support context helpers - **Audience modes in scope**: operator-MSP, support-platform on existing provider surfaces only - **Decision/diagnostic/raw hierarchy plan**: keep current provider-owned diagnostics nested; change only the shared contract and canonical route truth - **Raw/support gating plan**: preserve existing provider-owned raw detail where current support or consent workflows genuinely need it - **One-primary-action / duplicate-truth control**: no new action family is introduced; existing launch and open actions continue to own the workflow - **Handling modes by drift class or surface**: implementation-required for the named seams; out of scope for global enforcement and copy cleanup - **Repository-signal treatment**: direct runtime completion only; no broad source-scan or lint layer - **Special surface test profiles**: standard-native-filament, global-context-shell - **Required tests or manual smoke**: functional-core, targeted browser-smoke - **Exception path and spread control**: provider-owned Microsoft detail only; no other exception expansion - **Active feature PR close-out entry**: RuntimePrerequisite ## Shared Pattern & System Fit - **Cross-cutting feature marker**: yes - **Systems touched**: provider route ownership, shared provider target-scope and identity resolution, workspace-first access persistence cleanup, and shared test helpers - **Shared abstractions reused**: existing provider target-scope and identity helpers, existing workspace access resolver seams, and existing `tests/Pest.php` helper style - **New abstraction introduced? why?**: none - **Why the existing abstraction was sufficient or insufficient**: the abstractions already exist; the incomplete cutover behavior inside them is the real remaining work - **Bounded deviation / spread control**: Microsoft-specific profile detail remains nested only where provider-owned workflows still need it ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes, as shared provider-backed run context and canonical link truth only - **Central contract reused**: `ProviderOperationStartGate`, existing OperationRun context, and canonical operation links - **Delegated UX behaviors**: existing queued, blocked, and run-link semantics remain delegated; this slice changes only the prerequisite data and route truth they rely on - **Surface-owned behavior kept local**: existing provider launch or open actions only - **Queued DB-notification policy**: `N/A` - **Terminal notification path**: existing central lifecycle mechanism - **Exception path**: none ## Provider Boundary & Portability Fit - **Shared provider/platform boundary touched?**: yes - **Provider-owned seams**: consent/profile detail, Microsoft-specific identifiers, Graph-specific diagnostics, and provider-owned support metadata - **Platform-core seams**: provider-connection route ownership, shared target-scope and identity outputs, provider-backed run context, and workspace-scoped launch truth - **Neutral platform terms / contracts preserved**: `provider connection`, `target scope`, `scope kind`, `scope identifier`, `scope display name`, `workspace`, `managed environment` - **Retained provider-specific semantics and why**: existing consent and support flows still need Microsoft tenant/profile data, but only as provider-owned nested detail - **Bounded extraction or follow-up path**: Spec `288` for no-legacy enforcement after this slice lands ## Constitution Check *GATE: Must pass before implementation begins and again after design artifacts are complete.* - Inventory-first: PASS. No new inventory or snapshot truth is introduced. - Read/write separation: PASS. The slice completes existing runtime seams without adding a new workflow surface. - Graph contract path: PASS by preservation. No new Graph integration family or registry is introduced. - Deterministic capabilities: PASS by preservation. Capability families do not expand. - RBAC-UX: PASS. Workspace membership remains role-bearing and environment scope becomes narrowing-only on the completed seams. - Workspace isolation: PASS. The package preserves workspace-first routing and entitlement boundaries. - Managed-environment isolation: PASS. The package narrows environment scope instead of broadening it. - Run observability: PASS. Existing OperationRun lifecycle and links remain central. - OperationRun start UX: PASS. No new start or completion UX family is added. - Data minimization: PASS. No new persistence or compatibility ledger is introduced. - Test governance: PASS. Validation stays targeted and explicit. - Proportionality / no premature abstraction: PASS. Existing seams are completed rather than wrapped in new frameworks. - Persisted truth / behavioral state: PASS. No new persistence or state family is introduced. - Provider boundary: PASS. Shared provider-core contracts become more neutral while provider-owned detail stays bounded. **Gate evaluation**: PASS. **Post-design re-check**: PASS while `spec.md`, `plan.md`, `tasks.md`, and `quickstart.md` keep the same literal proof commands, and `research.md`, `data-model.md`, `contracts/cutover-prerequisite-completion.logical.openapi.yaml`, and `checklists/requirements.md` keep the same seam inventory and Spec `288` follow-up boundary. ## Test Governance Check - **Test purpose / classification by changed surface**: Feature, Browser - **Affected validation lanes**: fast-feedback, confidence, browser - **Why this lane mix is the narrowest sufficient proof**: the changed seams are concrete runtime and test-support paths, so focused feature tests plus a narrow browser smoke are sufficient and honest - **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/Feature/ProviderConnections/LegacyRedirectTest.php tests/Feature/ProviderConnections/TenantlessListRouteTest.php tests/Feature/ProviderConnections/TenantlessListScopingTest.php tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php tests/Feature/Rbac/TriageReviewStateAuthorizationTest.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/Spec281ProviderConnectionScopeSmokeTest.php tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.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)` - **Fixture / helper / factory / seed / context cost risks**: moderate only because the shared tenant-panel helper is being replaced on `tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php` and `tests/Feature/Rbac/TriageReviewStateAuthorizationTest.php` - **Expensive defaults or shared helper growth introduced?**: no; the helper cutover must reduce dependence on retired panel state rather than add new implicit defaults - **Heavy-family additions, promotions, or visibility changes**: none - **Surface-class relief / special coverage rule**: `standard-native-filament` and `global-context-shell` remain sufficient; no heavy-governance coverage is justified - **Closing validation and reviewer handoff**: rerun the exact commands above, verify Filament stays on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, no asset registration or deployment-step drift was added, no full-suite or guard-family work was added, and confirm Spec `288` remains the explicit follow-up - **Budget / baseline / trend follow-up**: contained feature-local increase only - **Review-stop questions**: did the implementation widen into no-legacy guards, UI copy cleanup, package execution, guided operations, or provider capability expansion - **Escalation path**: `document-in-feature` for contained seam follow-up, `reject-or-split` for scope expansion - **Active feature PR close-out entry**: RuntimePrerequisite ## Review Checklist Status - **Review checklist artifact**: `checklists/requirements.md` - **Review outcome class**: `acceptable-special-case` - **Workflow outcome**: `keep` - **Test-governance outcome**: `keep` - **Resolution note**: the package is implementation-ready as a bounded prerequisite-completion slice and no longer depends on a blocked-by-prerequisites posture - **Escalation rule**: if implementation starts adding guard suites, full-suite baselines, or adjacent product features, stop and split the work out of `287` ## Rollout Considerations - Retire the legacy provider-connection route family before widening targeted validation so the canonical route truth settles first. - Complete provider target-scope neutralization before touching the browser proof so the live provider surfaces already speak the intended shared contract. - Cut over the shared test helper before updating `apps/platform/tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php` and `apps/platform/tests/Feature/Rbac/TriageReviewStateAuthorizationTest.php` so targeted tests do not drift through two helper systems. - Keep Spec `288` untouched during runtime completion; it should start from the completed baseline produced by this slice. ## Risk Controls - Reject any implementation that keeps legacy provider-connection aliases as compatibility routes for convenience. - Reject any implementation that solves provider target-scope cleanup by hiding Microsoft-only fields in new platform-core wrappers instead of neutralizing the shared contract. - Reject any implementation that preserves copied role-bearing state on environment-scope persistence after the cleanup. - Reject any implementation that adds a global guard family, broad source-scan package, or full-suite baseline under this spec. ## Research & Design Outputs - `research.md` records the completion-first decisions, bounded runtime scope, rejected guard-suite alternative, and evidence anchors. - `data-model.md` captures the derived seam inventory, canonical replacements, and invariants. - `quickstart.md` gives reviewers the scope boundary, review scenarios, and exact targeted proof commands. - `contracts/cutover-prerequisite-completion.logical.openapi.yaml` models the logical completion seams and the targeted validation contract. - `checklists/requirements.md` records the review outcome and bounded scope rules. ## Project Structure ### Documentation (this feature) ```text specs/287-cutover-prerequisite-completion/ ├── checklists/ │ └── requirements.md ├── contracts/ │ └── cutover-prerequisite-completion.logical.openapi.yaml ├── data-model.md ├── plan.md ├── quickstart.md ├── research.md ├── spec.md └── tasks.md ``` ### Source Code (repository root) ```text apps/platform/ ├── app/ ├── routes/ └── tests/ ├── Browser/ ├── Feature/ └── Pest.php ``` **Structure Decision**: keep the package inside the existing Laravel app, route, provider, access-service, and Pest test-support structure. No new base directory is needed.