TenantAtlas/specs/179-provider-truth-cleanup/plan.md
ahmido dc46c4fa58 feat: complete provider truth cleanup (#207)
## Summary
- implement Spec 179 to make tenant lifecycle, provider consent, and provider verification the primary truth axes on the targeted Filament surfaces
- demote legacy tenant app status and legacy provider status and health to diagnostic-only roles, add centralized badge mappings for provider consent and verification, and keep provider connections excluded from global search
- add the full Spec 179 artifact set under `specs/179-provider-truth-cleanup/` plus focused Pest coverage for tenant truth cleanup, provider truth cleanup, RBAC, discovery safety, and badge semantics
- fix the numeric out-of-scope tenant route regression so inaccessible `/admin/tenants/{id}` paths return `404 Not Found` instead of `500`

## Testing
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantLifecycleStatusDomainSeparationTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantTruthCleanupSpec179Test.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/ProviderConnectionsDbOnlyTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections/ProviderConnectionTruthCleanupSpec179Test.php`
- `vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections/RequiredFiltersTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Tenants/TenantProviderConnectionsCtaTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Rbac/TenantResourceAuthorizationTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections/ProviderConnectionListAuthorizationTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections/ProviderConnectionAuthorizationTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Rbac/AdminGlobalSearchContextSafetyTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantGlobalSearchLifecycleScopeTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TenantScopingTest.php`
- `vendor/bin/sail artisan test --compact tests/Unit/Badges/TenantBadgesTest.php`
- `vendor/bin/sail artisan test --compact tests/Unit/Badges/ProviderConnectionBadgesTest.php`

## Manual validation
- integrated-browser smoke on `/admin/tenants`, tenant detail, `/admin/provider-connections`, provider detail, and provider edit
- verified out-of-scope tenant and provider URLs return `404 Not Found` with the current session

## Notes
- branch: `179-provider-truth-cleanup`
- commit: `e54c6632`
- target: `dev`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #207
2026-04-05 00:48:31 +00:00

26 KiB
Raw Permalink Blame History

Implementation Plan: Provider Readiness Source-of-Truth Cleanup

Branch: 179-provider-truth-cleanup | Date: 2026-04-04 | Spec: /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/179-provider-truth-cleanup/spec.md Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/179-provider-truth-cleanup/spec.md

Note: This plan keeps the current tenant and provider connection model intact. It cleans up which already-stored fields are allowed to act as leading operator truth and explicitly avoids introducing a new readiness model, new persistence, or a new semantic framework.

Summary

Harden the existing tenant and provider operator surfaces so lifecycle, consent, and verification become the only leading truth axes on the targeted pages. The implementation will remove Tenant.app_status from primary tenant surfaces, repoint the tenant-detail Provider section from legacy connection status and health_status to current consent and verification, and reorganize provider connection list, view, and edit surfaces so legacy projections become secondary diagnostics. The narrowest safe tenant-list choice in this slice is to omit a new row-level provider readiness signal rather than invent one. The provider list will instead promote consent and verification to the default-visible connection-state axes, while existing legacy fields remain persisted and available only as secondary diagnostics.

Technical Context

Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v5, Livewire v4, Pest v4, existing TenantResource, ProviderConnectionResource, TenantVerificationReport, BadgeCatalog, BadgeRenderer, TenantOperabilityService, ProviderConsentStatus, ProviderVerificationStatus, and shared provider-state Blade partials
Storage: PostgreSQL unchanged; no new table, column, or persisted artifact is introduced
Testing: Pest 4 feature tests and Livewire-style Filament surface tests through Laravel Sail, reusing existing tenant and provider surface tests plus focused Spec 179 truth-cleanup coverage
Target Platform: Laravel monolith web application running in Sail locally and containerized Linux environments in staging and production
Project Type: web application
Performance Goals: Keep existing DB-only render guarantees, avoid adding uncontrolled per-row provider queries to the tenant list, and keep provider and tenant detail rendering bounded to already-loaded or single-record derived state
Constraints: No new readiness enum or score, no new persisted truth, no new presenter or taxonomy framework, no authorization widening, no cross-tenant leakage, no new asset pipeline work, and no false equivalence between active, connected, consented, and ready
Scale/Scope: Two existing Filament resources, five operator-facing surfaces, one existing tenant provider-summary partial, one tenant verification widget, three legacy badge domains under review, and a focused regression pack around truth hierarchy and filter semantics

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 read-time presentation of already-stored tenant and provider truth. No snapshot or inventory contract changes are introduced.
Read/write separation PASS PASS No new mutation path, preview flow, or destructive action is added. Existing provider and tenant mutations remain unchanged.
Graph contract path N/A N/A No Graph contract or config/graph_contracts.php change is required.
Deterministic capabilities PASS PASS Existing provider view/manage and tenant membership rules remain authoritative.
RBAC-UX authorization semantics PASS PASS All touched surfaces remain in /admin, preserve tenant and workspace scoping, keep non-members at 404, and keep member-without-capability behavior unchanged.
Workspace and tenant isolation PASS PASS No new route or query broadens tenant visibility. Canonical tenantless provider routes remain tenant-aware and scoped.
Run observability / Ops-UX PASS PASS No new OperationRun type or queueing behavior is introduced. Existing verification and provider-run actions keep their current semantics.
Data minimization PASS PASS No new persisted data, logs, or surface payloads are introduced.
Proportionality / no premature abstraction PASS PASS The plan reuses existing resources, helpers, and partials instead of adding a new readiness service, presenter, or DTO layer.
Persisted truth / behavioral state PASS PASS No new state family or table is planned. Existing legacy fields remain persisted for compatibility but lose leading surface prominence.
UI semantics / few layers PASS PASS The plan uses direct lifecycle, consent, and verification truth instead of introducing a new readiness interpreter.
Badge semantics (BADGE-001) PASS PASS Existing badge semantics remain centralized. Where provider consent or verification need status-like badges, the plan extends BadgeCatalog and BadgeRenderer through narrow centralized mappings instead of page-local labels or a synthetic readiness domain.
Filament-native UI / Action Surface Contract PASS PASS Existing Filament resources, sections, tables, infolists, view entries, and action groups are reused. No redundant inspect action or empty group is introduced.
Filament UX-001 PASS PASS No new screen type is introduced. Existing list, view, and edit surfaces remain within current Filament layout patterns while truth hierarchy changes inside those sections.
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 provider or panel 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 and edit pages. ProviderConnectionResource remains non-globally-searchable.
Destructive action safety PASS PASS No new destructive action is introduced. Existing archive, restore, force-delete, credential deletion, and disable flows remain confirmed and capability-gated.
Asset strategy PASS PASS No new asset bundle or deploy-time filament:assets change is required.
Testing truth (TEST-TRUTH-001) PASS PASS The plan adds focused regression coverage for default-visible truth hierarchy, filter semantics, and non-readiness interpretation.

Phase 0 Research

Research outcomes are captured in /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/179-provider-truth-cleanup/research.md.

Key decisions:

  • Remove tenant-list app_status prominence entirely instead of replacing it with a premature readiness badge.
  • Reuse the existing tenant-detail Provider section and providerConnectionState() helper, but make consent and verification the leading provider summary fields.
  • Keep TenantVerificationReport as the verification deep-dive and do not repurpose tenant operability gating as provider readiness truth.
  • Promote provider connection consent_status and verification_status to the lists default-visible current-state axes.
  • Split provider connection detail and edit pages into current-state truth versus diagnostics rather than one mixed Status section.
  • Keep legacy persisted fields and existing projection writers intact; this slice is presentation cleanup only.
  • Update provider filters so the main operator filter language follows consent and verification rather than legacy status projections.
  • Extend existing surface-truth tests and add one focused provider-surface truth test instead of introducing a broader test framework.

Phase 1 Design

Design artifacts are created under /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/179-provider-truth-cleanup/:

  • research.md: planning decisions and rejected alternatives
  • data-model.md: persistent source truths and derived surface truth contracts
  • contracts/provider-truth-cleanup.openapi.yaml: internal route and surface contract for tenant and provider truth cleanup
  • quickstart.md: focused implementation and validation workflow

Design highlights:

  • The tenant list will remain lifecycle-led and will not gain a new speculative readiness badge in this slice.
  • TenantResource::providerConnectionState() and the shared Provider infolist entry will be repointed to current consent and verification truth, with legacy status and health_status relegated to diagnostics.
  • Tenant.app_status will be removed from the leading tenant-list and tenant-detail operator contract rather than simply hidden behind a toggle.
  • ProviderConnectionResource will make consent and verification the default-visible list columns and primary detail/edit state section, while legacy status and health become secondary diagnostics.
  • No new readiness framework, readiness enum, or provider-state presenter will be introduced. Existing badge infrastructure will be extended only as needed with narrow centralized mappings for provider consent and provider verification, while legacy lifecycle and diagnostic mappings remain centralized.
  • Existing provider mutation, verification, and authorization behavior stays intact; only the truth hierarchy on touched surfaces changes.

Phase 1 — Agent Context Update

Run after artifact generation:

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

Project Structure

Documentation (this feature)

specs/179-provider-truth-cleanup/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── provider-truth-cleanup.openapi.yaml
├── checklists/
│   └── requirements.md
└── tasks.md

Source Code (repository root)

app/
├── Filament/
│   ├── Resources/
│   │   ├── TenantResource.php
│   │   ├── TenantResource/
│   │   │   └── Pages/
│   │   │       ├── ListTenants.php
│   │   │       └── ViewTenant.php
│   │   ├── ProviderConnectionResource.php
│   │   └── ProviderConnectionResource/
│   │       └── Pages/
│   │           ├── ListProviderConnections.php
│   │           ├── ViewProviderConnection.php
│   │           └── EditProviderConnection.php
│   └── Widgets/
│       └── Tenant/
│           └── TenantVerificationReport.php
├── Models/
│   ├── Tenant.php
│   ├── ProviderConnection.php
│   ├── ProviderCredential.php
│   └── OperationRun.php
├── Services/
│   └── Tenants/
│       └── TenantOperabilityService.php
└── Support/
    ├── Badges/
    │   ├── BadgeCatalog.php
    │   ├── BadgeDomain.php
    │   └── Domains/
    │       ├── TenantAppStatusBadge.php
    │       ├── ProviderConnectionStatusBadge.php
    │       └── ProviderConnectionHealthBadge.php
    ├── Providers/
    │   ├── ProviderConsentStatus.php
    │   └── ProviderVerificationStatus.php
    └── Tenants/
        └── TenantLifecycle.php

resources/
└── views/
    └── filament/
        ├── infolists/
        │   └── entries/
        │       └── provider-connection-state.blade.php
        └── widgets/
            └── tenant/
                └── tenant-verification-report.blade.php

tests/
├── Feature/
│   ├── Filament/
│   │   ├── TenantLifecycleStatusDomainSeparationTest.php
│   │   ├── TenantTruthCleanupSpec179Test.php
│   │   └── ProviderConnectionsDbOnlyTest.php
│   ├── ProviderConnections/
│   │   ├── ProviderConnectionTruthCleanupSpec179Test.php
│   │   ├── ProviderConnectionListAuthorizationTest.php
│   │   ├── ProviderConnectionAuthorizationTest.php
│   │   └── RequiredFiltersTest.php
│   ├── Tenants/
│   │   └── TenantProviderConnectionsCtaTest.php
│   └── Rbac/
│       └── TenantResourceAuthorizationTest.php
└── Unit/
    └── Badges/
        ├── TenantBadgesTest.php
        └── ProviderConnectionBadgesTest.php

Structure Decision: Keep the feature entirely inside the existing Laravel/Filament monolith. Update the current resources, shared provider-summary partial, tenant verification widget contract, and focused test files instead of creating a new provider-readiness subsystem.

Complexity Tracking

No Constitution Check violations are planned. No exception or bloat trigger is currently justified.

Violation Why Needed Simpler Alternative Rejected Because

Proportionality Review

No new enum, persisted entity, abstraction layer, taxonomy, or cross-domain UI framework is planned in this slice.

  • Current operator problem: Current tenant and provider surfaces let legacy status fields sound more authoritative than current lifecycle, consent, and verification truth.
  • Existing structure is insufficient because: The model already stores better truth, but the resources and shared provider-summary partial still foreground the wrong fields.
  • Narrowest correct implementation: Rewire the existing tenant and provider surfaces to consume the current truth hierarchy without inventing a new readiness model or dropping persisted compatibility fields.
  • Ownership cost created: Focused resource updates, one shared partial rewrite, and a small regression suite for surface truth hierarchy.
  • Alternative intentionally rejected: A new readiness enum, new presenter layer, or schema cleanup.
  • Release truth: Current-release truth cleanup.

Implementation Strategy

Phase A — Remove Legacy Tenant App Status From Primary Tenant Surfaces

Goal: Make tenant lifecycle the only default-visible tenant status axis on the tenant list and tenant detail identity block.

Step File Change
A.1 app/Filament/Resources/TenantResource.php Remove app_status from the default-visible tenant list column set and stop treating it as a primary list filter. Keep lifecycle and other tenant identity fields intact.
A.2 app/Filament/Resources/TenantResource.php Remove app_status from the tenant-detail Identity sections leading status contract rather than simply hiding it behind a toggle.
A.3 tests/Feature/Filament/TenantLifecycleStatusDomainSeparationTest.php and tests/Feature/Filament/TenantTruthCleanupSpec179Test.php Prove tenant list and tenant detail no longer use app_status as primary operator truth.

Phase B — Repoint Tenant Detail Provider Summary To Current Provider Truth

Goal: Make tenant detail show provider consent and verification rather than legacy provider connection status and health.

Step File Change
B.1 app/Filament/Resources/TenantResource.php Refactor providerConnectionState() to derive provider summary fields from the chosen Microsoft connections consent_status, verification_status, is_default, last_health_check_at, and last_error_reason_code, while keeping missing-connection handling explicit.
B.2 resources/views/filament/infolists/entries/provider-connection-state.blade.php Rewrite the Provider summary entry so consent and verification are the leading displayed values and legacy status and health_status move to optional diagnostics.
B.3 app/Filament/Widgets/Tenant/TenantVerificationReport.php and its Blade view Keep the existing verification widget as the deep-dive stored-report surface and ensure the surrounding tenant-detail semantics do not duplicate or contradict it.
B.4 tests/Feature/Tenants/TenantProviderConnectionsCtaTest.php and tests/Feature/Filament/TenantTruthCleanupSpec179Test.php Prove the tenant-detail provider CTA remains correct and the Provider section no longer implies readiness from legacy fields.

Goal: Make the provider connections list answer the current provider-state questions from consent and verification first.

Step File Change
C.1 app/Filament/Resources/ProviderConnectionResource.php Add default-visible consent and verification columns using existing label helpers and keep connection type and default designation as supporting facts.
C.2 app/Filament/Resources/ProviderConnectionResource.php Move legacy status and health_status columns to toggleable or explicitly diagnostic visibility and adjust visible-column defaults accordingly.
C.3 app/Filament/Resources/ProviderConnectionResource.php Replace or demote core status and health_status filters so the leading filter language becomes consent and verification, with legacy filters retained only if clearly diagnostic.
C.4 tests/Feature/ProviderConnections/RequiredFiltersTest.php, tests/Feature/Filament/ProviderConnectionsDbOnlyTest.php, and tests/Feature/ProviderConnections/ProviderConnectionTruthCleanupSpec179Test.php Prove default-visible columns and filters follow the new hierarchy without breaking DB-only rendering.

Phase D — Split Provider Connection View And Edit Into Current State Versus Diagnostics

Goal: Prevent provider connection detail and edit pages from showing current and legacy status axes as peers.

Step File Change
D.1 app/Filament/Resources/ProviderConnectionResource.php Replace the mixed Status infolist section with a primary current-state section for consent and verification and a secondary diagnostics section for legacy status, legacy health, migration review, and last-error fields.
D.2 app/Filament/Resources/ProviderConnectionResource.php Apply the same separation to the edit forms read-only state context so mutations happen in the presence of current consent and verification truth, with legacy fields shown only as diagnostics if retained.
D.3 tests/Feature/ProviderConnections/ProviderConnectionTruthCleanupSpec179Test.php Prove current state and diagnostics are visually and semantically separate on provider connection view and edit surfaces.

Phase E — Preserve Central Truth Mapping Without Adding A New Badge Framework

Goal: Keep badge and presentation semantics centralized while avoiding a new readiness taxonomy.

Step File Change
E.1 app/Support/Badges/BadgeCatalog.php, app/Support/Badges/BadgeDomain.php, app/Support/Badges/Domains/ProviderConsentStatusBadge.php, app/Support/Badges/Domains/ProviderVerificationStatusBadge.php, and existing legacy badge-domain classes Add narrow centralized badge mappings for consent_status and verification_status, keep legacy app-status or connection-status mappings diagnostic-only, and do not introduce a synthetic readiness badge domain in this slice.
E.2 tests/Unit/Badges/TenantBadgesTest.php and tests/Unit/Badges/ProviderConnectionBadgesTest.php Update unit badge coverage for the centralized provider consent and provider verification mappings and keep legacy diagnostic mapping expectations explicit.

Phase F — Protect Authorization, DB-only Rendering, And Formatting

Goal: Keep the cleanup semantically tight and operationally safe.

Step File Change
F.1 Existing tenant and provider authorization tests Re-run and extend current authorization coverage only where surface visibility or filter behavior changes could affect scope boundaries.
F.2 Focused Pest verification pack Add or update tests proving legacy-status suppression, provider current-state promotion, filter truth alignment, DB-only rendering, and denial semantics.
F.3 vendor/bin/sail bin pint --dirty --format agent and focused Pest runs Apply formatting and run the minimum Sail-first verification pack before implementation is considered complete.

Key Design Decisions

D-001 — Do not create a new readiness model in a truth-cleanup spec

The current feature exists to stop misleading primary surfaces, not to coin a new readiness taxonomy. Lifecycle, consent, and verification already provide the necessary authoritative inputs.

D-002 — The tenant list should consciously omit provider readiness rather than compress it unsafely

Any row-level provider readiness badge would require non-trivial aggregation and semantic compression across provider connections. Omitting that signal is safer and matches the specs fallback allowance.

D-003 — Reuse the existing tenant-detail Provider section instead of adding another summary layer

providerConnectionState() and the shared infolist entry already own the tenant-detail provider summary. Repointing that contract is narrower than building a new widget or presenter.

D-004 — Provider detail pages need one current-state section and one diagnostics section

Equal-weight status groupings are the core current UI problem. Separating current state from diagnostics is the smallest structural change that makes the leading truth obvious.

D-005 — Keep legacy persisted fields, but remove their authority on targeted surfaces

The model and some jobs still project status and health_status, and tenant app_status still exists in storage. This slice preserves compatibility while eliminating misleading prominence.

D-006 — Do not use tenant operability gating as provider readiness truth

TenantOperabilityService answers lifecycle and capability eligibility questions. It is not a substitute for consent or verification health and must stay separate in both implementation and messaging.

Risk Assessment

Risk Impact Likelihood Mitigation
Removing app_status from the tenant list may reduce scanability before a later readiness spec lands Medium Medium Keep the tenant list lifecycle-led, keep the provider CTA obvious on tenant detail, and treat any future list signal as follow-up work rather than a rushed addition now.
Tenant detail may still show the wrong provider connection when multiple connections exist High Medium Continue to prefer the default Microsoft connection when present and render an explicit missing or configured state rather than an optimistic readiness label.
Provider list could retain legacy semantics if filters change more slowly than columns High Medium Update both default-visible columns and core filter set together and protect them with RequiredFiltersTest plus a focused truth test.
Existing tests currently asserting app_status visibility will fail noisily Medium High Update the intentional tenant truth-separation test first and add a spec-specific replacement that encodes the new contract.
New tenant-list provider queries could create unnecessary DB cost Medium Low Avoid adding a new per-row provider signal on the tenant list in this slice.

Test Strategy

  • Update tests/Feature/Filament/TenantLifecycleStatusDomainSeparationTest.php so it protects the new tenant truth hierarchy instead of asserting legacy app_status prominence.
  • Add tests/Feature/Filament/TenantTruthCleanupSpec179Test.php to cover tenant list default-visible truth and tenant detail Provider section semantics.
  • Add tests/Feature/ProviderConnections/ProviderConnectionTruthCleanupSpec179Test.php to cover provider connection list, view, and edit truth hierarchy.
  • Update tests/Feature/ProviderConnections/RequiredFiltersTest.php so the core filter contract follows consent and verification instead of legacy status or health.
  • Update tests/Feature/Filament/ProviderConnectionsDbOnlyTest.php so visible-column expectations and DB-only rendering stay valid after the list contract changes.
  • Keep tests/Feature/Tenants/TenantProviderConnectionsCtaTest.php and existing authorization tests in the focused pack so route and scope semantics do not regress.
  • Keep tenant global-search safety and provider-connection global-search exclusion in the focused pack so discovery behavior does not regress.
  • Finish the feature with an explicit no-migration and no-new-persistence diff validation gate in addition to runtime regression coverage.
  • Run the focused Sail-first pack defined in quickstart.md, then run vendor/bin/sail bin pint --dirty --format agent before closing implementation.

Constitution Check (Post-Design)

Re-check result: PASS.

  • Livewire v4.0+ compliance: preserved because the plan stays within the current Filament v5 + Livewire v4 resources, widgets, and pages.
  • Provider registration location: unchanged; no panel or provider registration work is needed beyond the existing bootstrap/providers.php setup.
  • Global-searchable resources: TenantResource remains globally searchable and already has both view and edit pages; ProviderConnectionResource remains non-globally-searchable, so no global-search conflict is introduced.
  • Destructive actions: no new destructive action is added. Existing tenant archive, restore, and force-delete flows and existing provider credential or connection-state mutations remain confirmation-protected and capability-gated.
  • Asset strategy: unchanged; no new assets are introduced and the deploy-time php artisan filament:assets process remains unaffected.
  • Testing plan: focused Pest coverage will protect tenant legacy-status suppression, tenant-detail provider summary truth, provider current-state promotion, filter hierarchy, DB-only rendering, and unchanged authorization boundaries.