# Data Model — Provider Access Hardening v1 (Intune Write Gate)

## Entities (existing)

### Tenant

Used as the persisted source of truth for the gate.

- `tenants.rbac_status` (nullable string)
  - Expected values used by this feature: `null`, `not_configured`, `ok`, `degraded`, `failed`
- `tenants.rbac_status_reason` (nullable string)
  - Human-readable/safe explanation from last check / setup.
- `tenants.rbac_last_checked_at` (nullable timestamp)
  - Used for freshness evaluation.

Relationships:

- `Tenant` → hasMany `AuditLog`
- `Tenant` → hasMany `OperationRun` (tenant-scoped runs)

### OperationRun

Used to record outcomes for queued/async work.

- `operation_runs.type`
  - Existing types touched by this feature (no new types introduced):
    - `restore.execute`
    - `assignments.restore`
    - health checks: reuse existing verification/provider connection check type(s)
- `operation_runs.failures` / failure metadata
  - Must include stable reason codes when gate blocks a queued job.

### RestoreRun

Represents the restore workflow state and links to an `OperationRun`.

- `restore_runs.operation_run_id` (FK to `operation_runs`)
- `restore_runs.status` transitions drive UX and notifications.

### AuditLog

Used to record UI-level blocked write attempts (P3 in spec).

- Must store stable `action` (e.g., `intune_rbac.write_blocked`)
- Must store tenant scope and sanitized metadata (no tokens, no raw Graph payloads)

## Domain Objects (new / feature-scoped)

### IntuneRbacWriteGate (service)

- Inputs: `Tenant`, operation identifier (string), “write class” operation
- Output: allowed or throws a domain exception containing:
  - `reason_code`: one of
    - `intune_rbac.not_configured`
    - `intune_rbac.unhealthy`
    - `intune_rbac.stale`
  - `reason_message`: sanitized, user-facing message

Freshness rule:

- Evaluate staleness by comparing `tenant.rbac_last_checked_at` to `now() - threshold`.
- Threshold is configurable (default 24 hours).

Config toggle:

- If disabled, gate always allows but logs a warning per evaluation (observability).

### ProviderAccessHardeningRequired (exception)

- Carries tenant + operation + reason code + safe message.
- Used by both start surfaces (to present UX) and jobs (to fail `OperationRun` safely).

## State transitions (gate perspective)

- **Allowed**: `rbac_status = ok` AND `rbac_last_checked_at` is within freshness threshold.
- **Blocked — Not configured**: `rbac_status is null` OR `rbac_status = not_configured`.
- **Blocked — Unhealthy**: `rbac_status in {degraded, failed}`.
- **Blocked — Stale**: `rbac_status = ok` but `rbac_last_checked_at` is older than threshold.

## Validation & safety rules

- Gate evaluation must be DB-only (no Graph calls).
- Blocked paths must ensure **zero Graph write calls**.
- Failure metadata must be sanitized and stable.
