TenantAtlas/specs/281-provider-connection-scope/research.md
ahmido 023274c46c feat: normalize provider connection scope contracts (#339)
## Summary
- normalize provider-neutral target-scope and identity contracts across provider connection resolution, operation-start gating, verification reporting, and boundary configuration
- align provider connection resource, onboarding, tenant summaries, and operation follow-up on the same shared scope contract while keeping Microsoft-specific profile details in provider-owned metadata
- add Spec 281 artifacts and focused feature/browser coverage for the new provider-scope contract
- move the tenant dashboard context-chip rail into Filament header widgets so the metadata row renders directly under the page subtitle

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Providers/ProviderConnectionTargetScopeNeutralityTest.php tests/Feature/Providers/ProviderIdentityResolutionNeutralityTest.php tests/Feature/Providers/ProviderOperationStartGateTargetScopeContextTest.php tests/Feature/Filament/ProviderConnectionResourceScopeSummaryTest.php tests/Feature/Onboarding/ManagedTenantOnboardingProviderConnectionScopeTest.php tests/Feature/Guards/ProviderConnectionMicrosoftScopeLeakGuardTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec281ProviderConnectionScopeSmokeTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Dashboard/TenantDashboardProductizationSummaryTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Dashboard/TenantDashboardProductizationSmokeTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Filament remains on v5 with Livewire v4-compatible surfaces only.
- Provider registration location is unchanged; Laravel 11+ providers stay in `apps/platform/bootstrap/providers.php`.
- `ProviderConnectionResource` remains non-globally-searchable and still exposes View/Edit pages.
- No new asset registration was added; deploy-time `filament:assets` expectations are unchanged.
- No new destructive action path was introduced; existing server authorization and confirmation handling remain in place where applicable.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #339
2026-05-07 19:28:42 +00:00

11 KiB

Research: Provider Connection Scope & Microsoft Profile Extraction

Date: 2026-05-07
Branch: 281-provider-connection-scope

Decision 1: Keep 281 as a contract-extraction slice, not a new provider framework

  • Decision: limit the slice to provider-neutral target-scope and identity contract extraction across the existing ProviderConnection, resolver, summary, onboarding, and start-gate seams.
  • Rationale: current repo truth already contains the right seam inventory: ProviderConnectionTargetScopeDescriptor, ProviderConnectionTargetScopeNormalizer, ProviderConnectionSurfaceSummary, ProviderIdentityResolution, and ProviderOperationStartGate. The problem is the Microsoft-shaped payload still flowing through them, not the absence of a framework.
  • Alternatives considered:
    • Introduce a provider registry or capability framework: rejected because the feature has only one current provider and the constitution explicitly prefers bounded extraction over speculative multi-provider machinery.
    • Add a generic provider profile subsystem first: rejected because the current release does not need new persistence to describe provider profile detail.

Decision 2: Treat ProviderConnection as the unchanged persisted source of truth

  • Decision: ProviderConnection stays the only persisted binding record for this slice, anchored by workspace_id plus managed_environment_id. No tenant_id migration work and no provider-profile table are added.

  • Rationale: ProviderConnection already belongs to ManagedEnvironment via managed_environment_id, and the raw candidate wording about replacing tenant_id is stale. The remaining defect is contract shaping, not relationship ownership.

  • Alternatives considered:

    • Re-open the old tenant_id to managed_environment_id candidate: rejected because repo truth already completed that move.
    • Add a separate provider-profile entity: rejected because provider profile detail is still derived from the current connection and identity-resolution path.
  • Documented candidate deviation: the raw candidate also proposed a new shared field family around provider_key, external_account_id, provider_metadata, and explicit run-context workspace/environment keys. In current repo truth, provider, workspace_id, and managed_environment_id already exist, so this package narrows the shared contract to target_scope, effective_client_identity, nested provider_context, and existing provider-owned metadata instead of inventing new top-level persisted fields.

Decision 3: Make ProviderConnectionTargetScopeDescriptor the canonical shared target_scope contract

  • Decision: standardize every shared target-scope output on provider, scope_kind, scope_identifier, scope_display_name, shared_label, and shared_help_text instead of any Microsoft-named top-level keys.
  • Rationale: the descriptor already exists and already exposes the right field names. What is missing is consistent reuse across resolver, audit, UI summary, onboarding, and run-context seams.
  • Alternatives considered:
    • Keep target_scope.entra_tenant_id alongside the neutral fields: rejected because pre-production lean doctrine favors canonical replacement over shared compatibility aliases.
    • Add a second DTO or presenter for neutral naming: rejected because the existing descriptor already covers the need.

Decision 4: Reshape the shared identity result around effective client identity and nested provider context

  • Decision: keep ProviderIdentityResolution as the single shared identity-result object, but move its planned contract center of gravity to target_scope, effective_client_identity, blocked_reason, and nested provider_context rather than shared tenantContext naming.
  • Rationale: the current implementation already carries these concepts as targetScope, effectiveClientId, credentialSource, and contextual detail arrays. The shared leak is the tenantContext field name and its downstream use as the primary field in platform-core seams, so the package standardizes the planned artifact language on target_scope, effective_client_identity, and provider_context.
  • Alternatives considered:
    • Leave tenantContext as the canonical shared field and just add neutral labels in UI: rejected because the identity contract would remain Microsoft-shaped below the UI.
    • Split dedicated and platform identities into new separate result types: rejected because one existing result object already supports both paths.

Decision 5: Keep Microsoft-specific detail inside provider-owned profile or context metadata

  • Decision: preserve Microsoft tenant ID, authority tenant, redirect URI, consent URL shaping, and Graph runtime options only inside provider-owned nested metadata or provider-owned consumer seams such as ProviderIdentityContextMetadata, AdminConsentUrlFactory, and ProviderGateway.
  • Rationale: the boundary catalog already marks identity resolution and operation start as platform-core while runtime transport and consent URL shaping remain provider-owned. The narrowest fix is to push Microsoft semantics outward, not to pretend they disappear.
  • Alternatives considered:
    • Strip Microsoft detail from all outputs entirely: rejected because support, consent, and troubleshooting still need those fields.
    • Keep Microsoft detail as top-level fields in shared summaries: rejected because that keeps the platform-core contract Microsoft-shaped.

Decision 6: Reuse ProviderConnectionSurfaceSummary as the only summary adapter

  • Decision: keep ProviderConnectionSurfaceSummary as the one summary contract reused by ProviderConnectionResource, ManagedTenantOnboardingWizard, and managed-environment related-context summaries.
  • Rationale: the wizard already calls ProviderConnectionSurfaceSummary::forConnection($connection)->targetScopeSummary(), and the resource already delegates summary rendering there. Reusing the same adapter is the narrowest way to remove duplicate truth.
  • Alternatives considered:
    • Let onboarding and provider-connections each compute their own neutral labels: rejected because it would create the second identity story this feature is meant to remove.
    • Put raw entra_tenant_id back into onboarding for clarity: rejected because it would reintroduce a Microsoft-only summary path.

Decision 7: Rewrite provider-operation start context to use neutral shared scope fields

  • Decision: ProviderOperationStartGate should emit the neutral target_scope descriptor in started and blocked run context, with any Microsoft-specific detail nested under provider-owned provider-context metadata.
  • Rationale: the start gate is one of the explicit boundary hotspots in config/provider_boundaries.php, and it still writes target_scope.entra_tenant_id directly today. Later artifact and taxonomy work would inherit that leak if it stays.
  • Alternatives considered:
    • Leave the start-gate context alone and only update UI summaries: rejected because OperationRun context is shared operational truth.
    • Add neutral fields beside entra_tenant_id: rejected because the feature is pre-production and should replace the shared shape rather than dual-write it.

Decision 8: Keep credential validation neutral at the platform-core seam

  • Decision: CredentialManager stays the existing credential access seam, but its scope-validation rule and error wording should align to the normalized target-scope identifier rather than a Microsoft-shaped mismatch message.
  • Rationale: CredentialManager is already inside the shared provider identity path and currently compares a payload scope assertion against connection->entra_tenant_id. That is acceptable as an implementation detail, but not as the platform-core contract wording.
  • Alternatives considered:
    • Ignore the message mismatch and leave it Microsoft-shaped: rejected because it leaks provider semantics back into the shared identity failure path.
    • Change provider-credential persistence shape now: rejected because no new persistence is justified for this slice.

Decision 9: Keep Filament surface behavior and deployment strategy unchanged

  • Decision: ProviderConnectionResource remains non-globally-searchable, keeps View and Edit pages, preserves the current confirmation-protected mutations, and adds no new asset registration.
  • Rationale: the feature changes summary and contract shaping only. The current resource already satisfies the required Filament action and search guardrails.
  • Alternatives considered:
    • Enable global search for provider connections while touching the resource: rejected because the feature does not need it and the spec explicitly keeps it out of scope.
    • Add new UI chrome or asset bundles for provider profile detail: rejected because the existing resource and wizard can carry the nested disclosure.

Decision 10: Prove the slice with focused feature coverage and one browser smoke

  • Decision: use focused provider feature tests, one Filament/browser smoke covering provider-connections plus onboarding, and keep proof commands identical across spec, plan, and quickstart.
  • Rationale: this slice changes shared contract truth across several PHP seams and two operator-facing consumers, but it does not justify a new heavy-governance family or broad browser matrix.
  • Alternatives considered:
    • Feature tests only: rejected because the live resource plus onboarding continuity is part of the user-visible contract.
    • Broad browser or smoke expansion across unrelated provider pages: rejected because it would create unnecessary suite cost.

Final Research Outcome

  • ProviderConnection and ProviderCredential persistence stay unchanged.
  • The canonical shared target_scope contract is the existing descriptor shape, not entra_tenant_id.
  • The canonical shared identity result is the existing ProviderIdentityResolution seam, but its planned contract must stop centering tenantContext as shared truth and instead standardize on target_scope, effective_client_identity, and nested provider_context.
  • Provider-specific Microsoft details remain available through ProviderIdentityContextMetadata and provider-owned consumers only.
  • ProviderConnectionSurfaceSummary stays the one summary adapter for provider-connections, onboarding, and related context.
  • ProviderOperationStartGate becomes the critical contract rewrite point for neutral run context.
  • ProviderConnectionResource remains non-globally-searchable with confirmation-protected mutations intact.
  • The narrowest honest proof is the feature suite already named in the spec, one browser smoke, and no new runtime abstractions.