# Research — Remove Legacy Tenant Graph Options ## Goal Remove usage of the deprecated tenant-based Graph options accessor (`$tenant->graphOptions()` / `Tenant::graphOptions()`) and make provider-based resolution (`ProviderConnectionResolver` + `ProviderGateway`) the single source of truth for Microsoft Graph configuration. ## Decisions ### Decision: Provider connection is canonical for Graph options - **Chosen**: Resolve `ProviderConnection` via `App\Services\Providers\ProviderConnectionResolver::resolveDefault($tenant, 'microsoft')`, then derive options via `App\Services\Providers\ProviderGateway::graphOptions($connection, $overrides)`. - **Rationale**: Existing services already follow this path (e.g. onboarding/health); it centralizes credential storage and validation. - **Alternatives considered**: - Keep reading tenant columns (`Tenant.app_client_id` / `Tenant.app_client_secret`) as fallback — rejected (explicitly forbidden; introduces mixed credential sources). ### Decision: Kill-switch behavior for `Tenant::graphOptions()` - **Chosen**: Keep method for now, but make it throw a clear exception (fail-fast). - **Rationale**: Ensures any missed call site fails deterministically in CI/runtime. - **Alternatives considered**: - Delete method outright — rejected (higher risk / more disruptive API break). ### Decision: Guardrail scope and matching rules - **Chosen**: Add a CI guard test that scans **`app/` only** for these forbidden patterns: - `\$tenant->graphOptions(` - `Tenant::graphOptions(` - **Rationale**: Matches the agreed scope and avoids false positives on unrelated `graphOptions(...)` methods (e.g. `ProviderGateway::graphOptions`). - **Alternatives considered**: - Scan for all `->graphOptions(` — rejected (would flag many legitimate methods). - Scan `tests/` too — rejected (would create noisy failures during refactors). ## Current Code Findings (Call Sites) ### Deprecated tenant accessor call sites (must be removed) These are current `app/` references to `$tenant->graphOptions()`: - `app/Services/AssignmentRestoreService.php` - `app/Services/AssignmentBackupService.php` - `app/Services/Intune/RestoreRiskChecker.php` - `app/Services/Intune/TenantPermissionService.php` - `app/Services/Intune/VersionService.php` - `app/Services/Intune/FoundationMappingService.php` - `app/Services/Intune/PolicyCaptureOrchestrator.php` - `app/Services/Intune/FoundationSnapshotService.php` - `app/Services/Intune/TenantConfigService.php` (wraps + returns `$tenant->graphOptions()`) - `app/Services/Intune/ConfigurationPolicyTemplateResolver.php` - `app/Services/Directory/EntraGroupSyncService.php` - `app/Services/Directory/RoleDefinitionsSyncService.php` - `app/Services/Graph/AssignmentFilterResolver.php` ### Canonical provider-based examples to copy - `app/Services/Intune/RbacOnboardingService.php` - `app/Services/Intune/RbacHealthService.php` - `app/Services/Intune/RestoreService.php` - `app/Services/Intune/PolicySnapshotService.php` ### Source of legacy accessor - `app/Models/Tenant.php` defines the deprecated `graphOptions()` method. ## Recommended Refactor Pattern ```php $resolution = $this->providerConnections()->resolveDefault($tenant, 'microsoft'); if (! $resolution->resolved || ! $resolution->connection instanceof ProviderConnection) { // Fail fast with actionable reason. } $connection = $resolution->connection; $graphOptions = $this->providerGateway()->graphOptions($connection, $overrides); ``` ## Notes / Non-goals - No database schema changes (tenant credential columns stay for now). - No changes to Graph contract registry (`config/graph_contracts.php`) are required for this feature; this work changes configuration sourcing only.