# Feature Specification: Entra Admin Roles Evidence + Findings

**Feature Branch**: `105-entra-admin-roles-evidence-findings`
**Created**: 2026-02-21
**Status**: Draft
**Input**: User description: "Entra Admin Roles Evidence + Findings (StoredReports + Alerts) — auditierbar beantworten: wer hat welche Entra Admin-Rollen, welche sind high privilege, hat sich die Lage verändert (drift), und kann ich das als Evidence snapshotten und als Findings/Alerts operationalisieren."

## Spec Scope Fields *(mandatory)*

- **Scope**: tenant (per-tenant Entra role assignment posture) + workspace (alerts infrastructure extends workspace-level)
- **Primary Routes**:
  - No new standalone Filament pages in this spec
  - Existing: Monitoring > Findings (extended with `finding_type=entra_admin_roles`)
  - Existing: Monitoring > Alerts (extended with new `entra.admin_roles.high` event type)
  - Existing: Tenant dashboard card showing admin roles summary (new card widget)
  - Existing: Stored Reports viewer filtered to `report_type=entra.admin_roles`
- **Data Ownership**:
  - `stored_reports` — tenant-owned, reused from Spec 104 (new `report_type=entra.admin_roles`)
  - `findings` — tenant-owned, extended with `finding_type=entra_admin_roles` (existing table)
  - `config/entra_permissions.php` — new registry file for Entra-specific Graph permissions (workspace infra)
- **RBAC**:
  - Workspace membership required for any access (non-members → 404)
  - New capabilities: `ENTRA_ROLES_VIEW` (see Admin Roles card + report viewer), `ENTRA_ROLES_MANAGE` (trigger on-demand scan)
  - `entra_admin_roles` findings in the Findings list are gated by existing `FINDINGS_VIEW` — `ENTRA_ROLES_VIEW` is NOT required to see them
  - Readonly/Operator roles → `ENTRA_ROLES_VIEW`
  - Manager/Owner roles → `ENTRA_ROLES_MANAGE`
  - Findings remain gated by existing `TENANT_FINDINGS_ACKNOWLEDGE` for acknowledge actions

## Assumptions and Dependencies

- **Spec 104 (Provider Permission Posture)** established the `stored_reports` table, `StoredReport` model, fingerprint-based dedupe, and the posture report pattern. This spec reuses that infrastructure with a new `report_type`.
- **StoredReports retention** is handled by existing infrastructure and defaults to 90 days.
- **Alerts v1 (Spec 099)** provides the generic `AlertDispatchService`, `EvaluateAlertsJob` framework, and `AlertRule` model. This spec adds a new event type constant; no structural changes to alerting.
- **Findings model** already supports `finding_type`, `source`, `subject_type`, `subject_external_id`, severity, status (including `resolved`), and fingerprint-based idempotent upsert (established by drift findings + Spec 104).
- **`config/intune_permissions.php`** is the existing registry for Intune-specific Graph permissions. A new `config/entra_permissions.php` will be created for Entra Directory permissions. The registry loader (`TenantPermissionService` or equivalent) must merge both sources.
- **Microsoft Graph unified RBAC API** provides `roleManagement/directory/roleDefinitions` and `roleManagement/directory/roleAssignments?$expand=principal` for reading directory role data.
- **No PIM/Eligible Assignments** in v1 — only active (permanent) role assignments are covered.
- **No automatic remediation** — this spec covers evidence collection, findings, and alerts only.

## User Scenarios and Testing *(mandatory)*

### User Story 1 — Scan Entra admin role assignments and produce evidence snapshot (Priority: P1)

As a workspace operator, I want the system to scan a tenant's Entra directory role definitions and active role assignments, then persist the results as a stored report, so that I have an auditable snapshot of who holds which admin roles at any point in time.

**Why this priority**: This is the foundational data pipeline. Without fetching and persisting role assignment data, no findings, alerts, or UI can function.

**Independent Test**: Trigger an on-demand scan for a tenant; confirm a `stored_report` record exists with `report_type=entra.admin_roles`, a valid payload containing `role_definitions`, `role_assignments`, `totals`, and a content-based fingerprint.

**Acceptance Scenarios**:

