TenantAtlas/specs/084-verification-surfaces-unification/research.md
ahmido 0e2adeab71 feat(verification): unify verification surfaces (Spec 084) (#102)
Implements Spec 084 (verification-surfaces-unification).

Highlights
- Unifies tenant + onboarding verification start on `provider.connection.check` (OperationRun-based, enqueue-only).
- Ensures completed blocked runs persist a schema-valid `context.verification_report` stub (DB-only viewers never show “unavailable”).
- Adds tenant embedded verification report widget with DB-only rendering + canonical tenantless “View run” links.
- Enforces 404/403 semantics for tenantless run viewing (workspace membership + tenant entitlement required; otherwise 404).
- Fixes admin panel widgets to resolve tenant from record context so Owners can start verification and recent operations renders correctly.

Tests
- Ran: `vendor/bin/sail artisan test --compact tests/Feature/Verification/ tests/Feature/ProviderConnections/ProviderOperationBlockedGuidanceSpec081Test.php tests/Feature/Onboarding/OnboardingVerificationTest.php tests/Feature/RunAuthorizationTenantIsolationTest.php tests/Feature/Filament/TenantVerificationReportWidgetTest.php tests/Feature/Filament/RecentOperationsSummaryWidgetTest.php`

Notes
- Filament v5 / Livewire v4 compatible.
- No new assets; no changes to provider registration.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #102
2026-02-09 11:28:09 +00:00

71 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research — Verification Surfaces Unification (Spec 084)
Date: 2026-02-09
## Decision 1: Unify on `provider.connection.check` OperationRun type
- Decision: Use the existing `OperationRun.type = provider.connection.check` as the single verification run type for both:
- Tenant detail “Verify configuration”
- Onboarding “Verify access”
- Rationale:
- This run type already exists and is used by onboarding (`ManagedTenantOnboardingWizard::startVerification()`).
- The job (`ProviderConnectionHealthCheckJob`) already produces a schema-valid verification report via `VerificationReportWriter::write(...)`.
- Dedupe and “scope busy” semantics are already implemented in `ProviderOperationStartGate`.
- Alternatives considered:
- Create a new run type (e.g., `tenant.verification`). Rejected because it would duplicate existing job logic and complicate dedupe and viewer behavior.
## Decision 2: Tenant verification start uses `StartVerification` / `ProviderOperationStartGate` (enqueue-only)
- Decision: Replace the tenant detail synchronous verification (`TenantResource::verifyTenant()`) with an enqueue-only start that:
1) authorizes,
2) creates/dedupes an `OperationRun`,
3) dispatches `ProviderConnectionHealthCheckJob`,
4) returns a canonical “View run” link.
- Rationale:
- Constitution requires external calls be observable and performed asynchronously via `OperationRun`.
- The current tenant action performs Graph calls inline; onboarding already uses the queued run model.
- Unifies UX and operational auditability.
- Alternatives considered:
- Keep tenant verification synchronous and only add a “view last run” viewer. Rejected because it preserves inconsistency and violates run observability for remote calls.
## Decision 3: Completed blocked verification runs MUST always have a schema-valid stub report
- Decision: When a verification run is finalized as blocked (outcome `blocked`) for `provider.connection.check`, immediately write a stub `context.verification_report` using `VerificationReportWriter`.
- Rationale:
- Both verification viewers render DB-only and expect a report for completed runs.
- `OperationRunService::finalizeBlockedRun()` currently sets `context.reason_code` and `context.next_steps` but does not write a report, which produces a “report unavailable” state.
- A stub report can encode the reason code and next steps in a consistent, schema-valid format.
- Alternatives considered:
- Modify `VerificationReportViewer` to fabricate a report at render time if blocked. Rejected because rendering must be DB-only and deterministic, and should not create derived data in the UI layer.
- Add report writing inside `OperationRunService::finalizeBlockedRun()` for all operations. Rejected because not all blocked operations are “verification” and we should not inject verification reports into unrelated runs.
## Decision 4: Embedded tenant viewer selects latest run attempt for tenant + type
- Decision: In the tenant view, select the latest `OperationRun` attempt by:
- `tenant_id = current tenant`,
- `type = provider.connection.check`,
- ordered by `id desc`.
- Rationale:
- Matches the clarified spec requirement: latest attempt even if queued/running.
- Avoids coupling selection to provider connection id.
- Alternatives considered:
- Select by `context.provider_connection_id` and only show the default connections run. Rejected because it can hide recent verification attempts started against a different (now selected) connection.
## Decision 5: Canonical tenantless run links are mandatory
- Decision: All “View run” CTAs use `OperationRunLinks::tenantlessView($runId)` (route `admin.operations.view`).
- Rationale:
- Canonical URL improves supportability and reduces ambiguity.
- Tenantless views must still enforce workspace + tenant entitlement (404 if missing).
- Alternatives considered:
- Use tenant-scoped run URLs for tenant pages. Rejected because canonical linking is a core requirement.
## Decision 6: Authorization semantics follow RBAC-UX 404/403 split
- Decision:
- Non-members (missing workspace membership or tenant entitlement): deny-as-not-found (404) for tenant routes and tenantless operation views of tenant-associated runs.
- Members without capability: show action visible-but-disabled (UX), but server enforces 403 on attempt.
- Rationale:
- Matches constitution RBAC-UX-002 and RBAC-UX-003.