# Implementation Plan: Verification Surfaces Unification **Branch**: `084-verification-surfaces-unification` | **Date**: 2026-02-09 | **Spec**: `specs/084-verification-surfaces-unification/spec.md` **Input**: Feature specification from `specs/084-verification-surfaces-unification/spec.md` ## Summary Unify tenant “Verify configuration” and onboarding “Verify access” to the same `OperationRun`-based flow (`provider.connection.check`), with DB-only viewing and canonical tenantless run links. Ensure any **completed blocked** verification run persists a **schema-valid** `context.verification_report` stub so viewers never show “report unavailable” for blocked completions. ## Technical Context **Language/Version**: PHP 8.4 (Laravel 12) **Primary Dependencies**: Filament v5 (Livewire v4), Queue/Jobs (Laravel), Microsoft Graph via `GraphClientInterface` **Storage**: PostgreSQL (JSONB-backed `OperationRun.context`) **Testing**: Pest v4 (Feature tests), Filament/Livewire component testing where applicable **Target Platform**: Web application (Sail-first local dev) **Performance Goals**: - Tenant detail + onboarding verification surfaces render DB-only with no external provider/Graph calls. - Start action returns quickly (authorize → create/dedupe run → enqueue job → notify + “View run”). **Constraints**: - RBAC isolation: non-members are deny-as-not-found (404); members missing capability are 403 on execution. - `OperationRun` active dedupe enforced (already handled via `OperationRunService::ensureRunWithIdentity()` + active-run checks). **Scale/Scope**: Tenant-scoped verification for provider connection health + permission inventory refresh (existing behavior). ## Constitution Check *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* - Inventory-first / Read-write separation: PASS (verification is an explicit user-triggered operation; viewing is read-only). - Graph contract path: PASS (provider verification uses `ProviderGateway` + `GraphClientInterface`; no render-time Graph calls). - Workspace/Tenant isolation: PASS (tenantless canonical views must still enforce workspace + tenant entitlement; missing either is 404). - RBAC-UX 404/403 split: PASS (start is 403 for members missing capability; non-members 404; Livewire calls included). - Run observability: PASS (verification is queued and tracked as `OperationRun`; start surfaces enqueue-only; Monitoring is DB-only). - Data minimization/safe logging: PASS (verification report stored in `OperationRun.context`; no secrets; next steps are link-only). - Filament action safety: PASS (verification start uses `->action(...)`; any destructive action confirmations remain required). No constitution violations are required for this feature. ## Project Structure ### Documentation (this feature) ```text specs/084-verification-surfaces-unification/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md └── contracts/ ├── operation-run-context.provider-connection-check.schema.json └── verification-surfaces.routes.md ``` ### Source Code (existing, relevant) ```text app/ ├── Filament/ │ ├── Resources/ │ │ ├── TenantResource.php │ │ └── TenantResource/Pages/ViewTenant.php │ ├── Pages/Workspaces/ManagedTenantOnboardingWizard.php │ └── Support/VerificationReportViewer.php ├── Jobs/ProviderConnectionHealthCheckJob.php ├── Services/ │ ├── Providers/ProviderOperationStartGate.php │ ├── OperationRunService.php │ └── Verification/StartVerification.php └── Support/ ├── OperationRunLinks.php └── Verification/VerificationReportWriter.php resources/views/filament/components/verification-report-viewer.blade.php resources/views/filament/forms/components/managed-tenant-onboarding-verification-report.blade.php ``` ## Phase 0 — Outline & Research ### Key decisions (grounded in current code) - Use `provider.connection.check` as the unified verification run type. - Already used by onboarding (`ManagedTenantOnboardingWizard::startVerification()`) and by `StartVerification::providerConnectionCheck()`. - Tenant verification start surfaces (tenant detail and tenant list actions) will be refactored to start/dedupe `provider.connection.check` via `StartVerification`, always resolve the tenant's default provider connection, and always offer a canonical “View run” link. - `StartVerification` API changes remain non-breaking in this feature (keep existing explicit-connection method; add a tenant-default start helper rather than replacing signatures). - Blocked runs must write a schema-valid stub report: - Implement stub generation immediately after `OperationRunService::finalizeBlockedRun()` for the `provider.connection.check` operation type, using `VerificationReportWriter::write(...)`. - Tenantless canonical run viewing for tenant-associated runs is a foundational blocker and must enforce workspace membership + tenant entitlement with deny-as-not-found semantics before user-story completion. ### Outputs - `research.md` records decisions + rationale + alternatives. ## Phase 1 — Design & Contracts ### Data model (no DB migration expected) - Store verification report exclusively in `operation_runs.context.verification_report` (existing pattern). - Enforce: when a `provider.connection.check` run completes with outcome `blocked`, `context.verification_report` is present and schema-valid. ### Contracts - `contracts/operation-run-context.provider-connection-check.schema.json` - Documents expected `OperationRun.context` keys for the verification run type. - `contracts/verification-surfaces.routes.md` - Documents user actions → routes/surfaces and the canonical run viewer URL. ### Outputs - `data-model.md`, `contracts/*`, `quickstart.md`. ## Phase 2 — Implementation Planning (for `/speckit.tasks`) Planned work items to convert into `tasks.md`: 1. Refactor tenant verification actions (tenant detail + tenant list) to use the unified start path (`provider.connection.check`) with default connection resolution, returning started/deduped/busy outcomes with a canonical run URL. 2. Add tenant embedded verification viewer: - Select latest `provider.connection.check` run attempt for the tenant. - Show DB-only empty state when none exists. - Show DB-only “in progress” state when active with no report yet. 3. Ensure blocked verification runs always store a schema-valid stub report: - Post-`finalizeBlockedRun()` write via `VerificationReportWriter` for `provider.connection.check`. 4. Authorization + isolation (blocking): - Non-members: 404 for tenant routes and tenantless operations viewer of tenant-associated runs. - Members missing capability: UI visible-but-disabled; server returns 403. 5. Tests (Pest): - Blocked start produces a completed blocked run with a schema-valid `verification_report`. - Tenant page and onboarding viewer render from stored report only (no external calls during render). - Tenant render path never persists permission inventory updates and never uses synchronous verification paths. - Canonical run links point to `admin.operations.view` (tenantless).