1. **Given** a tenant has a configured provider connection with `RoleManagement.Read.Directory` permission, **When** the admin roles scan runs, **Then** a `stored_report` is created with `report_type=entra.admin_roles`, `tenant_id` set, and a JSONB payload containing `role_definitions`, `role_assignments`, and `totals`.
2. **Given** the scan runs twice with identical role assignment data, **When** the second scan completes, **Then** no duplicate stored report is created (fingerprint-based dedupe).
3. **Given** a role assignment has changed between scans (e.g., new Global Admin added), **When** the second scan completes, **Then** a new stored report is created with a different fingerprint, and `previous_fingerprint` references the prior report's fingerprint.
4. **Given** the tenant has no provider connection, **When** the scan is dispatched, **Then** the job completes gracefully with no stored report or findings created.
5. **Given** the Graph API is unreachable during the scan, **When** the job fails, **Then** the `OperationRun` records the failure with error details and no stored report or findings are created.

---

### User Story 2 — Generate findings for high-privilege role assignments (Priority: P1)

As a compliance officer, I want the system to automatically generate findings for high-privilege Entra admin role assignments, so that I can track privileged access posture and identify excessive admin patterns.

**Why this priority**: Findings are the actionable output of the scan. Without them, there is no governance signal.

**Independent Test**: Run the finding generator for a tenant with a Global Administrator assignment; confirm a finding with `finding_type=entra_admin_roles`, `severity=critical`, and the correct fingerprint exists.

**Acceptance Scenarios**:

1. **Given** a tenant has 3 users assigned to "Global Administrator", **When** the finding generator runs, **Then** 3 individual findings are created (one per principal per role), each with `finding_type=entra_admin_roles`, `severity=critical`, and a deterministic fingerprint.
2. **Given** a tenant has a user assigned to "Security Administrator" (high privilege, not Global Admin), **When** the finding generator runs, **Then** a finding is created with `severity=high`.
3. **Given** a previously-flagged principal's high-privilege role assignment is removed, **When** the next scan runs the finding generator, **Then** the corresponding finding is auto-resolved (`status=resolved`, `resolved_at` set, `resolved_reason=role_assignment_removed`).
4. **Given** the same principal still holds the same high-privilege role across two scans, **When** the finding generator runs again, **Then** no duplicate finding is created (fingerprint idempotency); `times_seen` and `last_seen_at` are updated.
5. **Given** a tenant has more than 5 principals assigned to "Global Administrator", **When** the finding generator runs, **Then** an aggregate finding "Too many Global Admins" is created with `severity=high` and a distinct fingerprint.
6. **Given** a resolved finding's principal is re-assigned the same high-privilege role, **When** the finding generator runs, **Then** the finding is re-opened (`status=new`, `resolved_at`/`resolved_reason` cleared, evidence updated).

---

### User Story 3 — Alert on high-privilege admin role events (Priority: P2)

As a workspace manager, I want to receive alerts (Teams/email) when new high-privilege Entra admin role assignments are detected, so I can react to privilege escalation without polling the UI.

**Why this priority**: Alerts are the push-notification layer. They close the feedback loop for time-sensitive governance signals.

**Independent Test**: Create an alert rule for the new event type, run the finding generator with a new Global Admin assignment, and confirm a delivery is queued.

**Acceptance Scenarios**:

1. **Given** an alert rule exists for `entra.admin_roles.high` with minimum severity = high, **When** a new high-privilege finding is created, **Then** a delivery is queued for each enabled destination on that rule.
2. **Given** an alert rule exists with minimum severity = critical, **When** a finding with severity = high (not critical) is created, **Then** no delivery is queued.
3. **Given** the same finding persists across scans (no change), **When** the alert evaluator runs, **Then** cooldown/dedupe logic prevents duplicate notifications.

---

### User Story 4 — Entra permissions appear in permission posture (Priority: P2)

As a workspace operator, I want the new Entra-specific Graph permissions (`RoleManagement.Read.Directory`) to appear in the permission posture checks, so that the posture score accurately reflects whether a tenant can run admin role scans.

**Why this priority**: Without this, Spec 104's posture score would silently ignore whether the Entra roles feature is permission-ready — the posture would "lie."

**Independent Test**: Verify that after loading the merged registry (Intune + Entra), the required permission `RoleManagement.Read.Directory` appears in the list. Verify posture score computation includes it.

**Acceptance Scenarios**:

