# Implementation Plan: Canonical Provider Connection State Cleanup

**Branch**: `188-provider-connection-state-cleanup` | **Date**: 2026-04-09 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/188-provider-connection-state-cleanup/spec.md`
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/188-provider-connection-state-cleanup/spec.md`

**Note**: This plan is a hard-cut cleanup. It removes legacy provider connection truth instead of layering a compatibility bridge on top of it.

## Summary

Replace legacy provider connection `status` and `health_status` with one explicit lifecycle truth, `is_enabled`, beside the existing canonical `consent_status` and `verification_status`. The implementation adds that narrow lifecycle column, moves every runtime reader and writer onto the three canonical dimensions, trims health-check and projector contracts so they stop emitting legacy state, rewrites admin and system surfaces to consume lifecycle, consent, and verification plus diagnostics only, retires legacy provider badge domains, updates factories and Pest helpers, and then drops the removed columns and their indexes in the final migration.

## Technical Context

**Language/Version**: PHP 8.4.15  
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing `ProviderConnection` model, `ProviderConnectionResolver`, `ProviderConnectionStateProjector`, `ProviderConnectionMutationService`, `ProviderConnectionHealthCheckJob`, `StartVerification`, `ProviderConnectionResource`, `TenantResource`, system directory pages, `BadgeCatalog`, `BadgeRenderer`, and shared provider-state Blade entries  
**Storage**: PostgreSQL with one narrow schema addition (`is_enabled`) followed by final removal of legacy `status` and `health_status` columns and their indexes  
**Testing**: Pest 4 feature and unit coverage via Laravel Sail, reusing provider connection, onboarding, verification, audit, badge, and system-directory tests plus one new residual guard for removed legacy state usage  
**Target Platform**: Laravel monolith web application running in Sail locally and containerized Linux environments in staging and production  
**Project Type**: web application  
**Performance Goals**: Preserve DB-only rendering guarantees on touched pages, avoid extra per-row remote calls or presenter layers, keep resolver and system-directory queries index-friendly, and keep canonical provider summaries bounded to existing record lookups  
**Constraints**: No dual-read or dual-write compatibility period, no new provider-state framework, no new persisted summary artifact, no new badge taxonomy for lifecycle, no authorization widening, no cross-plane truth drift between `/admin` and `/system`, and no destructive-action safety regression  
**Scale/Scope**: One tenant-owned model, one narrow lifecycle column, two removed legacy columns, at least six runtime read paths, at least six runtime write paths, six operator-facing surfaces, two shared Blade views, the centralized badge registry, shared factory and Pest helper scaffolding, and a focused regression pack spanning provider, onboarding, verification, audit, and directory flows

## Constitution Check

