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
71 lines
4.4 KiB
Markdown
71 lines
4.4 KiB
Markdown
# 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 connection’s 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.
|
||
|