1. **Given** `config/entra_permissions.php` defines `RoleManagement.Read.Directory` as required, **When** the registry loader runs, **Then** the merged required-permissions list includes the Entra entry alongside all Intune entries.
2. **Given** a tenant does not have `RoleManagement.Read.Directory` granted, **When** the permission posture generator runs, **Then** a posture finding is created for that missing permission, and the posture score reflects the gap.
3. **Given** a tenant has all Intune + Entra permissions granted, **When** posture runs, **Then** the posture score is 100.

---

### User Story 5 — View admin roles summary on tenant dashboard (Priority: P3)

As a workspace operator, I want to see a summary card on the tenant dashboard displaying the latest admin roles scan timestamp and high-privilege assignment count, so I can quickly assess a tenant's admin roles posture at a glance.

**Why this priority**: UI visibility provides daily governance context. Lower priority because the backend data and findings are more critical.

**Independent Test**: Navigate to a tenant dashboard after a scan has completed; confirm the card shows the correct timestamp and high-privilege count drawn from the latest stored report.

**Acceptance Scenarios**:

1. **Given** a tenant has a stored report with `report_type=entra.admin_roles`, **When** I view the tenant dashboard, **Then** the "Admin Roles" card displays the last scan timestamp and high-privilege assignment count from the latest report.
2. **Given** a tenant has no admin roles report yet, **When** I view the tenant dashboard, **Then** the card shows "No scan performed" with a "Scan now" CTA (gated by `ENTRA_ROLES_MANAGE`).
3. **Given** I click "Scan now" and have `ENTRA_ROLES_MANAGE`, **When** the button is pressed, **Then** a new `entra.admin_roles.scan` OperationRun is dispatched and the UI shows a pending state.
4. **Given** I have `ENTRA_ROLES_VIEW` but not `ENTRA_ROLES_MANAGE`, **When** I view the card, **Then** the "Scan now" button is not visible and the scan action endpoint returns 403.

---

### User Story 6 — View full admin roles report (Priority: P3)

As a compliance officer, I want to view the full admin roles report (table of all high-privilege assignments, totals, role definitions) for a specific tenant, so I can audit detailed role assignment state.

**Why this priority**: Detailed report viewing supports audit workflows but is not required for core posture detection.

**Independent Test**: Navigate to the stored reports viewer filtered by `entra.admin_roles` for a tenant; confirm the report displays a table of high-privilege assignments with principal names, roles, and scope.

**Acceptance Scenarios**:

1. **Given** a tenant has a stored report with `report_type=entra.admin_roles`, **When** I view the report, **Then** I see a summary (totals) and a table of high-privilege role assignments with principal display name, principal type, role display name, and directory scope.
2. **Given** multiple reports exist over time, **When** I list reports, **Then** they are ordered by creation date descending and I can navigate between them.

---

### Edge Cases

- **Graph API returns partial data**: If `roleDefinitions` succeeds but `roleAssignments` fails (or vice versa), the scan MUST fail the OperationRun (no partial reports). All-or-nothing per scan.
- **Service principal assignments**: Some high-privilege role assignments go to service principals, not users. The finding generator MUST handle all principal types (`user`, `group`, `servicePrincipal`) and include the principal type in evidence.
- **Group-assigned roles**: When a group holds a high-privilege role, it counts as one principal (no member expansion in v1). The finding is created for the group itself. The aggregate "Too many Global Admins" threshold counts assignment records, not expanded members. Evidence records `principal.type=group` so operators can identify group-based assignments for manual review.
- **Scoped role assignments**: Some role assignments are scoped to administrative units (`directory_scope_id != "/"`). The high-privilege finding still applies — scope is recorded in evidence but does not downgrade severity in v1.
- **Role definitions without `template_id`**: Custom role definitions may not have a `template_id`. The `HighPrivilegeRoleCatalog` falls back to `display_name` matching for classification. If neither matches, the role is not flagged as high-privilege.
- **Tenant with zero role assignments**: Possible for app-only tenants. The scan produces a valid report with `assignments_total=0` and no findings.
- **Concurrent scans for same tenant**: OperationRun active-run uniqueness constraint (per workspace_id, tenant_id, run_type) prevents duplicate concurrent scans.
- **Threshold for "Too many Global Admins"**: v1 hardcodes threshold at 5. A `TODO` comment marks this for future settings-based configuration.

## Requirements *(mandatory)*

