24 KiB
Implementation Plan: Provider Boundary Hardening
Branch: 237-provider-boundary-hardening | Date: 2026-04-24 | Spec: spec.md
Input: Feature specification from /specs/237-provider-boundary-hardening/spec.md
Note: This plan keeps the slice intentionally narrow. It classifies the first high-risk shared provider seams, removes Graph-shaped request building from shared identity resolution, makes provider binding explicit in the shared operation registry path, and adds focused guardrails without introducing a second-provider runtime, new persistence, or new operator-facing surfaces.
Summary
Add a config-seeded provider-boundary catalog plus a small App\Support\Providers\Boundary helper layer to classify the first hot seams as provider_owned or platform_core, with documented current-release exception metadata where needed. The authoritative first-slice seam inventory is locked to provider.gateway_runtime, provider.identity_resolution, provider.connection_resolution, provider.operation_registry, and provider.operation_start_gate. The implementation will harden two concrete hotspots: first, move Graph request-option shaping out of ProviderIdentityResolution and keep it inside provider-owned seams such as ProviderGateway and MicrosoftGraphOptionsResolver; second, split ProviderOperationRegistry into platform-core operation metadata plus explicit provider binding metadata so ProviderOperationStartGate no longer treats microsoft as silent platform-default truth. Existing Microsoft-backed flows stay intact, entra_tenant_id and platform app identity remain documented current-release exceptions for the follow-up identity-neutrality slice, and the boundary is enforced through focused unit plus feature guard tests.
Technical Context
Language/Version: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4
Primary Dependencies: existing provider seams under App\Services\Providers and App\Services\Graph, especially ProviderGateway, ProviderIdentityResolver, ProviderIdentityResolution, PlatformProviderIdentityResolver, ProviderConnectionResolver, ProviderConnectionResolution, MicrosoftGraphOptionsResolver, ProviderOperationRegistry, ProviderOperationStartGate, GraphClientInterface, Pest v4
Storage: Existing PostgreSQL tables such as provider_connections and operation_runs; one new in-repo config catalog for provider-boundary ownership; no new database tables
Testing: Pest v4 unit and focused feature tests through Laravel Sail
Validation Lanes: fast-feedback, confidence
Target Platform: Laravel admin web application running in Sail on the existing /admin, /admin/t/{tenant}, and provider-backed operation surfaces
Project Type: Monorepo with one Laravel runtime in apps/platform and spec artifacts at repository root
Performance Goals: Keep boundary evaluation deterministic and in-process, add no outbound call before existing provider-owned execution seams, and preserve current provider-backed runtime performance on supported Microsoft flows
Constraints: No new provider runtime, no broad provider marketplace abstraction, no schema or route redesign, no operation-type renaming, no new UI surface, no new Graph contract path, and no silent Microsoft fallback on touched shared seams
Scale/Scope: One config-backed boundary catalog, one small boundary support namespace, one shared identity-resolution cleanup, one shared operation-registry cleanup, and focused unit plus feature guard coverage
Filament v5 Implementation Contract
- Livewire v4.0+ compliance: Preserved. This slice changes shared services, value objects, and guardrails only and introduces no legacy Livewire patterns.
- Provider registration location: Unchanged. Panel providers remain registered in
apps/platform/bootstrap/providers.php. - Global search coverage: No new Filament Resource or Page is added, and no existing global-search posture changes in this slice. Provider connection surfaces remain on their current search posture.
- Destructive actions: No destructive action is added or changed. This slice does not introduce new Filament actions.
- Asset strategy: No new assets are planned. Deployment expectations remain unchanged, including
cd apps/platform && php artisan filament:assetsonly when later UI work introduces registered assets. - Testing plan: Prove the slice with focused Pest unit coverage for seam classification and registry behavior plus focused feature coverage for current Microsoft runtime preservation, unsupported-path behavior, and boundary guardrails.
UI / Surface Guardrail Plan
- Guardrail scope: workflow-only guardrail change
- Native vs custom classification summary:
N/A - Shared-family relevance: provider-backed execution seams, provider connection runtime semantics, shared architecture guards
- State layers in scope: none
- Handling modes by drift class or surface:
review-mandatory - Repository-signal treatment:
review-mandatory - Special surface test profiles:
N/A - Required tests or manual smoke:
functional-core,state-contract - Exception path and spread control: one named current-release exception boundary for Microsoft-specific target-scope and platform app identity semantics that remain until the follow-up identity-neutrality spec
- Active feature PR close-out entry:
Guardrail
Shared Pattern & System Fit
- Cross-cutting feature marker: yes
- Systems touched: provider gateway/runtime access, provider identity resolution, provider connection validation, provider-backed operation registry and start gate, provider-owned reason and next-step semantics, and adjacent feature guard patterns
- Shared abstractions reused: existing provider services, existing
GraphClientInterfacecontract, existingProviderOperationStartGate, existing feature-guard patterns undertests/Feature/Guardssuch asNoLegacyTenantGraphOptionsTest.phpandNoLegacyTenantProviderFallbackTest.php, and existing provider unit suites - New abstraction introduced? why?: yes. A small boundary catalog and boundary descriptor layer are required because prose and generic class names alone are not machine-checkable and have not prevented drift.
- Why the existing abstraction was sufficient or insufficient: existing provider seams are sufficient as extension points, but insufficiently explicit about ownership.
ProviderIdentityResolution::graphOptions()andProviderOperationRegistrycurrently mix provider-specific semantics into shared paths. - Bounded deviation / spread control: the only allowed retained deviation is the documented Microsoft-first identity/target-scope exception on existing provider connection data until the follow-up identity-neutrality slice lands
Provider Boundary & Portability Fit
- Shared provider/platform boundary touched?: yes
- Provider-owned seams:
GraphClientInterfaceimplementations,ProviderGateway,MicrosoftGraphOptionsResolver, and Intune-specific service calls that intentionally execute Microsoft behavior - Platform-core seams: provider-boundary catalog,
ProviderIdentityResolver,ProviderIdentityResolution,ProviderConnectionResolver,ProviderConnectionResolution,ProviderOperationRegistrycore operation definition path,ProviderOperationStartGateshared orchestration decisions - Authoritative first-slice seam inventory:
provider.gateway_runtime->ProviderGateway.php,MicrosoftGraphOptionsResolver.phpprovider.identity_resolution->ProviderIdentityResolution.php,ProviderIdentityResolver.php,PlatformProviderIdentityResolver.phpprovider.connection_resolution->ProviderConnectionResolver.php,ProviderConnectionResolution.phpprovider.operation_registry->ProviderOperationRegistry.phpprovider.operation_start_gate->ProviderOperationStartGate.php
- Neutral platform terms / contracts preserved: provider, provider connection, target scope, operation type, operation module, required capability, provider binding, unsupported provider behavior
- Retained provider-specific semantics and why:
entra_tenant_id, platform app credential config, redirect callback details, and Microsoft Graph request-option keys remain current-release Microsoft semantics because they are still needed for the only shipped provider runtime today - Bounded extraction or follow-up path:
follow-up-specfor Provider Identity & Target Scope Neutrality; this feature documents and bounds the remaining identity-shaped hotspot instead of solving schema and UI neutrality here
Constitution Check
GATE: Passed before Phase 0 research. Re-check after Phase 1 design: still passed with one config-backed seam catalog, one bounded runtime extraction, and no new persistence or operator surface.
| Gate | Status | Plan Notes |
|---|---|---|
| Inventory-first / read-write separation | PASS | The slice hardens contracts and runtime boundaries only. No new write path, preview flow, or operator mutation surface is introduced. |
| Single Graph contract path / no inline remote work | PASS | Existing Graph calls remain behind GraphClientInterface. The slice only relocates Graph option shaping to provider-owned seams and adds no new contract bypass. |
| RBAC, workspace isolation, tenant isolation | PASS | No new route, capability, or authorization plane is introduced. Existing provider-backed workflows keep their current tenant and workspace guards. |
| Run observability / Ops-UX lifecycle | PASS | The feature may touch ProviderOperationStartGate, but it does not create a new OperationRun type or change start-surface UX semantics. Existing service-owned run lifecycle rules remain intact. |
| Shared pattern first | PASS | The implementation reuses existing provider services and the existing guard-test pattern instead of creating a parallel portability framework. |
| Proportionality / no premature abstraction | PASS | One boundary catalog plus small descriptors are the narrowest machine-checkable source of truth for multiple real seams. No plugin system, provider marketplace, or second runtime is introduced. |
| Persisted truth / behavioral state | PASS | No new table or persisted lifecycle is added. One or two new provider-boundary reason codes may be introduced only if explicit unsupported-path behavior needs stable runtime semantics. |
| Provider boundary | PASS | The plan explicitly separates provider-owned seams from platform-core seams and records one bounded Microsoft-first exception path. |
| Filament v5 / Livewire v4 contract | PASS | No new Filament surface, action, or global-search behavior is introduced. Provider registration remains in bootstrap/providers.php. |
| Test governance | PASS | Coverage stays in focused unit plus feature lanes with no browser or heavy-governance expansion. |
Test Governance Check
- Test purpose / classification by changed surface:
Unitfor seam classification, registry split semantics, and retained exception behavior;Featurefor current Microsoft-backed runtime preservation, unsupported-path behavior, and boundary guardrails on touched shared seams - Affected validation lanes:
fast-feedback,confidence - Why this lane mix is the narrowest sufficient proof: The business risk is semantic drift inside shared code, not browser interaction. Unit tests prove classification and neutral-contract rules; feature tests prove current Microsoft-backed flows remain intact and unsupported shared-boundary cases fail explicitly.
- Narrowest proving command(s):
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Providers/ProviderBoundaryClassificationTest.php tests/Unit/Providers/ProviderBoundaryGuardrailTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Providers/ProviderBoundaryHardeningTest.php tests/Feature/Providers/UnsupportedProviderBoundaryPathTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/ProviderBoundaryPlatformCoreGuardTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
- Fixture / helper / factory / seed / context cost risks: Minimal. Reuse existing
ProviderConnectionand tenant factories plus current provider unit tests. Do not introduce a new default provider world helper. - Expensive defaults or shared helper growth introduced?: No. The boundary catalog stays config-backed and test fixtures remain opt-in.
- Heavy-family additions, promotions, or visibility changes: none
- Surface-class relief / special coverage rule:
N/A - Closing validation and reviewer handoff: Reviewers should verify that
ProviderIdentityResolutionno longer shapes Graph request options, that shared operation metadata no longer treatsmicrosoftas silent default truth, that the remaining Microsoft-specific identity fields are documented as exceptions, and that current Microsoft-backed starts still work through the hardened seams. - Budget / baseline / trend follow-up: none expected
- Review-stop questions: Did any platform-core seam retain Graph request shaping? Did
ProviderOperationRegistrystill expose provider binding as primary platform truth? Did the slice widen into schema/UI neutrality or operation-type renaming? Did any new test helper make provider context implicit by default? - Escalation path:
document-in-feature - Active feature PR close-out entry:
Guardrail - Why no dedicated follow-up spec is needed: The remaining identity-schema and UI neutrality work already has a named next candidate. This feature contains only the first bounded hardening pass.
Project Structure
Documentation (this feature)
specs/237-provider-boundary-hardening/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── provider-boundary-hardening.logical.openapi.yaml
└── tasks.md
Source Code (repository root)
apps/platform/
├── app/
│ ├── Models/
│ │ └── ProviderConnection.php
│ ├── Services/
│ │ ├── Graph/
│ │ │ ├── GraphClientInterface.php
│ │ │ └── MicrosoftGraphClient.php
│ │ └── Providers/
│ │ ├── MicrosoftGraphOptionsResolver.php
│ │ ├── PlatformProviderIdentityResolver.php
│ │ ├── ProviderConnectionResolution.php
│ │ ├── ProviderConnectionResolver.php
│ │ ├── ProviderGateway.php
│ │ ├── ProviderIdentityResolution.php
│ │ ├── ProviderIdentityResolver.php
│ │ ├── ProviderOperationRegistry.php
│ │ └── ProviderOperationStartGate.php
│ └── Support/
│ └── Providers/
│ └── Boundary/
├── config/
│ └── provider_boundaries.php
└── tests/
├── Feature/
│ ├── Providers/
│ └── Guards/
└── Unit/
└── Providers/
Structure Decision: Keep the entire slice inside the existing Laravel runtime in apps/platform. The only new top-level code shape is a small Support/Providers/Boundary namespace plus a config-backed seam catalog. Runtime changes stay inside the existing provider services and the shared provider operation registry path.
Complexity Tracking
No constitutional violation is planned. One bounded complexity addition is tracked because the feature introduces a new source of truth for seam ownership.
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| BLOAT-001 bounded boundary catalog | Multiple real shared seams now need one explicit, testable ownership source of truth so provider leakage stops depending on reviewer memory alone | Comments, prose-only notes, or local assertions would not be machine-checkable and would let each new seam drift independently |
Proportionality Review
- Current operator problem: Shared provider-backed platform code can still silently become more Microsoft-shaped, which raises the cost and risk of future governance work even when current operator behavior still appears to work.
- Existing structure is insufficient because: generic class names and partial provider abstractions do not stop Graph request shaping, provider binding defaults, and Microsoft-specific semantics from leaking into shared resolution or orchestration paths.
- Narrowest correct implementation: add one config-backed seam catalog, extract Graph option shaping out of shared identity resolution, and separate provider binding from shared operation metadata at the existing registry/start-gate seam.
- Ownership cost created: maintain the seam catalog, preserve a small set of boundary guard tests, and keep the one documented Microsoft-first exception path explicit until the follow-up identity-neutrality work lands.
- Alternative intentionally rejected: a broad multi-provider framework or connector platform. It would import speculative runtime machinery before there is a second real provider case.
- Release truth: current-release truth with deliberate anti-drift preparation for the next provider-boundary follow-through specs
Phase 0 Research Summary
- The first boundary hardening slice should use a small config-backed seam catalog, not a generic provider-plugin framework.
ProviderIdentityResolution::graphOptions()is a concrete provider-leak hotspot because a shared resolution object currently shapes Microsoft Graph request options directly.ProviderOperationRegistryis a second concrete hotspot because shared operation definitions currently exposemicrosoftas if it were platform-default truth.- Existing
ProviderGateway,MicrosoftGraphOptionsResolver, and Intune-specific services are acceptable provider-owned seams for current Microsoft behavior. entra_tenant_id, platform app identity config, and callback/redirect details should remain explicit current-release exceptions here and be cleaned up in the follow-up identity-neutrality slice.- Focused unit plus feature guard tests are sufficient; browser or heavy-governance coverage would add cost without proving unique behavior.
Phase 1 Design Summary
research.mdrecords the boundary decisions that keep the slice narrow and explicit.data-model.mddefines the seam ownership catalog, operation definition vs provider binding split, and boundary guard result shape.contracts/provider-boundary-hardening.logical.openapi.yamldefines the logical internal contract for listing seam ownership and evaluating boundary changes.quickstart.mdrecords the narrow validation order and the intended code areas.tasks.mdwill sequence the work from seam-catalog foundation through shared identity and registry hardening to final guard coverage.
Phase 1 — Agent Context Update
Run after artifact generation:
.specify/scripts/bash/update-agent-context.sh copilot
Implementation Strategy
Phase A — Add the seam ownership catalog
Goal: Make the authoritative first-slice seams explicitly classifiable and testable.
| Step | File | Change |
|---|---|---|
| A.1 | apps/platform/config/provider_boundaries.php |
Add the bounded seam catalog for provider.gateway_runtime, provider.identity_resolution, provider.connection_resolution, provider.operation_registry, and provider.operation_start_gate, classifying each as provider_owned or platform_core and recording retained-provider-semantic notes as exception metadata where needed. |
| A.2 | apps/platform/app/Support/Providers/Boundary/ProviderBoundaryOwner.php, ProviderBoundarySeam.php, and ProviderBoundaryCatalog.php |
Model seam ownership, allowed exceptions, and deterministic lookup for tests and runtime guard checks. |
| A.3 | apps/platform/tests/Unit/Providers/ProviderBoundaryClassificationTest.php |
Prove the catalog contains the intended first-slice seams and only the allowed ownership classifications. |
Phase B — Move Graph request shaping behind provider-owned seams
Goal: Stop shared identity resolution from emitting Microsoft Graph-shaped runtime options directly.
| Step | File | Change |
|---|---|---|
| B.1 | apps/platform/app/Services/Providers/ProviderIdentityResolution.php |
Remove Graph request-option shaping from the shared resolution object and expose only the neutral runtime data the provider-owned seam needs. |
| B.2 | apps/platform/app/Services/Providers/ProviderGateway.php and MicrosoftGraphOptionsResolver.php |
Own Graph option assembly inside provider-owned seams and reuse the shared resolution data without reintroducing platform-core Graph leakage. |
| B.3 | apps/platform/app/Services/Providers/ProviderIdentityResolver.php, PlatformProviderIdentityResolver.php, and ProviderConnectionResolver.php |
Keep current Microsoft-first identity semantics working while marking the remaining target-scope and platform-app details as explicit current-release exceptions. |
Phase C — Split shared operation metadata from provider binding
Goal: Keep shared orchestration metadata platform-core while making provider binding explicit and bounded.
| Step | File | Change |
|---|---|---|
| C.1 | apps/platform/app/Services/Providers/ProviderOperationRegistry.php |
Separate platform-core operation definition fields from provider-binding fields so the shared definition does not treat microsoft as silent default truth. |
| C.2 | apps/platform/app/Services/Providers/ProviderOperationStartGate.php |
Consume the explicit provider binding, preserve current Microsoft-backed start behavior, and return explicit unsupported behavior when a touched shared seam has no provider-owned binding. |
| C.3 | apps/platform/app/Support/Providers/ProviderReasonCodes.php and adjacent translation helpers if needed |
Add one narrow provider-boundary reason code only if explicit unsupported shared-boundary behavior needs stable runtime semantics. |
Phase D — Add guardrails and preserve runtime behavior
Goal: Keep the boundary enforceable without widening the slice.
| Step | File | Change |
|---|---|---|
| D.1 | apps/platform/tests/Unit/Providers/ProviderBoundaryGuardrailTest.php |
Prove platform-core seam rules, allowed exceptions, and registry split behavior are deterministic. |
| D.2 | apps/platform/tests/Feature/Providers/ProviderBoundaryHardeningTest.php |
Prove a current Microsoft-backed workflow still succeeds through the hardened seams. |
| D.3 | apps/platform/tests/Feature/Providers/UnsupportedProviderBoundaryPathTest.php |
Prove the touched shared seam fails explicitly rather than inheriting Microsoft default behavior when binding or ownership is absent. |
| D.4 | specs/237-provider-boundary-hardening/quickstart.md and tasks.md |
Keep the validation order, exception boundary, and no-second-provider-runtime guardrail explicit. |
Risks and Mitigations
- Identity scope creep: Boundary hardening could drift into full provider identity neutrality. Mitigation: keep
entra_tenant_idand platform app identity as explicit current-release exceptions and defer schema/UI neutrality to the next spec. - Operation-type scope creep: Registry cleanup could become operation-type canonicalization work. Mitigation: keep operation type values unchanged and limit this slice to ownership separation, not naming reform.
- Guardrail overreach: A broad filesystem scan could flag legitimate provider-owned Microsoft services. Mitigation: make the seam catalog the allowlist source of truth and keep the guard coverage focused on touched shared seams.
- Runtime regression: Moving Graph option shaping can break current Microsoft-backed flows. Mitigation: preserve and extend focused provider unit and feature coverage around the hardened gateway and registry paths.
Post-Design Re-check
The feature remains constitution-compliant, Filament v5 and Livewire v4 compliant, and narrow. It introduces no new persistence, no new operator-facing page, no new provider runtime, and no operation-type renaming. The plan, research, data model, quickstart, contract, and later tasks align on one explicit seam catalog, one provider-owned Graph shaping boundary, one shared registry hardening step, and one bounded Microsoft-first exception path.