# 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).
