# 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.