# Data Model — Spec 139 (Verify Access Required Permissions Assist) ## Existing persisted entities reused ### TenantOnboardingSession (existing) - Source: `app/Models/TenantOnboardingSession.php` - Role in this feature: - Anchors the current onboarding draft and current wizard step. - Holds the selected provider connection and latest verification run reference in `state`. - Relevant persisted state: - `tenant_id` - `current_step` - `state.provider_connection_id` - `state.verification_operation_run_id` ### OperationRun (existing) - Source: `app/Models/OperationRun.php` - Role in this feature: - Supplies the latest stored verification result used by Verify Access. - Remains the source of truth for the existing verification report. - Relevant persisted context: - `context.provider_connection_id` - `context.target_scope` - `context.verification_report` ### TenantPermission dataset (existing) - Source: `tenant_permissions` via `TenantPermissionService::compare(... persist: false, liveCheck: false)` - Role in this feature: - Supplies the current DB-only permission diagnostics summary and missing-permission breakdown used by the assist and full page. ## Computed view models ### VerificationAssistVisibility (computed) - Purpose: determines whether the Verify Access step should expose `View required permissions`. - Inputs: - Stored verification report overall/check state. - Existing permission diagnostics summary. - Stale verification context for the selected provider connection. - Shape: - `is_visible: bool` - `reason: 'permission_blocked' | 'permission_attention' | 'hidden_ready' | 'hidden_irrelevant'` ### VerificationAssistViewModel (computed) - Purpose: compact payload rendered inside the slideover. - Derived from: - `verificationReportViewData()` output - `TenantRequiredPermissionsViewModelBuilder::build($tenant, ...)` - Shape: - `tenant: { id: int, external_id: string, name: string }` - `verification: { overall: string|null, status: string|null, is_stale: bool, stale_reason: string|null }` - `overview: { overall: string, counts: { missing_application: int, missing_delegated: int, present: int, error: int }, freshness: { last_refreshed_at: string|null, is_stale: bool } }` - `missing_permissions: { application: PermissionRow[], delegated: PermissionRow[] }` - `copy: { application: string, delegated: string }` - `actions: AssistActions` - `fallback: { has_incomplete_detail: bool, message: string|null }` ### PermissionRow (existing computed shape reused) - Source: `TenantRequiredPermissionsViewModelBuilder` - Shape: - `key: string` - `type: 'application' | 'delegated'` - `description: string|null` - `features: string[]` - `status: 'granted' | 'missing' | 'error'` - `details: array|null` ### AssistActions (computed) - Purpose: explicit, authorization-safe actions shown in the slideover. - Shape: - `full_page: { label: string, url: string, opens_in_new_tab: true, is_secondary: true }` - `copy_application: { label: string, payload: string, available: bool }` - `copy_delegated: { label: string, payload: string, available: bool }` - `grant_admin_consent: { label: string, url: string|null, opens_in_new_tab: bool, available: bool }` - `manage_provider_connection: { label: string, url: string|null, opens_in_new_tab: bool, available: bool }` - `rerun_verification: { label: string, handled_by_existing_wizard: true }` ### VerificationLinkBehavior (computed) - Purpose: normalize how Verify Access next-step links are rendered. - Shape: - `label: string` - `url: string` - `kind: 'external' | 'internal-diagnostic' | 'internal-inline-safe'` - `opens_in_new_tab: bool` - `show_new_tab_hint: bool` ## State transitions ### Assist visibility transition - Hidden → Visible when: - verification report indicates permission-related blockers or relevant needs-attention state, and - the current user remains authorized for the underlying tenant context. - Visible → Hidden when: - verification becomes fully ready with no relevant permission guidance, or - authorization context no longer permits the assist. ### Wizard continuity invariant - Opening the assist does not change `TenantOnboardingSession.current_step`. - Closing the assist does not mutate onboarding draft state. - Opening the full page from the assist does not navigate the current onboarding tab away from the wizard. ### Copy-action availability invariant - A copy action is available only when the corresponding `copy.*` payload is a non-empty string. - Empty payloads result in omitted or replaced controls, not broken actions. ## Authorization model reuse - Onboarding wizard access continues to depend on workspace membership plus onboarding capability rules. - Required Permissions deep-dive access continues to depend on tenant entitlement and existing page authorization. - The assist is visible only when both contexts are valid enough to avoid leaking tenant or permission detail. ## No persistence changes - No new tables. - No new route-backed models. - No new onboarding-only permissions store. - No mutation to the existing Required Permissions page role.