*GATE: Passed before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Pre-Research | Post-Design | Notes |
|-----------|--------------|-------------|-------|
| Inventory-first / snapshots-second | PASS | PASS | The feature changes only live provider-connection truth and its operator-facing interpretation. No snapshot or backup contract changes are introduced. |
| Read/write separation | PASS | PASS | Existing provider mutations, onboarding callbacks, and verification flows remain the only write paths. No new mutation surface is introduced. |
| Graph contract path | PASS | PASS | No Graph endpoint family or contract registry expansion is required. Existing provider health checks remain behind the current provider gateway abstractions. |
| Deterministic capabilities | PASS | PASS | Existing provider view and manage capabilities remain authoritative. No role or capability remapping is introduced. |
| RBAC-UX authorization semantics | PASS | PASS | `/admin` and `/system` stay plane-separated, non-members remain `404`, member-but-missing-capability remains `403`, and existing provider mutations stay server-authorized. |
| Workspace and tenant isolation | PASS | PASS | No new route or query broadens tenant visibility. Tenant resource helpers and system-directory views stay scoped to entitled records only. |
| Run observability / Ops-UX | PASS | PASS | Existing verification and provider health-check runs keep their current `OperationRun` semantics. This cleanup changes state persistence and rendering only. |
| Data minimization | PASS | PASS | The plan removes redundant persisted truth and does not add new logs, secrets, or summary artifacts. |
| Proportionality / no premature abstraction | PASS | PASS | The design adds one narrow lifecycle column and reuses existing badge, resource, and verification infrastructure instead of introducing a provider-state framework or DTO layer. |
| Persisted truth / behavioral state | PASS | PASS | One new persisted lifecycle truth is justified because `disabled` currently has real behavioral consequences. Two redundant legacy columns are removed. |
| UI semantics / few layers | PASS | PASS | Surfaces consume lifecycle, consent, and verification directly from canonical truth with thin helpers. No new readiness taxonomy or presenter stack is introduced. |
| Badge semantics (BADGE-001) | PASS | PASS | `BadgeDomain::BooleanEnabled`, `ProviderConsentStatus`, and `ProviderVerificationStatus` are reused. Legacy provider badge domains are retired instead of retained as compatibility layers. |
| Filament-native UI / Action Surface Contract | PASS | PASS | Existing Filament resources, infolists, tables, actions, and read-only directory pages are reused. Destructive-like actions remain confirmed and capability-gated. |
| Filament UX-001 | PASS | PASS | No new screen type is introduced. Existing list, detail, edit, and read-only directory surfaces remain within current Filament layout patterns. |
| Filament v5 / Livewire v4 compliance | PASS | PASS | The design stays within the current Filament v5 and Livewire v4 stack. |
| Provider registration location | PASS | PASS | No panel or provider registration change is required. Laravel 11+ provider registration remains in `bootstrap/providers.php`. |
| Global search hard rule | PASS | PASS | `TenantResource` remains globally searchable and already has view/edit pages. `ProviderConnectionResource` remains non-globally-searchable. |
| Destructive action safety | PASS | PASS | Existing enable, disable, and credential-affecting actions keep confirmation, authorization, and audit requirements. |
| Asset strategy | PASS | PASS | No new Filament assets or deploy-time `filament:assets` changes are required. |
| Testing truth (TEST-TRUTH-001) | PASS | PASS | The plan updates real provider and onboarding tests and adds a residual guard rather than creating new testing indirection. |

## Phase 0 Research

Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/188-provider-connection-state-cleanup/research.md`.

Key decisions:

- Persist lifecycle as a single `is_enabled` boolean and present it as the operator-facing `Lifecycle` dimension.
- Keep `ProviderConsentStatus` and `ProviderVerificationStatus` as the only consent and verification truths; do not introduce a combined readiness state.
- Remove legacy projector outputs and trim `ProviderConnectionStateProjector` to canonical verification-outcome logic only.
- Replace legacy `HealthResult` transport fields with canonical verification output and diagnostics.
- Enforce the hard-cut sequence: readers first, writers second, presentation and badge cleanup third, tests and helpers fourth, schema removal last.
- Recompute system-directory health rollups from canonical verification and permission truth.
- Reuse existing centralized badge infrastructure and retire legacy provider badge domains.
- Change shared provider-state helpers and views to emit lifecycle, consent, and verification only.
- Update factories and Pest helpers before the final drop migration and add contradiction and residual-guard coverage.

## Phase 1 Design

Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/188-provider-connection-state-cleanup/`:

- `research.md`: planning decisions and rejected alternatives
- `data-model.md`: canonical persistence model, state transitions, mutation rules, and derived surface contracts
- `contracts/provider-connection-state-cleanup.openapi.yaml`: internal route, runtime reader/writer, and surface contract
- `quickstart.md`: phased implementation and validation workflow

Design highlights:

