TenantAtlas/specs/216-provider-dispatch-gate/data-model.md
Ahmed Darrazi 34230be79d
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m21s
feat: unify provider-backed action dispatch gating
2026-04-20 08:47:08 +02:00

236 lines
9.8 KiB
Markdown

# Data Model: Provider-Backed Action Preflight and Dispatch Gate Unification
## Overview
This feature does not introduce new persisted entities or tables. It extends the existing provider-backed start contract around `ProviderConnection`, `OperationRun`, and onboarding draft state so every covered operator-triggered start follows the same queue-admission, conflict-protection, and operator-feedback rules.
The key design constraint is that start truth remains service-owned and derived from existing runtime records:
- provider readiness and connection identity from `ProviderConnection` plus `ProviderConnectionResolver`
- accepted or prevented work truth from `OperationRun`
- click-time queue-admission decisions from `ProviderOperationStartGate`
- onboarding bootstrap continuity from existing `TenantOnboardingSession.state`
- operator feedback from existing Ops UX helpers plus one thin shared provider-start presentation helper
## Existing Persistent Inputs
### 1. ProviderConnection
- Purpose: Tenant-owned provider credential and readiness record that defines which delegated connection a provider-backed operation can use.
- Key persisted fields used by this feature:
- `id`
- `tenant_id`
- `provider`
- `entra_tenant_id`
- Existing runtime facts consumed through current services:
- default-vs-explicit selection
- consent readiness
- credential usability
- provider identity and scope targeting
- Relationships used by this feature:
- owning tenant
- related operation runs through `OperationRun.context.provider_connection_id`
### 2. OperationRun
- Purpose: Canonical operational truth for queued or executed provider-backed work and for blocked preflight attempts that must remain observable.
- Key persisted fields used by this feature:
- `id`
- `tenant_id`
- `type`
- `status`
- `outcome`
- `reason_code`
- `context`
- `summary_counts`
- `started_at`
- `completed_at`
- Existing relationships or references used by this feature:
- initiator user
- tenant scope
- canonical Monitoring → Operations detail route
### 3. TenantOnboardingSession
- Purpose: Workspace-owned onboarding workflow record that already stores onboarding progress and step state, including bootstrap operation selections and related run references.
- Key persisted fields used by this feature:
- `id`
- `workspace_id`
- `tenant_id` (nullable until attached)
- `current_step`
- `state`
- Existing state keys used by this feature:
- `bootstrap_operation_types`
- `bootstrap_operation_runs`
- Relationships used by this feature:
- workspace
- attached tenant when present
### 4. RestoreRun
- Purpose: Tenant-owned restore execution record whose execute action becomes part of the canonical provider-backed start contract.
- Key persisted fields used by this feature:
- `id`
- `tenant_id`
- restore configuration and preview state already captured before execution
- Relationships used by this feature:
- tenant
- backup source records and restore safety flow already owned by existing restore logic
## Existing Service-Owned Inputs
### A. ProviderOperationRegistry Entry
This is an existing logical definition, not a new persisted entity.
| Field | Meaning |
|---|---|
| `operationType` | The write-time operation type string admitted by the gate |
| `module` | Current operation family/module metadata used in run context |
| `dispatcher` | The queue-dispatch callback or equivalent start hook |
| `requiredCapability` | Capability gate required to start the operation |
| `providerScopeExpectation` | Whether the operation is connection-scoped and therefore protected by click-time conflict rules |
Rules:
- First-slice coverage adds entries for every covered action host instead of introducing a second registry.
- This feature does not normalize historical operation names; registry entries follow current write-time operation types.
### B. ProviderConnectionResolution
Logical output of `ProviderConnectionResolver` and `ProviderOperationStartGate`.
| Field | Meaning |
|---|---|
| `providerConnectionId` | Explicit resolved connection identity when admission is possible |
| `provider` | Provider family used for copy and downstream dispatch |
| `targetScope` | Current tenant/provider scope metadata for run context |
| `reasonCode` | Stable blocked reason when admission is prevented |
| `reasonMeta` | Sanitized structured detail for translation and next steps |
| `nextSteps` | Resolution path metadata used by the shared presentation helper |
Rules:
- Every accepted covered start must resolve this state before queue admission.
- Blocked resolutions never admit a queued job.
## Derived Coordination Entities
### 1. ProtectedProviderScope
This is a logical concurrency boundary, not a new table.
| Field | Meaning |
|---|---|
| `tenantId` | Tenant boundary for the operation |
| `providerConnectionId` | Connection boundary for provider-backed conflict protection |
| `activeRunId` | Existing queued or running run occupying the scope, when present |
| `activeOperationType` | Operation currently using the protected scope |
Rules:
- At most one provider-backed operation may be accepted at a time for one protected scope.
- If the same operation type is already active on the scope, the result is `deduped`.
- If a different covered operation is already active on the same scope, the result is `scope_busy`.
### 2. PreflightStartResult
This is the shared logical result of a covered start attempt.
| Field | Meaning | Source |
|---|---|---|
| `internalState` | Current service-owned state such as `started`, `deduped`, `scope_busy`, or `blocked` | `ProviderOperationStartResult` |
| `operatorState` | Operator-facing vocabulary: `accepted`, `deduped`, `scope_busy`, `blocked` | shared presenter |
| `operationType` | Covered operation being started | action host + registry |
| `runId` | Canonical run for accepted, deduped, or scope-busy results, and optionally for blocked truth where already created | `OperationRun` |
| `providerConnectionId` | Resolved connection identity when known | gate + resolver |
| `reasonCode` | Stable problem class for blocked or other directed outcomes | current reason system |
| `nextSteps` | Structured resolution or follow-up guidance | next-step registry / helper |
Validation rules:
- `blocked` must never dispatch a background job.
- `accepted`, `deduped`, and `scope_busy` must point to the canonical run the operator should inspect.
- `accepted` must carry the explicit `provider_connection_id` into accepted-work context.
### 3. AcceptedProviderBackedRunContext
Logical accepted-work context persisted inside `OperationRun.context`.
| Field | Meaning |
|---|---|
| `provider_connection_id` | Explicit connection identity used by the accepted run |
| `provider` | Provider family label for display and diagnostics |
| `target_scope` | Sanitized tenant/provider scope metadata |
| `source_surface` | Action-host family such as tenant detail, provider connection, onboarding, restore, or directory sync |
| `initiator_user_id` | Starting actor |
| `operation_specific_context` | Existing per-operation context already needed by downstream jobs |
Rules:
- Jobs must receive the same `provider_connection_id` that was accepted at click time.
- Monitoring and notifications explain accepted work using this context rather than a runtime default connection lookup.
### 4. ProviderStartPresentation
Logical derived presentation output returned by the shared start-result helper.
| Field | Meaning |
|---|---|
| `title` | Standardized accepted/deduped/scope-busy/blocked headline |
| `body` | Short reason or queue message aligned with the spec vocabulary |
| `statusStyle` | Existing toast/notification severity |
| `viewRunAction` | Canonical open-run action when a run exists |
| `nextStepActions[]` | Optional resolution actions or follow-up links |
| `domainVerb` | Local action verb preserved from the host surface |
| `domainTarget` | Local object noun preserved from the host surface |
Rules:
- Accepted and deduped outcomes continue to use the existing `OperationUxPresenter` toast style.
- Blocked and scope-busy outcomes must no longer rely on page-local copy branches.
- Domain verb and target remain local so the shared contract does not flatten the product vocabulary into generic verbs.
### 5. OnboardingBootstrapAdmission
Logical onboarding-only coordination model built from existing draft state.
| Field | Meaning |
|---|---|
| `selectedOperationTypes[]` | Bootstrap operations the operator selected |
| `acceptedOperationType` | The one operation type admitted for the current submission |
| `pendingOperationTypes[]` | Remaining selected types still waiting to run |
| `runIdsByOperationType` | Existing and newly accepted run references persisted in draft state |
Rules:
- A bootstrap submission may accept at most one provider-backed operation for the protected scope.
- Remaining bootstrap selections stay in existing draft state rather than spawning a new orchestration entity.
- The onboarding step continues to be the only primary context; operators should not need a new workflow page to understand the pending follow-up.
## State Transitions
### Covered Start Attempt
```text
requested
-> blocked (no queue admission, blocked truth retained where applicable)
-> deduped (existing same-operation active run reused)
-> scope_busy (existing different-operation active run reused)
-> accepted (run admitted, queued job dispatched with explicit provider connection)
```
### Onboarding Bootstrap Submission
```text
selected operations
-> blocked / deduped / scope_busy (no new run admitted)
-> accepted operation + pending operations retained in draft state
```
## Non-Goals In The Data Model
- No new provider-start table or umbrella batch entity
- No new persisted summary or presentation artifact
- No platform-wide operation-type rename or taxonomy rewrite
- No new status family beyond the shared start-result vocabulary already required by the spec