Use Case 038: Agent Cost Governance RBAC
Overview
| Property |
Value |
| Use Case ID |
UC-038 |
| Use Case Name |
Agent Cost Governance RBAC |
| Module |
Agent Identity — Cost Governance |
| Priority |
High |
| Status |
✅ Implemented |
| Version |
1.0 |
| Last Updated |
June 15, 2026 |
Implementation status (agent-identity release, June 2026). Implemented: 14 seeded platform capabilities (7 cost-governance + 7 agent/gateway) enforced by pure capability-claim policies (CapabilityAuthorizationPolicies) with no Administrator-role bypass — the only path to a feature is a logged RoleCapabilityMapping, bootstrapped once for the Administrator role by the first-startup seeder.
Description
This use case describes capability-based RBAC for the entire agent-identity, LLM-gateway, and cost-governance surface area introduced in the agent-identity release. AM exposes a set of granular capabilities; administrators compose them into roles using the existing Role / Capability / RoleCapabilityMapping model (the same model documented in UC-006). AM deliberately does not prescribe specific roles such as "FinanceAdmin" or "AiPlatformAdmin" — implementers compose roles to fit their own segregation-of-duties model. As an MVP practical default, anyone holding an existing AM admin role inherits all of the new capabilities; finer-grained roles can be layered on after the fact without code changes.
Every governance-config change — capability edit, provider-account credential rotation, model-deployment edit, and plan-extension approval — is recorded with before/after values via the respective *AuditEntry rows (BudgetCapAuditEntry, ProviderAccountAuditEntry, ModelDeploymentAuditEntry) folded into the existing AuditLog (see UC-010), on the same actor-chain pipeline as identity events. The SDK master design is riptide-sdk/docs/plans/AGENT-IDENTITY-ARCHITECTURE.md; the Application Manager plan is docs/internal/agent-identity-plan.md (Section 6). These are planned features and not yet built.
Actors
| Actor |
Description |
Role |
| AM Administrator |
Holds an existing AM admin role; seeds/composes roles, inherits all new capabilities at MVP |
Primary |
| Role Composer |
Admin who builds finer-grained roles from the new capabilities (segregation of duties) |
Primary |
| Governance Operator |
User assigned a composed role (e.g., cap editor, provider-account manager) acting on a gated surface |
Primary |
| Initial Migration |
EF migration that seeds the new Capability rows |
Supporting |
| Authorization Pipeline |
Capability check on Web/API actions |
Supporting |
| AuditLog |
Existing audit trail receiving *AuditEntry before/after records |
Supporting |
Preconditions
- The existing
Role / Capability / RoleCapabilityMapping model is in place (UC-006).
- The agent-identity / gateway / cost-governance controllers and endpoints exist and declare capability requirements.
- The initial cost-governance EF migration runs on startup, seeding the new
Capability rows.
- The existing
AuditLog actor-chain pipeline is operational (UC-010).
- At least one AM admin role exists to receive the MVP default capability grant.
Postconditions
Success Postconditions
- All new agent/gateway/cost capabilities are registered as
Capability rows after migration.
- Admins can compose arbitrary roles from these capabilities via
RoleCapabilityMapping.
- A gated API/Web action permits the call only when the caller holds the required capability.
- Existing AM admin roles inherit all new capabilities (MVP default), preserving day-one operability.
- Every governance-config change writes a before/after
*AuditEntry into AuditLog with the acting principal.
Failure Postconditions
- A caller lacking a required capability is denied (
403); no state change occurs.
- A migration that fails to seed capabilities leaves the prior state intact (transactional), and the app surfaces the error rather than silently running un-gated.
- A governance change that fails validation is not persisted and writes no
*AuditEntry.
Primary Flow Sequence
sequenceDiagram
actor Op as Governance Operator
participant Web as Web/API action
participant Authz as Authorization (capability check)
participant MR as MediatR handler
participant Repo as Cost-governance repo
participant Audit as AuditLog (*AuditEntry)
Op->>Web: Request gated action (e.g., edit cap)
Web->>Authz: Require capability (e.g., cost.cap.edit)
alt capability present
Authz-->>Web: Authorized
Web->>MR: Send command (UpdateBudgetCap)
MR->>Repo: Read current values (before)
MR->>Repo: Persist new values (after)
MR->>Audit: Write BudgetCapAuditEntry (before/after, actor chain)
MR-->>Web: Success
Web-->>Op: 200 / redirect
else capability missing
Authz-->>Web: Denied
Web-->>Op: 403 (no state change)
end
UC-038a: Seed New Capabilities in Initial Migration
Triggers
- The cost-governance EF migration runs on application startup.
Basic Flow
- The initial cost-governance migration includes seed data inserting the new
Capability rows (see Data Requirements).
- On startup, migrations auto-run; the new capabilities become available for mapping.
- The seed is idempotent — re-running migrations does not duplicate capability rows.
- Each capability carries a stable name (e.g.,
cost.cap.edit) and a human-readable description used in the role-composition UI.
Alternative Flows
- A1: Capability already present — seed skips existing rows (idempotent), no duplicates.
- A2: Migration failure — the transaction rolls back; the app reports the failure rather than starting with partially seeded capabilities.
UC-038b: Compose a Custom Role from Capabilities (Segregation of Duties)
Triggers
- An admin builds a new role tailored to their org's segregation-of-duties model.
Basic Flow
- The admin opens role management (existing Roles admin UI — Bootstrap 5 + Tabler MVC).
- They create a role (e.g., "Cap Approver") and select capabilities to attach.
- They attach only the intended capabilities (e.g.,
cost.cap.view + cost.cap.approve but not cost.cap.edit) via RoleCapabilityMapping.
- They assign the role to users.
- AM enforces exactly those capabilities for that role's members on the gated surfaces.
Alternative Flows
- A1: Separation of edit vs. approve — an org composes one role with
cost.cap.edit and a distinct role with cost.cap.approve, so the editor cannot self-approve.
- A2: Read-only auditor role — composed from the
*.view/*.read capabilities only.
- A3: No prescribed roles — AM ships none of "FinanceAdmin"/"AiPlatformAdmin"; the admin names and shapes roles freely.
UC-038c: Enforce a Capability at an API/Web Action
Triggers
- A user invokes a gated action (e.g., editing a budget cap, rotating a provider-account credential, editing a deployment).
Basic Flow
- The user requests the action (Web form post or API call).
- The authorization pipeline checks the caller's capabilities for the one the action requires (e.g.,
cost.cap.edit for a cap edit).
- If present, the request proceeds to the MediatR handler.
- The handler executes and writes the relevant
*AuditEntry (UC-038e).
- The user receives success.
Alternative Flows
- A1: Capability missing —
403; no state change; the denial may itself be logged.
- A2: Capability present but validation fails — the command's FluentValidation rejects the input; no persistence, no audit entry.
- A3: API vs. Web — the same capability gates both the
X-Api-Key-authenticated API action and the cookie-authenticated Web action.
sequenceDiagram
actor User
participant Action as Web/API action
participant Authz as Capability check
participant Handler as MediatR handler
participant Audit as AuditLog
User->>Action: POST rotate provider credential
Action->>Authz: Require llm.providers.write
alt has capability
Authz-->>Action: Authorized
Action->>Handler: RotateProviderAccountCredential
Handler->>Audit: ProviderAccountAuditEntry (before/after)
Handler-->>Action: Success
Action-->>User: 200
else lacks capability
Authz-->>Action: Denied
Action-->>User: 403
end
UC-038d: MVP Default — Admin Role Inherits All New Capabilities
Triggers
- The agent-identity release is deployed onto an installation with existing AM admin roles.
Basic Flow
- The migration seeds the new capabilities and maps them to the existing AM admin role(s) by default.
- Existing admins immediately hold all agent/gateway/cost capabilities — no manual wiring needed for day-one operability.
- Implementers may later create finer-grained roles and reassign users off the broad admin role.
- Removing a capability from the admin role (or moving users to narrower roles) is a pure
RoleCapabilityMapping change — no code change.
Alternative Flows
- A1: Implementer tightens the default — they remove specific capabilities from the admin role and compose narrower roles; enforcement follows immediately.
- A2: Multiple admin roles — each existing admin role receives the default grant unless the implementer scopes the seed otherwise.
UC-038e: Audit a Governance Config Change (Before/After, Actor Chain)
Triggers
- A governance-config change occurs: cap edit, provider-account credential rotation, deployment edit, or plan-extension approval.
Basic Flow
- The handler reads the current entity state (the "before" snapshot).
- It applies and persists the change (the "after" state).
- It writes the matching
*AuditEntry — BudgetCapAuditEntry, ProviderAccountAuditEntry, or ModelDeploymentAuditEntry — capturing before/after values, the acting principal, and the timestamp.
- The entry folds into the existing
AuditLog on the same actor-chain pipeline as identity events (so delegated actions record the full chain).
- Plan-extension approvals (gated by
cost.plan.extension.approve) are recorded the same way.
Alternative Flows
- A1: Credential rotation — the
ProviderAccountAuditEntry records the rotation event and metadata, never the secret value itself (write-only credential field).
- A2: No-op change — if before == after, the handler may still record the attempt per audit policy, but no functional state changes.
- A3: Failed change — validation/permission failure writes no
*AuditEntry (nothing was persisted); the denial may be logged separately.
API Endpoints
These are existing cost-governance endpoints; this use case defines the capability each requires. Auth column shows the gating capability (all are X-Api-Key-authenticated at the transport layer).
| Method |
Path |
Auth (capability) |
Purpose |
| PUT |
/api/budgets/caps/{id} |
cost.cap.edit |
Edit a budget cap |
| POST |
/api/budgets/approvals/{reservationId}/approve |
cost.cap.approve |
Approve an ApprovalRequired exhaustion |
| POST |
/api/agents/{id}/plans/{planId}/extend |
cost.plan.extension.approve |
Approve a plan budget extension |
| POST |
/api/providers/accounts/{id}/rotate |
llm.providers.write |
Rotate a provider-account credential |
| PUT |
/api/deployments/{id} |
llm.deployments.write |
Edit a model deployment |
| GET |
/api/insights/overview |
cost.insights.view.tenant/.agency |
View insights dashboard data |
| POST |
/api/insights/billing-report |
cost.report.generate |
Generate billing report |
// PUT /api/budgets/caps/{id} (requires cost.cap.edit)
// Request
{
"limitAmount": 5000.00,
"currency": "USD",
"enforcementMode": "HardStop",
"midStreamMode": "Strict",
"alertThresholds": [0.5, 0.8, 0.95]
}
// Response 200 — change recorded as a BudgetCapAuditEntry (before/after)
{
"id": "b1c2d3e4-f5a6-4b7c-8d90-1a2b3c4d5e6f",
"tier": "Tenant",
"limitAmount": 5000.00,
"previousLimitAmount": 3000.00,
"enforcementMode": "HardStop",
"updatedBy": "admin@acme.gov",
"updatedAt": "2026-06-09T15:20:00Z",
"auditEntryId": "ae-77a1..."
}
// PUT /api/budgets/caps/{id} without cost.cap.edit -> 403 (no state change)
Business Rules
| Rule |
Description |
| BR-1 |
AM ships granular capabilities; admins compose them into roles via Role/Capability/RoleCapabilityMapping |
| BR-2 |
AM does not prescribe specific roles ("FinanceAdmin", "AiPlatformAdmin") — implementers compose to their own SoD model |
| BR-3 |
New capabilities are seeded by the initial cost-governance EF migration (idempotent) |
| BR-4 |
MVP default: existing AM admin roles inherit all new capabilities |
| BR-5 |
Tightening the default is a pure RoleCapabilityMapping change — no code change |
| BR-6 |
A gated action requires its capability on both the API and Web surfaces |
| BR-7 |
Missing capability → 403, no state change |
| BR-8 |
Every cap edit, provider-account credential rotation, deployment edit, and plan-extension approval writes a before/after *AuditEntry into AuditLog |
| BR-9 |
*AuditEntry records the acting principal on the same actor-chain pipeline as identity events |
| BR-10 |
Credential rotation audit never records the secret value (write-only credential field) |
Data Requirements
New capabilities registered by the initial migration:
| Capability |
Permits |
agents.read |
View agent identities |
agents.write |
Create / update agent identities |
agents.disable |
Disable / revoke agent identities |
llm.gateway.read |
View the task-type registry |
llm.gateway.config |
Edit the task-type registry |
llm.providers.write |
Create / rotate / disable ProviderAccounts |
llm.deployments.write |
Edit ModelDeployment rows |
cost.cap.view |
View budget caps |
cost.cap.edit |
Create / edit / delete budget caps |
cost.cap.approve |
Approve ApprovalRequired cap-exhaustion events |
cost.plan.extension.approve |
Approve plan budget extensions when policy requires |
cost.insights.view.tenant |
View the insights dashboard scoped to one tenant |
cost.insights.view.agency |
View the insights dashboard across tenants |
cost.report.generate |
Generate the billing report |
Capability row shape (existing entity; new rows added):
| Field |
Type |
Constraints |
| Id |
Guid |
Primary key |
| Name |
string |
Stable capability key (e.g., cost.cap.edit), unique |
| Description |
string |
Human-readable; shown in role-composition UI |
*AuditEntry (folds into AuditLog) — common shape for BudgetCapAuditEntry / ProviderAccountAuditEntry / ModelDeploymentAuditEntry:
| Field |
Type |
Constraints |
| Id |
Guid |
Primary key |
| EntityId |
Guid |
The cap / provider account / deployment changed |
| ChangeType |
string |
e.g., CapEdit, CredentialRotation, DeploymentEdit, PlanExtensionApproval |
| BeforeValuesJson |
string (JSON) |
Snapshot of prior values (secrets excluded) |
| AfterValuesJson |
string (JSON) |
Snapshot of new values (secrets excluded) |
| ActingPrincipal |
string |
Acting user/agent; full actor chain for delegated calls |
| OccurredAt |
DateTimeOffset |
Timestamp |
Security Considerations
- Authentication: API governance actions authenticate via
X-Api-Key; Web governance actions via the admin cookie scheme. Capability enforcement is layered on top of authentication.
- Authorization / capabilities: every agent/gateway/cost action is gated by a granular capability; least privilege is achievable by composing narrow roles. The "no prescribed roles" stance lets organizations enforce separation of duties (e.g., edit vs. approve) without fighting baked-in role shapes.
- Data protection: credential-rotation audit entries record metadata only, never secret values; the provider-account credential field is write-only. Before/after snapshots exclude secrets.
- Audit: all governance-config changes write before/after
*AuditEntry rows into AuditLog on the actor-chain pipeline, giving a complete forensic record including delegated actor chains. Denials may be logged for tamper visibility.
Testing Scenarios
| ID |
Scenario |
Expected Result |
| T-1 |
Run initial migration |
All 14 new capabilities present as Capability rows |
| T-2 |
Re-run migration |
No duplicate capability rows (idempotent seed) |
| T-3 |
Migration failure mid-seed |
Transaction rolls back; app reports error, not partial state |
| T-4 |
Compose role with cost.cap.view + cost.cap.approve only |
Members can view and approve, but not edit caps |
| T-5 |
Compose separate edit and approve roles |
Editor cannot self-approve (SoD enforced) |
| T-6 |
Edit a cap with cost.cap.edit |
Succeeds; BudgetCapAuditEntry written with before/after |
| T-7 |
Edit a cap without cost.cap.edit |
403; no state change; no audit entry |
| T-8 |
Rotate provider credential with llm.providers.write |
Succeeds; ProviderAccountAuditEntry records rotation, not the secret |
| T-9 |
Rotate provider credential without capability |
403; no rotation |
| T-10 |
Edit deployment with llm.deployments.write |
Succeeds; ModelDeploymentAuditEntry written |
| T-11 |
Approve plan extension with cost.plan.extension.approve |
Extension approved; audited |
| T-12 |
Approve plan extension without capability |
403 |
| T-13 |
MVP default after deploy |
Existing admin role holds all 14 new capabilities |
| T-14 |
Remove a capability from admin role |
Members lose that gated action immediately; no code change |
| T-15 |
Same capability gates API and Web |
Both surfaces deny without the capability |
| T-16 |
Read-only auditor role from *.view/*.read |
Can view, cannot mutate any governance surface |
| T-17 |
Delegated action audit |
*AuditEntry records full actor chain |
| T-18 |
No-op change (before == after) |
No functional change; audit per policy |
| T-19 |
Validation failure on gated action |
Rejected by FluentValidation; no persistence, no audit entry |
| T-20 |
Capability description rendering |
Role-composition UI shows human-readable descriptions |
| T-21 |
Multiple admin roles |
Each receives the default grant unless scoped otherwise |
| T-22 |
cost.insights.view.tenant vs .agency |
Tenant scope limited to one tenant; agency scope cross-tenant |
- UC-006 — Role-Based Access Control (the
Role/Capability/RoleCapabilityMapping model reused here)
- UC-005 — Administrator User Management (admin roles that inherit the MVP default)
- UC-010 — Activity Logging & Audit Trail (the
AuditLog pipeline receiving *AuditEntry rows)
- UC-025 — Agent Identity Registration & Lifecycle (gated by
agents.read/write/disable)
- UC-032 — Budget Cap Lattice Enforcement (gated by
cost.cap.view/edit/approve)
- UC-036 — Cost Insights Dashboard & Billing Report (gated by
cost.insights.view.*, cost.report.generate)
Revision History
| Version |
Date |
Author |
Notes |
| 1.0 |
June 9, 2026 |
Platform Architecture Team |
Initial draft |