# Halo agent role model

How agent and contact roles are modeled in a GRCITSM tenant: two distinct role catalogs keyed differently, the role categories that govern what each role does, and the per-tenant SQL to fetch the GUID/int_id values that resolve role assignments. Role names, categories, and rules are the standard GRCITSM model and are portable; the identifier VALUES (GUIDs, int_ids) differ per tenant and must always be fetched live, never hardcoded.

## Dual role model

GRCITSM keeps two separate role catalogs. They share many names but are keyed differently, and the key is the only reliable disambiguator.

| Catalog | Keyed by | Holds | Primary key columns | Surfaced via |
|---|---|---|---|---|
| `NHD_Roles` | GUID | Agent roles — what an agent IS or can DO | `Id` (varchar36 GUID), `Name`, `IdInt` | `role_id` GUIDs on team/section memberships resolve here |
| `UserRoles` | integer | Customer-contact / POC roles | `URid` (int), `URname`, `URGUID` | `system_pocs` MCP tool |

Observed `UserRoles`: System Owner = 8, ISSO = 9, Incident POC = 10.

**Names collide across the two catalogs.** The same label (for example `System Owner` or `ISSO`) exists in both. The GUID (`NHD_Roles.Id`) versus the integer (`UserRoles.URid`) is what disambiguates them. Matching on name alone silently mixes rows from the two models or misses rows entirely — always resolve through the key, never the name.

## Role categories (NHD_Roles) and their rules

| Category | Function / rule | Role names |
|---|---|---|
| **Capability** | Sets what an agent can DO in Halo (permission tier). | `Capability - Basic User`, `Capability - Power User`, `Capability - Administrator` |
| **Agent Assignment** | EXACTLY ONE user holds each; designates the single agent that tickets of that type get auto-assigned to. | `Agent Assignment - Alerts/Incidents`, `Agent Assignment - Change Requests`, `Agent Assignment - Data Requests`, `Agent Assignment - Deviations`, `Agent Assignment - Issue Tickets`, `Agent Assignment - User Access Requests`, `Agent Assignment - Validations` |
| **Approver** | CAB approver for that workflow (multiple agents may hold). | `Approver - After Action Reports`, `Approver - Change Requests`, `Approver - Deviations`, `Approver - User Access Requests` |
| **Agent Membership** | Team / section membership. | `Agent Membership - All Teams`, `Agent Membership - GRC Team`, `Agent Membership - ConMon Team` |
| **POC / reporting-only** | Confer NO permissions; reporting assignments only. Also exist in `UserRoles` as POC roles, integer-keyed. | `ISSO`, `System Owner` |
| **Notifications** | Notification routing only. | `Notifications`, `Notifications - FedRAMP Security Inbox` |
| **Platform / integration** | Not personnel roles. | `MCP Access`, `Testing`, `N8N`, `N8N Test Roles`, `N8N Automation Agent - UAT` |

### Key rules

1. **`Agent Assignment - *` roles are single-holder.** Exactly one user holds each — enforce uniqueness when assigning. Granting it to a second agent should displace the first, not add a second holder.
2. **`ISSO` and `System Owner` are reporting-only.** They grant no Halo permissions. An agent needs a `Capability` role to actually do anything; a POC/reporting role alone confers nothing operational.
3. **`Approver - *` roles map to the four CABs:** After Action Report (AAR), Change Request, Deviation, and User Access Request (UAR).
4. **`Agent Membership` gates `assign_agent`.** Assigning a ticket to an agent who is NOT a member of the ticket's team silently reverts the owner to the team default — `assign_agent` returns success with no error, so the failure is invisible. Add the agent to the ticket's team (grant the relevant `Agent Membership - *` role) BEFORE assigning, and verify the owner via `get_ticket` after. Write-tool mechanics: `halo-platform/write-semantics.md` § 12.

## Canonical refresh SQL (tenant-specific)

The role NAMES, categories, and rules above are the standard model and travel between tenants unchanged. The GUID and int_id VALUES that resolve assignments differ per tenant/instance, so the catalog must be fetched per instance rather than hardcoded. Run these against the target tenant to refresh the values.

All role names from both models, labeled:

```sql
SELECT 'Agent Role (NHD_Roles)' AS role_model, Name AS role_name, Id AS role_guid, IdInt AS role_int_id
FROM NHD_Roles
UNION ALL
SELECT 'Customer/POC Role (UserRoles)' AS role_model, URname AS role_name, URGUID AS role_guid, CAST(URid AS varchar(36)) AS role_int_id
FROM UserRoles;
```

Agent roles only:

```sql
SELECT Name AS role_name, Id AS role_guid, IdInt AS role_int_id FROM NHD_Roles;
```

POC roles only:

```sql
SELECT URname AS role_name, URid AS role_id FROM UserRoles;
```

## Tenant specificity

- **Portable** (this doc): role names, the two-catalog model, the categories, and the rules — the standard GRCITSM agent role model.
- **Per-tenant** (never in this doc): `NHD_Roles.Id` GUIDs, `NHD_Roles.IdInt`, and `UserRoles.URid` / `URGUID` values. Always fetch them live with the SQL above. Do not embed any specific tenant's GUID or int_id values here.