**Constitution alignment (required):** This feature introduces Microsoft Graph calls and scheduled/queued work.
- **Contract registry**: New Graph endpoints registered in `config/graph_contracts.php`:
  - `GET /roleManagement/directory/roleDefinitions`
  - `GET /roleManagement/directory/roleAssignments?$expand=principal`
- **Safety gates**: Scan is a read-only operation against Entra (no writes). No confirmation required for scan dispatch — it's non-destructive.
- **Tenant isolation**: Stored reports, findings, and OperationRuns are always scoped to a specific tenant via `tenant_id` FK. Cross-tenant data access is impossible at the query level.
- **Run observability**: Each scan execution is tracked as an `OperationRun` with `type=entra.admin_roles.scan`, recording status, outcome, started_at, completed_at, and error details.
- **Tests**: Unit tests for report generation, finding generation, high-privilege classification, auto-resolve, alert event production, and registry merge.

**Constitution alignment (RBAC-UX):**
- **Authorization planes**: Tenant-context (`/admin/t/{tenant}/...`) for viewing reports and findings; workspace-context for alerts.
- **404 vs 403**: Non-member / not entitled to workspace or tenant scope → 404 (deny-as-not-found). Member missing `ENTRA_ROLES_VIEW` → 403 for Admin Roles card/report. Member missing `ENTRA_ROLES_MANAGE` → 403 for scan. Findings list uses existing `FINDINGS_VIEW` (no `ENTRA_ROLES_VIEW` required).
- **Server-side enforcement**: New policy for on-demand scan action; existing `FindingPolicy` covers findings (gated by `FINDINGS_VIEW`, not `ENTRA_ROLES_VIEW`).
- **Capability registry**: New constants `ENTRA_ROLES_VIEW` and `ENTRA_ROLES_MANAGE` added to `App\Support\Auth\Capabilities`.
- **Global search**: No new globally searchable resources.
- **Destructive actions**: None. Scans are read-only; findings are system-generated.
- **Authorization tests**: Positive test (user with `ENTRA_ROLES_VIEW` can see report; user with `ENTRA_ROLES_MANAGE` can trigger scan) and negative test (user without capability gets 403; non-member gets 404).

**Constitution alignment (OPS-EX-AUTH-001):** Not applicable — no OIDC/SAML login flows involved.

**Constitution alignment (BADGE-001):**
- `finding_type=entra_admin_roles` — new finding type value added to centralized `FindingTypeBadge` mapper with appropriate icon and color.
- `severity` values: Uses existing `low`, `medium`, `high`, `critical` — no new severity values.
- `status` values: Uses existing `new`, `acknowledged`, `resolved` — no new status values.
- Tests cover rendering of `entra_admin_roles` type badge.

**Constitution alignment (Filament Action Surfaces):** This spec adds a small tenant card widget ("Admin Roles") with a "Scan now" header action and a "View latest report" link. No new full Resources or RelationManagers.

**Constitution alignment (UX-001 — Layout and Information Architecture):**
- The tenant card widget follows existing card widget conventions (summary stat + CTA).
- Report viewer reuses the existing stored reports viewer layout (existing UX-001-compliant screen).
- **Exemption**: No new Create/Edit pages — scan is a single-action trigger. No form layout applies.

### Functional Requirements

- **FR-001 (Entra Permissions Registry)**: The system MUST provide a `config/entra_permissions.php` registry file following the same schema as `config/intune_permissions.php`, declaring `RoleManagement.Read.Directory` (type: application) as required for the `entra-admin-roles` feature. Optionally: `Directory.Read.All` as a fallback entry.
- **FR-002 (Registry Merge)**: The registry loader (used by TenantPermissionService and posture generator) MUST merge entries from both `config/intune_permissions.php` and `config/entra_permissions.php` into a single required-permissions set. The merge MUST be non-breaking (existing Intune posture flows unchanged).
- **FR-003 (Graph Contract Registration)**: New Graph endpoints MUST be registered in `config/graph_contracts.php`:
  - `GET /roleManagement/directory/roleDefinitions`
  - `GET /roleManagement/directory/roleAssignments?$expand=principal`
