229 lines
25 KiB
Markdown
229 lines
25 KiB
Markdown
# Implementation Plan: Provider-Backed Action Preflight and Dispatch Gate Unification
|
|
|
|
**Branch**: `216-provider-dispatch-gate` | **Date**: 2026-04-19 | **Spec**: [spec.md](./spec.md)
|
|
**Input**: Feature specification from `/specs/216-provider-dispatch-gate/spec.md`
|
|
|
|
## Summary
|
|
|
|
Unify every covered operator-triggered provider-backed start behind the existing `ProviderOperationStartGate` so preventable provider blockers, same-operation dedupe, and cross-operation scope conflicts are resolved before queue admission instead of surfacing later as execution failures. The implementation extends the current gate and registry, adds one thin shared start-result presentation helper over the existing Ops UX stack, standardizes the public start wording as `queued`, `already running`, `scope busy`, and `blocked` over the canonical `accepted`, `deduped`, `scope busy`, and `blocked` result model, pins accepted work to an explicit `provider_connection_id`, keeps scheduled and system-run Monitoring language compatible without widening click-time UX scope, and migrates the first-slice tenant, provider-connection, restore, directory-sync, and onboarding start surfaces without adding new persistence or a second orchestration framework.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4
|
|
**Primary Dependencies**: Filament Resources/Pages/Actions, Livewire 4, Pest 4, `ProviderOperationStartGate`, `ProviderOperationRegistry`, `ProviderConnectionResolver`, `OperationRunService`, `ProviderNextStepsRegistry`, `ReasonPresenter`, `OperationUxPresenter`, `OperationRunLinks`
|
|
**Storage**: PostgreSQL via existing `operation_runs`, `provider_connections`, `managed_tenant_onboarding_sessions`, `restore_runs`, and tenant-owned runtime records; no new tables planned
|
|
**Testing**: Pest unit and focused feature tests for gate transitions, Filament action hosts, onboarding wizard flows, and Ops UX/run-detail alignment
|
|
**Validation Lanes**: `fast-feedback`, `confidence`
|
|
**Target Platform**: Laravel admin web app under Sail, rendered through Filament on Linux containers
|
|
**Project Type**: Monorepo with one Laravel platform application in `apps/platform` plus docs/spec artifacts at repository root
|
|
**Performance Goals**: Start preflight remains DB-only at click time, performs no Graph call before queue admission, adds no new remote latency source to the click-time path, and admits at most one accepted provider-backed run per protected scope
|
|
**Constraints**: No new provider-start framework, no parallel queue-admission path, no new persistence, no operation-type naming rewrite, no inline remote work on start surfaces, no drift from RBAC 404/403 semantics, no drift from Ops-UX 3-surface feedback, and no hidden bypass of the gate on covered start surfaces
|
|
**Scale/Scope**: First slice covers every current operator-triggered provider-backed start reachable from tenant-scoped surfaces, provider-connection surfaces, and onboarding: tenant verification, provider-connection check/inventory/compliance, restore execute, directory groups sync, role definitions sync, onboarding verification, and onboarding bootstrap
|
|
|
|
## Filament v5 Implementation Contract
|
|
|
|
- **Livewire v4.0+ compliance**: Preserved. This feature stays within existing Filament v5 and Livewire v4 patterns and does not introduce any Livewire v3-era APIs.
|
|
- **Provider registration location**: Unchanged. No panel/provider work is planned; existing panel providers remain registered in `bootstrap/providers.php`, not `bootstrap/app.php`.
|
|
- **Global search coverage**:
|
|
- `TenantResource` remains globally searchable and already has both view and edit pages.
|
|
- `EntraGroupResource` remains tenant-scoped for global search and already has a view page.
|
|
- `ProviderConnectionResource` keeps global search disabled (`$isGloballySearchable = false`).
|
|
- `RestoreRunResource` is not being newly enabled for global search in this feature; its existing view page remains available if current search behavior references it.
|
|
- **Destructive actions**: No new destructive actions are introduced. Restore execution remains a destructive-like flow that continues to rely on existing confirmation and authorization requirements; existing connection lifecycle actions remain `Action::make(...)->action(...)` based and keep `->requiresConfirmation()` where already required.
|
|
- **Asset strategy**: No new panel or shared assets are planned. Deployment expectations remain unchanged, including `cd apps/platform && php artisan filament:assets` when registered Filament assets change.
|
|
- **Testing plan**: Keep one supporting unit seam for the gate and shared presentation mapping, then prove the feature through focused Filament/Livewire feature coverage on tenant, provider-connection, onboarding, restore, directory-sync, and run-detail alignment. No browser lane or new heavy-governance family is planned.
|
|
|
|
## UI / Surface Guardrail Plan
|
|
|
|
- **Guardrail scope**: Changed surfaces across existing tenant-scoped provider-backed starts, provider-connection start actions, restore execute, directory sync starts, onboarding provider steps, and canonical Monitoring run drill-in language
|
|
- **Native vs custom classification summary**: Mixed shared-family change using native Filament actions, native notifications, and existing shared Ops UX helpers; no new custom shell or bespoke panel surface
|
|
- **Shared-family relevance**: Shared provider action family across `TenantResource`, `TenantVerificationReport`, `ProviderConnectionResource`, `RestoreRunResource`, `ListEntraGroups`, `ManagedTenantOnboardingWizard`, and related run-detail explanation surfaces
|
|
- **State layers in scope**: `page`, `detail`, `wizard-step`, and existing run-link drill-in; no new URL-query or shell ownership added
|
|
- **Handling modes by drift class or surface**: Hard-stop for blocked preflight and protected-scope conflicts; review-mandatory for any remaining direct dispatch path on a covered surface
|
|
- **Repository-signal treatment**: Review-mandatory because the feature changes a shared action-host family and canonical run-entry semantics
|
|
- **Special surface test profiles**: `standard-native-filament`, `workflow / wizard`, `monitoring-state-page`
|
|
- **Required tests or manual smoke**: `functional-core`, `state-contract`, `manual-smoke`
|
|
- **Exception path and spread control**: One named exception boundary only: onboarding bootstrap currently batches multiple provider-backed operations and must be normalized to one accepted protected-scope run without introducing a new orchestration framework
|
|
- **Active feature PR close-out entry**: `Guardrail`
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Passed before Phase 0 research. Re-checked after Phase 1 design: still passed with one bounded helper addition and no new persisted truth.*
|
|
|
|
| Gate | Status | Plan Notes |
|
|
|------|--------|------------|
|
|
| Inventory-first / read-write separation | PASS | The feature changes start admission and feedback only. Accepted work still executes asynchronously; restore keeps preview/confirmation/audit semantics and no new snapshot or runtime truth is introduced. |
|
|
| Single Graph contract path / no inline remote work | PASS | Start surfaces remain authorize + preflight + enqueue only. Existing queued jobs continue to own Graph calls through the current provider abstractions; no render-time or click-time Graph call is added. |
|
|
| RBAC, workspace isolation, tenant isolation | PASS | Covered starts remain inside existing tenant/workspace scopes. Non-members remain 404, members missing capability remain 403, and provider readiness messaging must not leak cross-tenant active runs or connection identity. |
|
|
| Run observability / Ops-UX 3-surface feedback | PASS | Covered starts continue to create or reuse canonical `OperationRun` truth. Accepted starts keep toast-only intent feedback, progress stays in active widgets/run detail, and terminal DB notifications remain owned by `OperationRunService` for accepted work only. |
|
|
| Proportionality / no premature abstraction / few layers | PASS | The plan extends the existing gate and registry and adds one thin presentation helper to absorb five-plus duplicated local result branches. No second gate, coordinator framework, or persisted summary layer is introduced. |
|
|
| UI semantics / Filament-native action discipline | PASS | Existing action hosts remain in place. The feature standardizes start-result language without adding new navigation models, new shell surfaces, or page-local status frameworks. |
|
|
| Test governance | PASS | Proof stays in unit plus focused feature lanes with explicit provider/tenant context opt-in. No new browser or heavy-governance family is required. |
|
|
|
|
## Test Governance Check
|
|
|
|
- **Test purpose / classification by changed surface**: `Unit` for gate transition and presentation mapping seams; `Feature` for tenant/provider/onboarding/restore/directory start surfaces and accepted-run reason alignment; `Heavy-Governance`: none; `Browser`: none
|
|
- **Affected validation lanes**: `fast-feedback`, `confidence`
|
|
- **Why this lane mix is the narrowest sufficient proof**: The business truth is server-side preflight, authorization, dedupe/scope busy handling, queue admission, and consistent operator feedback on existing action hosts. That behavior is fully provable with direct feature tests and one small unit seam; browser coverage would add cost without proving unique behavior.
|
|
- **Narrowest proving command(s)**:
|
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
|
|
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Providers/ProviderOperationStartGateTest.php tests/Unit/Providers/ProviderOperationStartResultPresenterTest.php tests/Feature/ProviderConnections/ProviderDispatchGateStartSurfaceTest.php tests/Feature/Tenants/TenantProviderBackedActionStartTest.php tests/Feature/Workspaces/ManagedTenantOnboardingProviderStartTest.php tests/Feature/Restore/RestoreRunProviderStartTest.php tests/Feature/Directory/ProviderBackedDirectoryStartTest.php tests/Feature/Onboarding/OnboardingRbacSemanticsTest.php tests/Feature/Filament/RestoreRunUiEnforcementTest.php tests/Feature/Guards/ActionSurfaceContractTest.php tests/Feature/Operations/ProviderBackedRunReasonAlignmentTest.php tests/Feature/OpsUx/CanonicalViewRunLinksTest.php tests/Feature/Operations/OperationRunBlockedExecutionPresentationTest.php tests/Feature/OpsUx/Constitution/JobDbNotificationGuardTest.php tests/Feature/OpsUx/NoQueuedDbNotificationsTest.php tests/Feature/Guards/ProviderDispatchGateCoverageTest.php tests/Feature/Guards/TestLaneManifestTest.php`
|
|
- **Fixture / helper / factory / seed / context cost risks**: Provider-backed start fixtures need tenant membership, explicit provider connections, active-run setup, and onboarding draft state; these remain explicit per test and must not become default helpers.
|
|
- **Expensive defaults or shared helper growth introduced?**: No. Existing factories and helpers remain opt-in; no global provider/workspace bootstrap is planned.
|
|
- **Heavy-family additions, promotions, or visibility changes**: None
|
|
- **Surface-class relief / special coverage rule**: Standard native Filament relief for resource/page action hosts; onboarding uses a named wizard-step profile but still remains feature-testable without browser automation.
|
|
- **Closing validation and reviewer handoff**: Re-run `pint`, then the focused test command above, then do one human smoke pass for blocked, deduped, scope busy, and queued outcomes across tenant, provider-connection, onboarding, and restore. Reviewers should confirm there is no remaining direct-dispatch bypass on a route-bounded covered surface, that scheduled or system-run Monitoring wording stays compatible with the same problem classes, and that no new custom completion notification path was introduced.
|
|
- **Budget / baseline / trend follow-up**: None expected; document in feature if the new focused suite materially expands runtime beyond ordinary feature-local upkeep.
|
|
- **Review-stop questions**: Did any coverage drift into browser/heavy lanes without a unique proving need? Did any helper start providing implicit tenant/provider context? Did any route-bounded covered start remain on local `ensureRun*/dispatch` logic? Did scheduled or system-run compatibility widen into click-time UX scope? Did any test assert presentation details that should stay in the shared presenter seam only?
|
|
- **Escalation path**: `document-in-feature`
|
|
- **Active feature PR close-out entry**: `Guardrail`
|
|
- **Why no dedicated follow-up spec is needed**: This change is feature-local hardening around existing start surfaces. Only recurring operation-type normalization or structural lane-cost drift would justify a follow-up spec.
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/216-provider-dispatch-gate/
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ └── provider-dispatch-gate.logical.openapi.yaml
|
|
└── tasks.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
apps/platform/
|
|
├── app/
|
|
│ ├── Filament/
|
|
│ │ ├── Pages/Operations/TenantlessOperationRunViewer.php
|
|
│ │ ├── Pages/Workspaces/ManagedTenantOnboardingWizard.php
|
|
│ │ ├── Resources/EntraGroupResource/Pages/ListEntraGroups.php
|
|
│ │ ├── Resources/OperationRunResource.php
|
|
│ │ ├── Resources/ProviderConnectionResource.php
|
|
│ │ ├── Resources/RestoreRunResource.php
|
|
│ │ └── Resources/TenantResource.php
|
|
│ ├── Notifications/
|
|
│ │ └── OperationRunCompleted.php
|
|
│ ├── Services/
|
|
│ │ ├── Directory/RoleDefinitionsSyncService.php
|
|
│ │ ├── Providers/ProviderConnectionResolver.php
|
|
│ │ ├── Providers/ProviderOperationRegistry.php
|
|
│ │ ├── Providers/ProviderOperationStartGate.php
|
|
│ │ └── Verification/StartVerification.php
|
|
│ └── Support/
|
|
│ ├── OperationRunLinks.php
|
|
│ ├── OpsUx/OperationUxPresenter.php
|
|
│ ├── OpsUx/ProviderOperationStartResultPresenter.php
|
|
│ ├── ReasonTranslation/
|
|
│ └── Providers/
|
|
└── tests/
|
|
├── Feature/
|
|
│ ├── Directory/
|
|
│ ├── Filament/
|
|
│ ├── Guards/
|
|
│ ├── Onboarding/
|
|
│ ├── Operations/
|
|
│ ├── OpsUx/
|
|
│ ├── ProviderConnections/
|
|
│ ├── Restore/
|
|
│ ├── Tenants/
|
|
│ └── Workspaces/
|
|
├── Support/
|
|
└── Unit/Providers/
|
|
```
|
|
|
|
**Structure Decision**: Single Laravel application inside the monorepo. All runtime work lands in `apps/platform`, while planning artifacts stay under `specs/216-provider-dispatch-gate`.
|
|
|
|
## Complexity Tracking
|
|
|
|
No constitutional violation is planned. One bounded addition is tracked explicitly because it adds a thin presentation layer.
|
|
|
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
|-----------|------------|-------------------------------------|
|
|
| BLOAT-001 bounded presenter helper | Five-plus real start surfaces already duplicate blocked/deduped/scope busy/accepted notification branches; one shared helper is the narrowest way to keep one operator vocabulary without inventing a framework | Keeping page-local branches would fail FR-216-008/009, preserve inconsistent copy drift, and force every new covered surface to duplicate the same start contract again |
|
|
|
|
## Proportionality Review
|
|
|
|
- **Current operator problem**: Operators can trigger provider-backed actions that look accepted at click time but only fail later because provider connection, consent, credential, or protected-scope prerequisites were missing. The same blocker also renders differently across tenant, provider-connection, onboarding, restore, and directory-sync surfaces.
|
|
- **Existing structure is insufficient because**: The canonical gate already exists, but only a subset of surfaces use it and those surfaces still duplicate result rendering locally. Legacy `ensureRun*/dispatch` starts also resolve provider identity too late, which allows runtime default drift and inconsistent click-time feedback.
|
|
- **Narrowest correct implementation**: Extend the existing `ProviderOperationRegistry` and `ProviderOperationStartGate`, add one thin start-result presentation helper over the current Ops UX primitives, and migrate the first-slice action hosts. Keep blocked/accepted truth in existing `OperationRun` records and do not add new persistence.
|
|
- **Ownership cost created**: One small shared presentation helper, a broader but still focused feature test surface, and explicit registry entries for each newly covered operation type.
|
|
- **Alternative intentionally rejected**: A new provider-start coordinator/orchestration framework or a second queue-admission path. Those would duplicate existing gate behavior, broaden the slice into architecture work, and violate FR-216-014.
|
|
- **Release truth**: Current-release truth. The problem exists today on current operator-facing start surfaces.
|
|
|
|
## Phase 0 Research Summary
|
|
|
|
- Reuse the current gate and registry; adoption breadth is the gap, not missing queue-admission infrastructure.
|
|
- Resolve and pin `provider_connection_id` at dispatch time for every accepted covered start to prevent runtime default drift.
|
|
- Preserve blocked preflight truth as canonical blocked start state, but never admit background work for blocked starts.
|
|
- Add one shared presentation helper over `OperationUxPresenter`, `ReasonPresenter`, `ProviderNextStepsRegistry`, and `OperationRunLinks` rather than leaving local `Notification::make()` branches in place.
|
|
- Keep the operator-facing start vocabulary explicit: the canonical outcomes remain `accepted`, `deduped`, `scope busy`, and `blocked`, while public wording stays `queued`, `already running`, `scope busy`, and `blocked`.
|
|
- Keep the first implementation slice bounded to tenant, provider-connection, onboarding, restore, and directory start surfaces named by the spec; workspace-level baseline/evidence/review generators remain outside this route-bounded slice.
|
|
- Do not combine dispatch-gate hardening with operation-type normalization; legacy write-time operation strings stay as-is in this feature.
|
|
- Keep scheduled and system-triggered compatibility bounded to Monitoring-side reason reuse and initiator-aware notification behavior; no click-time UX is added for those runs.
|
|
- Normalize onboarding bootstrap from multi-start batch admission to sequential protected-scope admission without adding a new orchestration framework.
|
|
|
|
## Phase 1 Design Summary
|
|
|
|
- `data-model.md` documents the feature as a service-owned extension over existing `ProviderConnection`, `OperationRun`, and onboarding draft state plus new logical models for protected scope, preflight result, accepted run context, and shared presentation output.
|
|
- `contracts/provider-dispatch-gate.logical.openapi.yaml` defines the internal action-start contract for tenant, provider-connection, restore, directory-sync, and onboarding action hosts plus canonical run-detail output.
|
|
- `quickstart.md` defines the focused verification path for blocked, deduped, scope busy, and accepted results, including the onboarding bootstrap sequentialization case.
|
|
|
|
## Implementation Strategy
|
|
|
|
1. **Extend canonical gate coverage**
|
|
- Expand `ProviderOperationRegistry` to include every first-slice covered operation type.
|
|
- Keep the existing `ProviderOperationStartGate` as the single queue-admission path.
|
|
- Ensure each migrated start resolves provider access before dispatch and passes the explicit `provider_connection_id` into both `OperationRun` context and job args.
|
|
|
|
2. **Centralize start-result presentation**
|
|
- Add one shared helper that consumes `ProviderOperationStartResult` and emits the standardized operator feedback mapping: `accepted -> queued`, `deduped -> already running`, `scope busy -> scope busy`, and `blocked -> blocked`.
|
|
- Reuse `OperationUxPresenter` for queued and already running toast semantics and `ReasonPresenter` plus next-step metadata for blocked and scope busy outcomes.
|
|
- Keep domain verbs and target nouns local to each action host while unifying outcome vocabulary and run-link behavior.
|
|
|
|
3. **Migrate already-gated surfaces first**
|
|
- Refactor `StartVerification`, `TenantResource`, `TenantVerificationReport`, `ProviderConnectionResource`, and the onboarding verification step to call the shared presentation helper instead of keeping local result-branching code.
|
|
- Preserve current protection while removing duplicated operator copy paths.
|
|
|
|
4. **Migrate legacy direct-dispatch starts**
|
|
- Replace local `ensureRun*/dispatch` logic on restore execute, directory groups sync, role definitions sync, and onboarding bootstrap with the canonical gate.
|
|
- Keep destructive restore semantics intact: existing preview, warning, confirmation, and authorization remain unchanged; only queue admission and feedback unify.
|
|
|
|
5. **Normalize onboarding bootstrap sequencing**
|
|
- Convert bootstrap admission from “start every selected provider-backed operation immediately” to “accept one protected-scope provider-backed operation at a time.”
|
|
- Reuse existing onboarding draft state to retain pending bootstrap selections and run references instead of introducing an umbrella batch entity or new orchestration model.
|
|
|
|
6. **Align accepted-run diagnostics**
|
|
- Ensure canonical run detail and terminal notification reason translation stay aligned with the shared start-result problem classes whenever they describe the same accepted operation.
|
|
- Keep scheduled and system-triggered Monitoring reason reuse in the same public language family without adding click-time start UX or breaking initiator-only notification policy.
|
|
- Keep blocked starts distinguishable from accepted execution failures in monitoring and audit surfaces.
|
|
|
|
7. **Backfill proof and regression guards**
|
|
- Extend the gate unit suite for newly registered operation types and onboarding sequencing.
|
|
- Add one route-bounded guard that inventories covered start hosts and fails if any first-slice action still bypasses `ProviderOperationStartGate` through local `ensureRun*/dispatch` logic.
|
|
- Add focused feature coverage around each migrated action host and around cross-surface reason vocabulary alignment.
|
|
|
|
## Risks and Mitigations
|
|
|
|
- **Onboarding bootstrap behavior change**: The current wizard can admit more than one provider-backed run for one connection. The mitigation is to normalize the flow to one accepted protected-scope run per submission and keep the rest as pending follow-up using existing draft state.
|
|
- **Legacy operation-type aliases**: Directory sync operations currently use legacy write-time names. The mitigation is to keep those names stable in this feature and avoid widening the scope into operation taxonomy cleanup.
|
|
- **Restore flow regression risk**: Restore is destructive-like and already confirmation-heavy. The mitigation is to change only the start path and feedback contract, not the preview/confirmation/authorization model.
|
|
- **Hidden direct-dispatch bypasses**: Additional provider-backed starts may exist outside obvious action hosts. The mitigation is to treat any remaining first-slice `ensureRun*/dispatch` path as a review blocker and keep the route-bounded scope explicit.
|
|
- **Over-testing via expensive fixtures**: Provider-backed starts need tenant, provider, and active-run context. The mitigation is to keep those fixtures opt-in and avoid a new shared “full provider world” helper.
|
|
|
|
## Post-Design Re-check
|
|
|
|
Phase 0 and Phase 1 outputs resolve the earlier design unknowns without introducing new gates, new persisted truth, or a second UI semantics framework. The plan remains constitution-compliant, bounded to current-release operator pain, and ready for `/speckit.tasks`.
|
|
|
|
## Implementation Close-Out
|
|
|
|
- **Final lane outcome**: Focused verification completed with `pint --dirty --format agent`, the documented quickstart suite, the broader migrated-surface regression suite, and the route-bounded guard/manifest close-out suite all passing during implementation.
|
|
- **Route-bounded covered-surface audit**: `tests/Feature/Guards/ProviderDispatchGateCoverageTest.php` now guards the first-slice route-bounded provider-backed starts so tenant verification, provider-connection actions, restore execute, directory sync, role definitions sync, and onboarding bootstrap stay on canonical gate-owned entry points rather than regressing to local `ensureRun` / `dispatchOrFail` admission paths.
|
|
- **Guardrail close-out**: Livewire v4.0+ compliance is preserved, provider registration remains in `bootstrap/providers.php`, no new global-search behavior was introduced, destructive restore semantics remain on existing confirmation/authorization paths, and no new Filament assets were added beyond the standing `php artisan filament:assets` deployment expectation when registered assets change.
|
|
- **Test governance disposition**: `document-in-feature` remains the explicit disposition. The proof stays in unit plus focused feature lanes, with one new heavy-governance guard family (`provider-dispatch-gate-coverage`) recorded in the manifest instead of widening into browser or new heavy workflow suites.
|