- `ProviderConnection` gains one narrow lifecycle column, `is_enabled`, and loses `status` and `health_status` in the final schema state.
- Lifecycle is rendered through the existing `BooleanEnabled` badge semantics and labeled as `Lifecycle` on provider surfaces.
- Consent and verification stay on their current enums and become the only non-lifecycle state axes across `/admin` and `/system`.
- `HealthResult` and `ProviderConnectionStateProjector` stop serving the removed legacy vocabulary.
- Provider enable or disable, onboarding, consent callback, verification start, credential mutation, and health-check writers all persist canonical state and diagnostics only.
- `ProviderConnectionResource`, `TenantResource`, the shared provider-state Blade entry, and system directory pages lose legacy provider status and health fields, filters, and badge domains.
- Factories, Pest helpers, and targeted regression suites are updated before the final drop migration lands.

## Phase 1 — Agent Context Update

Run after artifact generation:

- `.specify/scripts/bash/update-agent-context.sh copilot`

## Project Structure

### Documentation (this feature)

```text
specs/188-provider-connection-state-cleanup/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── provider-connection-state-cleanup.openapi.yaml
├── checklists/
│   └── requirements.md
└── tasks.md
```

### Source Code (repository root)

```text
apps/platform/
├── app/
│   ├── Filament/
│   │   ├── Pages/
│   │   │   └── Workspaces/
│   │   │       └── ManagedTenantOnboardingWizard.php
│   │   ├── Resources/
│   │   │   ├── ProviderConnectionResource.php
│   │   │   └── TenantResource.php
│   │   └── System/
│   │       └── Pages/
│   │           └── Directory/
│   │               ├── Tenants.php
│   │               └── ViewTenant.php
│   ├── Http/
│   │   └── Controllers/
│   │       ├── AdminConsentCallbackController.php
│   │       └── TenantOnboardingController.php
│   ├── Jobs/
│   │   ├── ProviderConnectionHealthCheckJob.php
│   │   └── ScanEntraAdminRolesJob.php
│   ├── Models/
│   │   └── ProviderConnection.php
│   ├── Services/
│   │   ├── Providers/
│   │   │   ├── Contracts/
│   │   │   │   └── HealthResult.php
│   │   │   ├── MicrosoftProviderHealthCheck.php
│   │   │   ├── ProviderConnectionMutationService.php
│   │   │   ├── ProviderConnectionResolver.php
│   │   │   └── ProviderConnectionStateProjector.php
│   │   └── Verification/
│   │       └── StartVerification.php
│   └── Support/
│       └── Badges/
│           ├── BadgeCatalog.php
│           ├── BadgeDomain.php
│           └── Domains/
│               ├── BooleanEnabledBadge.php
│               ├── ProviderConnectionHealthBadge.php
│               └── ProviderConnectionStatusBadge.php
├── database/
│   ├── factories/
│   │   └── ProviderConnectionFactory.php
│   └── migrations/
│       ├── 2026_01_24_000001_create_provider_connections_table.php
│       ├── 2026_02_04_090020_make_provider_connections_workspace_owned.php
│       ├── 2026_03_13_000001_add_provider_identity_fields_to_provider_connections.php
│       ├── 2026_04_09_000001_add_is_enabled_to_provider_connections.php
│       └── 2026_04_09_000002_drop_legacy_provider_state_columns_from_provider_connections.php
├── resources/
│   └── views/
│       └── filament/
│           ├── infolists/
│           │   └── entries/
│           │       └── provider-connection-state.blade.php
│           └── system/
│               └── pages/
│                   └── directory/
│                       └── view-tenant.blade.php
└── tests/
    ├── Feature/
    │   ├── Audit/
    │   │   ├── ProviderConnectionConsentAuditTest.php
    │   │   ├── ProviderConnectionConsentRevocationAuditTest.php
    │   │   └── ProviderConnectionVerificationAuditTest.php
    │   ├── EntraAdminRoles/
    │   │   └── ScanEntraAdminRolesJobTest.php
    │   ├── Filament/
    │   │   └── ProviderConnectionsDbOnlyTest.php
    │   ├── Guards/
    │   │   └── NoLegacyProviderConnectionStateFallbackTest.php
    │   ├── Onboarding/
    │   │   ├── OnboardingProviderConnectionPlatformDefaultTest.php
    │   │   └── OnboardingProviderConnectionTest.php
    │   ├── ProviderConnections/
    │   │   ├── ProviderConnectionEnableDisableTest.php
    │   │   ├── ProviderConnectionHealthCheckJobTest.php
    │   │   ├── ProviderConnectionHealthCheckStartSurfaceTest.php
    │   │   ├── ProviderConnectionListAuthorizationTest.php
    │   │   ├── ProviderConnectionTruthCleanupSpec179Test.php
    │   │   ├── ProviderConnectionViewsDbOnlyRenderingSpec081Test.php
    │   │   └── ProviderConnectionAuthorizationTest.php
    │   ├── Verification/
    │   │   └── ProviderConnectionHealthCheckWritesReportTest.php
    │   ├── ManagedTenantOnboardingWizardTest.php
    │   └── Tenants/
    │       └── TenantProviderConnectionsCtaTest.php
    └── Unit/
        ├── Badges/
        │   ├── BooleanEnabledBadgesTest.php
        │   └── ProviderConnectionBadgesTest.php
        └── Providers/
            ├── ProviderConnectionBadgeMappingTest.php
            └── ProviderConnectionClassifierTest.php
```