- **FR-004 (Admin Roles Report Service)**: The system MUST provide `EntraAdminRolesReportService` that:
  - Fetches role definitions via Graph
  - Fetches role assignments (with `$expand=principal`) via Graph
  - Builds a structured payload (see Payload Schema below)
  - Computes a deterministic content fingerprint from sorted `(role_definition_template_id or id, principal_id, scope_id)` tuples
  - Writes a `stored_report` with `report_type=entra.admin_roles`, fingerprint, and `previous_fingerprint`
  - Deduplicates on identical fingerprint (no new report if data unchanged)
- **FR-005 (Payload Schema)**: Stored report payload MUST contain: `provider_key`, `domain`, `measured_at`, `role_definitions` array, `role_assignments` array (with principal expansion), `totals` (roles_total, assignments_total, high_privilege_assignments), and `high_privilege` (definition_ids, count).
- **FR-006 (High-Privilege Role Catalog)**: The system MUST provide a `HighPrivilegeRoleCatalog` class that classifies roles as high-privilege using a deterministic list. Classification MUST prefer `template_id` matching, falling back to `display_name` matching. v1 set: Global Administrator, Privileged Role Administrator, Security Administrator, Conditional Access Administrator, Exchange Administrator, Authentication Administrator.
- **FR-007 (OperationRun Type)**: A new `OperationRunType` case `EntraAdminRolesScan` with value `entra.admin_roles.scan` MUST be added. Active-run uniqueness MUST be enforced per `(workspace_id, tenant_id, run_type)`.
- **FR-008 (Scan Job)**: The system MUST provide a `ScanEntraAdminRolesJob` that:
  - Creates/updates an `OperationRun`
  - Calls `EntraAdminRolesReportService` to produce the report
  - Calls `EntraAdminRolesFindingGenerator` to produce findings
  - Records success/failure outcome on the OperationRun
- **FR-009 (Finding Generator)**: The system MUST provide `EntraAdminRolesFindingGenerator` that creates findings with:
  - `finding_type = entra_admin_roles`
  - `source = entra.admin_roles`
  - For each high-privilege role assignment: one finding per (principal, role) pair
  - Severity: `critical` for Global Administrator, `high` for all other high-privilege roles
  - Fingerprint: `entra_admin_role:{tenant_id}:{role_template_or_id}:{principal_id}:{scope_id}`
  - Evidence: role display name, principal display name, principal type, scope, is_built_in
- **FR-010 (Aggregate Finding — Too Many Global Admins)**: When the number of principals assigned to Global Administrator exceeds a threshold (v1 hardcoded: 5), the system MUST create an aggregate finding with:
  - Fingerprint: `entra_admin_role_ga_count:{tenant_id}`
  - Severity: `high`
  - Evidence: count, threshold, list of principal display names
- **FR-011 (Idempotent Upsert)**: Finding generation MUST use fingerprint-based upsert to prevent duplicates. On repeated scans: `times_seen` and `last_seen_at` are updated. On data change: findings for removed assignments are auto-resolved; new assignments create new findings.
- **FR-012 (Auto-Resolve)**: When a high-privilege role assignment no longer appears in the latest scan, the corresponding finding MUST be auto-resolved (`status=resolved`, `resolved_at` set, `resolved_reason=role_assignment_removed`). Auto-resolve applies to `new` and `acknowledged` findings alike (acknowledged metadata preserved).
- **FR-013 (Re-Open)**: When a resolved finding's fingerprint matches a current assignment (re-assigned role), the finding MUST be re-opened (`status=new`, `resolved_at`/`resolved_reason` cleared, evidence updated).
- **FR-014 (Alert Event)**: When high-privilege findings are created or re-opened, the system MUST produce events with `event_type=entra.admin_roles.high` for the alert dispatch pipeline. Events include tenant_id, finding fingerprint, severity. Resolved or unchanged findings do NOT produce alert events.
- **FR-015 (Alert Rule Event Type)**: The `entra.admin_roles.high` event type MUST be available in the alert rule event type dropdown. Constant added to `AlertRule` model.
- **FR-016 (Scan Scheduling)**: The scan MUST support two modes:
  - Scheduled: daily via workspace dispatcher (alongside existing scheduled scans)
  - On-demand: "Scan now" button on tenant dashboard (gated by `ENTRA_ROLES_MANAGE`)
