4.4 KiB
4.4 KiB
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.checkas 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 viaVerificationReportWriter::write(...). - Dedupe and “scope busy” semantics are already implemented in
ProviderOperationStartGate.
- This run type already exists and is used by onboarding (
- 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.
- Create a new run type (e.g.,
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:- authorizes,
- creates/dedupes an
OperationRun, - dispatches
ProviderConnectionHealthCheckJob, - 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.
- Constitution requires external calls be observable and performed asynchronously via
- 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) forprovider.connection.check, immediately write a stubcontext.verification_reportusingVerificationReportWriter. - Rationale:
- Both verification viewers render DB-only and expect a report for completed runs.
OperationRunService::finalizeBlockedRun()currently setscontext.reason_codeandcontext.next_stepsbut 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
VerificationReportViewerto 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.
- Modify
Decision 4: Embedded tenant viewer selects latest run attempt for tenant + type
- Decision: In the tenant view, select the latest
OperationRunattempt 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_idand only show the default connection’s run. Rejected because it can hide recent verification attempts started against a different (now selected) connection.
- Select by
Decision 5: Canonical tenantless run links are mandatory
- Decision: All “View run” CTAs use
OperationRunLinks::tenantlessView($runId)(routeadmin.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.