**Structure Decision**: Keep the feature entirely inside the existing Laravel/Filament monolith. Update the current model, services, resources, shared Blade views, badge registry, migrations, and focused test files instead of introducing a new provider-state subsystem.

## Complexity Tracking

> No Constitution Check violation is planned. The feature does trigger proportionality review because it changes persisted truth and the provider-state surface contract.

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| — | — | — |

## Proportionality Review

- **Current operator problem**: The same provider connection can still read as disabled, connected, healthy, consented, or verified depending on which legacy field a runtime path or surface reads, which makes provider state unreliable.
- **Existing structure is insufficient because**: `consent_status` and `verification_status` already exist, but legacy `status` and `health_status` still persist, still drive runtime gates, and still appear in admin and system surfaces.
- **Narrowest correct implementation**: Add one explicit lifecycle truth, `is_enabled`, remove the two legacy columns, and repoint all affected readers, writers, badges, helpers, and tests to lifecycle, consent, and verification directly.
- **Ownership cost created**: One migration pair, focused updates across existing services and resources, badge cleanup, shared helper cleanup, and a targeted regression and residual-guard suite.
- **Alternative intentionally rejected**: Dual-read or dual-write compatibility shims, a new provider readiness framework, and keeping legacy badge domains as diagnostics.
- **Release truth**: Current-release truth cleanup.

## Implementation Strategy

### Phase A — Add Canonical Lifecycle Truth And Prepare The Model

**Goal**: Introduce the narrow lifecycle field needed to preserve enabled or disabled behavior without touching removed columns yet.

| Step | File | Change |
|------|------|--------|
| A.1 | `apps/platform/database/migrations/2026_04_09_000001_add_is_enabled_to_provider_connections.php` | Add `is_enabled` to `provider_connections`, default it to `true`, and backfill `false` where legacy `status = disabled`. |
| A.2 | `apps/platform/app/Models/ProviderConnection.php` | Add the new lifecycle field to the model contract and stop any model helper from treating `status` or `health_status` as canonical state. |
| A.3 | `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionEnableDisableTest.php` | Extend lifecycle tests so the new field becomes the canonical enable or disable truth immediately. |

### Phase B — Move Runtime Readers Off Legacy Provider State

**Goal**: Make all canonical readers safe before any writer stops populating legacy columns. This phase includes shared helper, operator-surface, and runtime-gate reads that establish the reader-cutover gate for the hard cut.