- **FR-017 (RBAC Capabilities)**: New capabilities `ENTRA_ROLES_VIEW` and `ENTRA_ROLES_MANAGE` MUST be added to the canonical `Capabilities` registry and mapped to roles (Readonly/Operator → view; Manager/Owner → manage).
- **FR-018 (Skip Unconnected Tenants)**: Scan MUST gracefully skip tenants without a configured provider connection. No findings, reports, or error OperationRuns for unconnected tenants.
- **FR-019 (Data Minimization)**: Stored report payload MUST contain only governance-relevant data: IDs, display names, principal types, and scopes. No tokens, secrets, or excessive PII.
- **FR-020 (Tenant Card Widget)**: A tenant dashboard card MUST display: last scan timestamp, high-privilege assignment count, "Scan now" CTA (gated by capability), and "View latest report" link.
- **FR-021 (Report Viewer)**: The existing stored reports viewer MUST support filtering by `report_type=entra.admin_roles` and display the report's summary + high-privilege assignments table.

## Payload Schema (stored_reports.payload)

```json
{
  "provider_key": "microsoft",
  "domain": "entra",
  "measured_at": "2026-02-21T10:00:00Z",
  "role_definitions": [
    {
      "id": "...",
      "template_id": "...",
      "display_name": "Global Administrator",
      "is_built_in": true
    }
  ],
  "role_assignments": [
    {
      "id": "...",
      "role_definition_id": "...",
      "directory_scope_id": "/",
      "principal": {
        "id": "...",
        "type": "user|group|servicePrincipal",
        "display_name": "..."
      }
    }
  ],
  "totals": {
    "roles_total": 100,
    "assignments_total": 42,
    "high_privilege_assignments": 7
  },
  "high_privilege": {
    "definition_ids": ["..."],
    "assignments": 7
  }
}
```

## UI Action Matrix *(mandatory when Filament is changed)*

| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Tenant Card "Admin Roles" (Widget) | Tenant Dashboard | "Scan now" (gated by `ENTRA_ROLES_MANAGE`, non-destructive → no confirmation) | N/A | N/A | N/A | "No scan performed — Scan now" (gated) | N/A | N/A | OperationRun tracks scan | Widget, not a full Resource |
| Report Viewer (existing) | Stored Reports > View (filtered `entra.admin_roles`) | N/A (uses existing report viewer) | Table of high-privilege assignments | N/A | N/A | "No reports yet" | N/A | N/A | No (read-only) | Reuses existing viewer, no new page |

### Key Entities *(include if feature involves data)*

- **StoredReport (extended)**: Existing model gains a new `report_type` constant `entra.admin_roles`. Payload schema documented above. Fingerprint-based dedupe and `previous_fingerprint` for drift detection.
- **Finding (extended)**: Existing model gains `finding_type=entra_admin_roles` and `source=entra.admin_roles`. Each finding represents a high-privilege role assignment (per principal per role) or an aggregate condition (too many Global Admins). Uses existing severity/status/fingerprint infrastructure.
- **AlertRule (extended)**: Existing model gains `EVENT_ENTRA_ADMIN_ROLES_HIGH` constant for `event_type=entra.admin_roles.high`. No schema change.
- **HighPrivilegeRoleCatalog (new)**: A value object / catalog class that deterministically classifies Entra role definitions as high-privilege. Uses `template_id` (preferred) or `display_name` (fallback). Testable and extensible.
- **EntraAdminRolesReportService (new)**: Service class responsible for fetching Graph data, building the payload, computing fingerprint, and writing the stored report.
- **EntraAdminRolesFindingGenerator (new)**: Service class responsible for generating/upserting findings from report data.
- **ScanEntraAdminRolesJob (new)**: Queued job orchestrating the OperationRun lifecycle, report service, and finding generator.
- **OperationRunType (extended)**: Enum gains `EntraAdminRolesScan = 'entra.admin_roles.scan'`.
- **Capabilities (extended)**: `ENTRA_ROLES_VIEW = 'entra_roles.view'` and `ENTRA_ROLES_MANAGE = 'entra_roles.manage'`.

## Non-Goals

- No PIM/Eligible Assignments (`RoleEligibilitySchedules` etc.) in v1.
- No automatic remediation (only evidence + findings + alerts).
- No new `EvidenceItems` model (comes later; we use `stored_reports`).
- No tenancy/RBAC refactor.
- No custom role definitions management.

## High-Privilege Role Classification (v1)