| Step | File | Change |
|------|------|--------|
| B.1 | `apps/platform/app/Services/Providers/ProviderConnectionResolver.php` | Replace `status === disabled` and `status === needs_consent` fallbacks with `is_enabled`, `consent_status`, and `verification_status`. |
| B.2 | `apps/platform/app/Jobs/ScanEntraAdminRolesJob.php` | Stop filtering on `status = connected`; gate on canonical lifecycle and consent rules instead. |
| B.3 | `apps/platform/app/Filament/Resources/ProviderConnectionResource.php` | Move action visibility, list filters, and detail context from legacy `status` or `health_status` to lifecycle, consent, and verification. |
| B.4 | `apps/platform/app/Filament/Resources/TenantResource.php`, `apps/platform/app/Filament/System/Pages/Directory/Tenants.php`, and `apps/platform/app/Filament/System/Pages/Directory/ViewTenant.php` | Stop tenant and system summaries from reading legacy provider columns. |
| B.5 | `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionHealthCheckStartSurfaceTest.php`, `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionListAuthorizationTest.php`, `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionAuthorizationTest.php`, and `apps/platform/tests/Feature/EntraAdminRoles/ScanEntraAdminRolesJobTest.php` | Prove canonical reader cutover before writers change. |

### Phase C — Move Runtime Writers And Internal Contracts To Canonical State

**Goal**: Stop all runtime writes and transport contracts from recreating removed legacy fields.

| Step | File | Change |
|------|------|--------|
| C.1 | `apps/platform/app/Services/Providers/Contracts/HealthResult.php` and `apps/platform/app/Services/Providers/MicrosoftProviderHealthCheck.php` | Replace legacy transport fields with canonical verification output and diagnostics. |
| C.2 | `apps/platform/app/Services/Providers/ProviderConnectionStateProjector.php` | Remove legacy `project()` and `projectForConnection()` responsibilities and keep only canonical verification-outcome derivation if it remains shared. |
| C.3 | `apps/platform/app/Jobs/ProviderConnectionHealthCheckJob.php` and `apps/platform/app/Services/Verification/StartVerification.php` | Persist lifecycle, consent, verification, and diagnostics only. |
| C.4 | `apps/platform/app/Http/Controllers/AdminConsentCallbackController.php`, `apps/platform/app/Http/Controllers/TenantOnboardingController.php`, `apps/platform/app/Filament/Pages/Workspaces/ManagedTenantOnboardingWizard.php`, and `apps/platform/app/Services/Providers/ProviderConnectionMutationService.php` | Remove dual-write behavior and reset canonical verification truth appropriately when onboarding, consent, or credential mutations occur. |
| C.5 | `apps/platform/tests/Feature/ProviderConnections/MvpProviderScopeTest.php`, `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionHealthCheckJobTest.php`, `apps/platform/tests/Feature/Verification/ProviderConnectionHealthCheckWritesReportTest.php`, `apps/platform/tests/Feature/Onboarding/OnboardingProviderConnectionTest.php`, `apps/platform/tests/Feature/Onboarding/OnboardingProviderConnectionPlatformDefaultTest.php`, and `apps/platform/tests/Feature/ManagedTenantOnboardingWizardTest.php` | Prove create-path coverage, writer cutover, and contract cleanup. |

### Phase D — Remove Legacy Presentation And Badge Semantics

**Goal**: Finalize operator-facing labels, summaries, and badge semantics after the reader and writer cutovers are already in place. This phase is presentation cleanup, not a replacement for the earlier reader-cutover gate.