The `HighPrivilegeRoleCatalog` classifies roles using `template_id` (preferred) with fallback to `display_name`:

| Role Display Name | Template ID | Severity when assigned |
|---|---|---|
| Global Administrator | `62e90394-69f5-4237-9190-012177145e10` | critical |
| Privileged Role Administrator | `e8611ab8-c189-46e8-94e1-60213ab1f814` | high |
| Security Administrator | `194ae4cb-b126-40b2-bd5b-6091b380977d` | high |
| Conditional Access Administrator | `b1be1c3e-b65d-4f19-8427-f6fa0d97feb9` | high |
| Exchange Administrator | `29232cdf-9323-42fd-ade2-1d097af3e4de` | high |
| Authentication Administrator | `c4e39bd9-1100-46d3-8c65-fb160da0071f` | high |

This set can be extended later via workspace settings.

## Graph API Details

### Required Permissions (Application)

- **Least privilege (v1)**: `RoleManagement.Read.Directory`
- **Fallback (if $expand=principal requires it)**: `Directory.Read.All`

### Endpoints

- `GET /roleManagement/directory/roleDefinitions` — all role definitions for the directory
- `GET /roleManagement/directory/roleAssignments?$expand=principal` — all active role assignments with principal details

## Clarifications

### Session 2026-02-21

- Q: Does this spec introduce new database tables? → A: No. Reuses `stored_reports` (Spec 104) and `findings` (existing).
- Q: Does this spec require database migrations? → A: Yes — one migration adds `fingerprint` and `previous_fingerprint` (plus indexes) to `stored_reports` for report dedupe and chaining, and `OperationRunType` gains the `entra.admin_roles.scan` case.
- Q: Is `$expand=principal` reliable for all principal types? → A: Yes — Graph returns principal details for users, groups, and service principals. If expansion fails for a specific principal, the assignment is still recorded with `principal.display_name = null` (evidence captures this gap).
- Q: Why two capabilities (VIEW/MANAGE) instead of reusing existing findings capabilities? → A: The scan action is domain-specific (Entra roles) and should be independently gatable. Viewing the resulting findings still uses existing findings capabilities, but triggering the scan and viewing the admin roles report card uses the new capabilities.
- Q: How is daily scheduling implemented? → A: Same pattern as existing scheduled scans — workspace dispatcher iterates tenants with active connections and dispatches `ScanEntraAdminRolesJob` per tenant.
- Q: When a group is assigned a high-privilege role, does the finding generator expand group members or treat the group as one principal? → A: Group = 1 principal. No member expansion in v1. Findings and the aggregate GA threshold count reflect Graph roleAssignment records directly (1 assignment = 1 finding). Member expansion would require additional Graph calls + `GroupMember.Read.All` permission and is deferred to a future spec.
- Q: Should `ENTRA_ROLES_VIEW` gate `entra_admin_roles` findings in the Findings list too, or only the Admin Roles card + report viewer? → A: Card + report only. `ENTRA_ROLES_VIEW` gates the Admin Roles dashboard card and the stored-report viewer. Findings of type `entra_admin_roles` in the Findings list remain gated by the existing `FINDINGS_VIEW` capability (no dual-gate).

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001 (Evidence completeness)**: For any tenant with a configured provider connection and `RoleManagement.Read.Directory` permission, 100% of active directory role assignments are captured in the stored report.
- **SC-002 (Finding accuracy)**: The number of open `entra_admin_roles` findings for a tenant exactly matches the number of high-privilege role assignments in the latest scan (no duplicates, no omissions).
- **SC-003 (Auto-resolve latency)**: When a high-privilege role assignment is removed, the corresponding finding is resolved within the next scan cycle.
- **SC-004 (Alert delivery)**: When a new high-privilege finding is created and an active alert rule matches, a delivery is queued within 2 minutes.
- **SC-005 (Posture integration)**: After deploying this spec, the permission posture score for tenants missing `RoleManagement.Read.Directory` reflects the gap (score decreases proportionally).
- **SC-006 (Temporal audit)**: An operator can query stored reports to see a tenant's admin role posture at any point within the configured retention window (default 90 days).
- **SC-007 (No duplicates)**: Repeated scans with identical data produce no duplicate findings or stored reports.
- **SC-008 (Scan performance)**: A single tenant scan completes within 30 seconds for tenants with up to 200 role assignments.