| Step | File | Change |
|------|------|--------|
| D.1 | `apps/platform/app/Filament/Resources/ProviderConnectionResource.php` | Replace legacy status and health sections, columns, and diagnostic filters with lifecycle, consent, and verification presentation. |
| D.2 | `apps/platform/app/Filament/Resources/TenantResource.php` and `apps/platform/resources/views/filament/infolists/entries/provider-connection-state.blade.php` | Change the shared tenant provider summary contract so it exposes only lifecycle, consent, verification, and diagnostics. |
| D.3 | `apps/platform/app/Filament/System/Pages/Directory/Tenants.php`, `apps/platform/app/Filament/System/Pages/Directory/ViewTenant.php`, and `apps/platform/resources/views/filament/system/pages/directory/view-tenant.blade.php` | Recompute system health rollups and provider rows from canonical verification and permission truth, counting only each tenant's default Microsoft connection in list rollups while keeping detail rows read-only and canonical. |
| D.4 | `apps/platform/app/Support/Badges/BadgeDomain.php`, `apps/platform/app/Support/Badges/BadgeCatalog.php`, `apps/platform/app/Support/Badges/Domains/ProviderConnectionStatusBadge.php`, and `apps/platform/app/Support/Badges/Domains/ProviderConnectionHealthBadge.php` | Retire legacy provider badge domains and reuse `BooleanEnabled`, provider consent, and provider verification badge mappings. |
| D.5 | `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionTruthCleanupSpec179Test.php`, `apps/platform/tests/Feature/Filament/ProviderConnectionsDbOnlyTest.php`, `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionViewsDbOnlyRenderingSpec081Test.php`, `apps/platform/tests/Feature/Filament/TenantGlobalSearchLifecycleScopeTest.php`, `apps/platform/tests/Feature/Filament/TenantScopingTest.php`, `apps/platform/tests/Feature/Tenants/TenantProviderConnectionsCtaTest.php`, `apps/platform/tests/Unit/Badges/BooleanEnabledBadgesTest.php`, `apps/platform/tests/Unit/Badges/BadgeCatalogTest.php`, `apps/platform/tests/Unit/Badges/ProviderConnectionBadgesTest.php`, and `apps/platform/tests/Unit/Providers/ProviderConnectionBadgeMappingTest.php` | Prove canonical presentation, search safety, DB-only rendering, and centralized badge semantics. |

### Phase E — Update Shared Test Scaffolding And Add Residual Guards

**Goal**: Prevent helpers and future regressions from recreating removed provider-state truth.

| Step | File | Change |
|------|------|--------|
| E.1 | `apps/platform/database/factories/ProviderConnectionFactory.php` and `apps/platform/tests/Pest.php` | Stop creating provider fixtures with legacy `status` or `health_status` values and move default healthy fixtures onto lifecycle, consent, and verification only. |
| E.2 | `apps/platform/tests/Feature/ProviderConnections/ProviderConnectionEnableDisableTest.php` and related provider truth tests | Add contradiction scenarios such as disabled plus granted consent and disabled plus `verification_status = healthy`. |
| E.3 | `apps/platform/tests/Feature/Guards/NoLegacyProviderConnectionStateFallbackTest.php` | Fail if targeted provider-state runtime files still reference removed provider columns or legacy provider badge domains. |

### Phase F — Remove Legacy Columns And Finish The Hard Cut

**Goal**: Complete the schema cleanup only after runtime, UI, and test scaffolding no longer depend on the removed fields.

| Step | File | Change |
|------|------|--------|
| F.1 | `apps/platform/database/migrations/2026_04_09_000002_drop_legacy_provider_state_columns_from_provider_connections.php` | Drop `status`, `health_status`, and their indexes from `provider_connections`. |
| F.2 | `apps/platform/app/Models/ProviderConnection.php` and any remaining touched files | Remove any leftover references to the removed columns, including diagnostic accessors, audit metadata keys, and helper-array comments. |
| F.3 | Focused regression pack from `quickstart.md` | Re-run runtime, surface, badge, onboarding, audit, and residual-guard coverage after the drop migration. |

## Post-Design Constitution Re-Check

The design still passes the Constitution after Phase 1:

- It removes redundant truth instead of adding layers.
- It introduces one narrow persisted lifecycle fact because current behavior requires it.
- It keeps existing authorization, confirmation, and audit rules intact.
- It keeps Filament v5 and Livewire v4 compliance.
- It requires no panel-provider registration change in `bootstrap/providers.php`.
- It keeps `ProviderConnectionResource` non-globally-searchable and leaves tenant global search unchanged.
- It requires no new asset registration or `filament:assets` deployment work.
- It keeps testing focused on real provider behavior rather than new indirection.