Skip to content

Commit a81869a

Browse files
committed
Docs: Add AIAC specification documents
Import the AIAC PRD and per-component specs under aiac/docs/specs/ as the basis for the upstream contribution. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Oleg Blinder <olegb@il.ibm.com>
1 parent d17993f commit a81869a

26 files changed

Lines changed: 5408 additions & 0 deletions

aiac/docs/specs/ARCHITECTURE-SUMMARY.md

Lines changed: 270 additions & 0 deletions
Large diffs are not rendered by default.

aiac/docs/specs/PRD.md

Lines changed: 590 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# Component PRD: AIAC Agent
2+
3+
## Description
4+
5+
A LangGraph-based AI agent service that enforces a natural-language access control policy against the live PDP state. Triggered via the **Event Broker** (NATS JetStream) for all automated triggers, and directly via HTTP for the operator-only `rebuild` command:
6+
7+
- **Event Broker**`aiac.apply.service.{id}` subject (originated by Keycloak SPI `CLIENT_CREATED`)
8+
- **Event Broker**`aiac.apply.role.{id}` subject (originated by Keycloak SPI role created/updated)
9+
- **Event Broker**`aiac.apply.policy.build` subject (originated by RAG Ingest Service post-ingest)
10+
- **Operator/admin call**`POST /apply/policy/rebuild` directly via `kubectl port-forward` (HTTP only — not routed through Event Broker)
11+
12+
The Agent subscribes to the Event Broker as a durable competing consumer (`aiac-agent-consumer` queue group). It acknowledges each message only after successful processing — ensuring at-least-once delivery and automatic replay on pod restart.
13+
14+
The `/apply/*` HTTP endpoints are retained as a debugging escape hatch. The **NATS consumer is a thin adapter layer** that receives events from the Event Broker and calls the same internal `/apply/*` handler functions — there is no duplicated business logic.
15+
16+
The service is structured as a **Controller** (FastAPI routes) that dispatches to the **Service Onboarding Orchestrator** (UC1) or directly to the Policy Update and Role Update sub-agents (UC2, UC3). Each producing sub-agent calls the **shared Policy Rules Builder** (`agent/policy_rules_builder/`) directly, merges the results internally, and returns a single `list[PolicyRule]` to the Controller. The Controller calls `compute_and_apply(merged_rules)` from `aiac.policy.computation` (PCE) once.
17+
18+
| Use Case | Dispatch | Sub-agents | Sub-agent output |
19+
|---|---|---|---|
20+
| Service Onboarding (UC1) | via Orchestrator | Service Provision + Service Policy Builder | `list[PolicyRule]` |
21+
| Policy Update (UC2) | Controller → sub-agent directly | Build or Rebuild (TBD) | `list[PolicyRule]` |
22+
| Role Update (UC3) | Controller → sub-agent directly | Role sub-agent | `list[PolicyRule]` |
23+
24+
Each producing sub-agent calls the **shared Policy Rules Builder** (`agent/policy_rules_builder/`) for each applicable (roles, scope) or (role, scopes) pair, merges the results, and returns a single `list[PolicyRule]` to the Controller. The Controller calls `compute_and_apply(merged_rules)` from `aiac.policy.computation` (PCE) once — no shared apply node exists. The PCE owns all Policy Store ↔ PDP Policy Writer coordination. Neither sub-agents nor the Policy Rules Builder call `aiac.pdp.policy.library` or `aiac.policy.store.library` directly.
25+
26+
All components are **logically separated modules within a single pod and process** — no inter-service network calls between orchestrators and sub-agents.
27+
28+
```mermaid
29+
flowchart TD
30+
NATS["Event Broker\nNATS JetStream\naiac.apply.>"]
31+
NATS_CONSUMER["NATS Consumer\nasyncio background task\nthin adapter"]
32+
TRIGGERS["HTTP Triggers\nPOST /apply/*\n(debugging + rebuild)"]
33+
CTRL["Controller\nroutes.py"]
34+
35+
NATS -->|"durable queue group\naiac-agent-consumer"| NATS_CONSUMER
36+
NATS_CONSUMER -->|"calls internal handler"| CTRL
37+
TRIGGERS --> CTRL
38+
39+
subgraph CO["Service Onboarding"]
40+
ORC1["Orchestrator"]
41+
SA1["Service Provision"]
42+
SA2["Service Policy Builder"]
43+
ORC1 --> SA1
44+
ORC1 --> SA2
45+
end
46+
47+
subgraph PU["Policy Update"]
48+
SA4["Build"]
49+
SA5["Rebuild"]
50+
SA5 -->|"delegates"| SA4
51+
end
52+
53+
subgraph RR["Role Update"]
54+
SA6["Role"]
55+
end
56+
57+
PRB["Policy Rules Builder (shared)\nagent/policy_rules_builder/"]
58+
PCE["Policy Computation Engine\naiac.policy.computation\ncompute_and_apply(rules)"]
59+
60+
CTRL -->|"service/:id"| ORC1
61+
CTRL -->|"build"| SA4
62+
CTRL -->|"rebuild"| SA5
63+
CTRL -->|"role/:id"| SA6
64+
65+
SA2 -->|"calls"| PRB
66+
SA4 -->|"calls"| PRB
67+
SA6 -->|"calls"| PRB
68+
69+
ORC1 -->|"list[PolicyRule]"| CTRL
70+
SA4 -->|"list[PolicyRule]"| CTRL
71+
SA5 -->|"list[PolicyRule]"| CTRL
72+
SA6 -->|"list[PolicyRule]"| CTRL
73+
74+
CTRL -->|"merged rules"| PCE
75+
```
76+
77+
---
78+
79+
## NATS Consumer
80+
81+
A thin adapter started as an **asyncio background task** in the FastAPI `lifespan` handler. It subscribes to the `aiac.apply.>` wildcard on the `aiac-events` NATS JetStream stream using the `aiac-agent-consumer` durable queue group.
82+
83+
### Dispatch table
84+
85+
| Subject pattern | Internal handler |
86+
|---|---|
87+
| `aiac.apply.service.{id}` | Service Onboarding Orchestrator (UC1) |
88+
| `aiac.apply.role.{id}` | Role Update sub-agent (UC3, via Controller) |
89+
| `aiac.apply.policy.build` | Policy Update Build sub-agent (UC2, via Controller) |
90+
91+
### Ack contract
92+
93+
The consumer **awaits** the internal handler before issuing the NATS acknowledgement. On handler success → ack. On handler exception → do not ack; NATS redelivers after `AckWait`. After 5 unacknowledged redeliveries, NATS routes the message to `aiac.apply.dlq`.
94+
95+
Fire-and-forget (`asyncio.create_task`) is explicitly prohibited — acking before handler completion would break at-least-once guarantees.
96+
97+
### Failure isolation
98+
99+
The consumer and the FastAPI HTTP server share the same process. If the Agent pod crashes mid-processing, the in-flight message was never acked and NATS redelivers it to the next pod instance. This prevents the consumer from exhausting retry counts against an unavailable handler (which would occur if they were separate containers).
100+
101+
### Configuration
102+
103+
| Variable | Default | Source |
104+
|---|---|---|
105+
| `NATS_URL` | `nats://aiac-event-broker-service:4222` | ConfigMap (`aiac-pdp-config`) |
106+
107+
---
108+
109+
## Controller
110+
111+
The Controller is a FastAPI routes layer (`controller/routes.py`). Its responsibilities are:
112+
113+
- Parse the trigger type and entity ID from the request path.
114+
- Dispatch to the Service Onboarding Orchestrator (UC1) or directly to the Policy Update / Role Update sub-agents (UC2, UC3).
115+
- Receive the `list[PolicyRule]` returned by the Orchestrator or sub-agent (already merged by the sub-agent).
116+
- Call `compute_and_apply(merged_rules)` from `aiac.policy.computation` (PCE) once.
117+
- Return a bare HTTP status code to the caller; write summary and debug info to the log.
118+
119+
No per-use-case business logic, retry handling, or state assembly lives in the Controller. PRB calls are owned by the producing sub-agents; the Controller's shared step is the single PCE call.
120+
121+
---
122+
123+
## Use Cases
124+
125+
Each use case (and the UC1 Orchestrator) is specified in a dedicated sub-PRD:
126+
127+
| Use Case | Sub-PRD | Trigger(s) | Notes |
128+
|---|---|---|---|
129+
| Service Onboarding | [aiac-agent/uc1-service-onboarding.md](aiac-agent/uc1-service-onboarding.md) | `aiac.apply.service.{id}`, `POST /apply/service/{id}` | Orchestrator sequences: Service Provision → Service Policy Builder (IdP reader + PRB invoker) |
130+
| Policy Update | [aiac-agent/uc2-policy-update.md](aiac-agent/uc2-policy-update.md) | `aiac.apply.policy.build`, `POST /apply/policy/build`, `POST /apply/policy/rebuild` | |
131+
| Role Update | [aiac-agent/uc3-role-update.md](aiac-agent/uc3-role-update.md) | `aiac.apply.role.{id}`, `POST /apply/role/{id}` | |
132+
133+
> **Note:** Each producing sub-agent calls the **shared Policy Rules Builder** directly, merges the results, and returns `list[PolicyRule]` to the Controller. The Controller calls `compute_and_apply(merged_rules)` from `aiac.policy.computation` (PCE) once. Policy rule application is fully specified in [policy-computation-engine.md](policy-computation-engine.md). The Policy Rules Builder is specified in [aiac-agent/policy-rules-builder.md](aiac-agent/policy-rules-builder.md).
134+
135+
### IdP access — library, not service
136+
137+
Every sub-agent (UC1 Provision + Service Policy Builder, UC2 Build + Rebuild, UC3 Role) performs **all** IdP reads and writes through the **idp-library** API — `aiac.idp.configuration.api.Configuration` — and **never** calls the IdP Configuration **service** (`aiac.idp.service.configuration.*`) or its HTTP endpoints directly. The library owns the HTTP transport, retry/backoff, and Keycloak↔model mapping; sub-agents depend only on its typed `Configuration` methods (e.g. `get_service`, `get_roles`, `get_scopes`, `create_service_role`, `create_service_scope`, `set_service_type`). The shared service-type vocabulary is `aiac.idp.configuration.models.ServiceType` (`Agent`/`Tool`) — the same enum used by `Service.type`. See [library-idp.md](library-idp.md).
138+
139+
---
140+
141+
## Endpoints
142+
143+
| Method | Path | Orchestrator | Sub-agent |
144+
|---|---|---|---|
145+
| POST | `/apply/policy/build` | Policy Update | Build |
146+
| POST | `/apply/policy/rebuild` | Policy Update | Rebuild |
147+
| POST | `/apply/role/{role_id}` | Role Update | Role |
148+
| POST | `/apply/service/{service_id}` | Service Onboarding | Provision |
149+
150+
All endpoints return bare HTTP status codes: `200 OK` on success (no response body), and the status codes from the Error Handling table on upstream failure. Success responses carry no body; upstream failures are raised as FastAPI `HTTPException`s, so error responses carry FastAPI's default JSON error body (`{"detail": ...}`) alongside the status code. Summary, applied-rule details, and debug information are written to the service log. Validation failures surface as an error status and log entry; detailed reporting is specified in [policy-rules-builder.md](aiac-agent/policy-rules-builder.md).
151+
152+
---
153+
154+
## Configuration
155+
156+
| Variable | Default | Source |
157+
|---|---|---|
158+
| `NATS_URL` | `nats://aiac-event-broker-service:4222` | ConfigMap (`aiac-pdp-config`) |
159+
| `AIAC_PDP_CONFIG_URL` | `http://aiac-pdp-config-service:7071` | ConfigMap (`aiac-pdp-config`) — used by `aiac.idp.configuration.api` (in-process via PCE) |
160+
| `AIAC_PDP_POLICY_URL` | `http://aiac-pdp-policy-service:7072` | ConfigMap (`aiac-pdp-config`) — used by `aiac.pdp.policy.library` (in-process via PCE) |
161+
| `AIAC_POLICY_STORE_URL` | `http://aiac-policy-store-service:7074` | ConfigMap (`aiac-pdp-config`) — used by `aiac.policy.store.library` (in-process via PCE) |
162+
| `AIAC_CHROMADB_URL` | `http://aiac-rag-service:8000` | ConfigMap (`aiac-pdp-config`) |
163+
| `KEYCLOAK_REALM` || ConfigMap (`aiac-pdp-config`) |
164+
| `LLM_BASE_URL` || ConfigMap |
165+
| `LLM_MODEL` || ConfigMap |
166+
| `LLM_API_KEY` || Kubernetes Secret |
167+
| `AIAC_AC_MODEL` | `RBAC` | ConfigMap (accepted: `RBAC`, `ABAC`, `REBAC`) |
168+
| `CHROMA_N_RESULTS` | `10` | ConfigMap |
169+
| `MAX_CHANGES_PER_RUN` | `50` | ConfigMap |
170+
| `UPSTREAM_MAX_RETRIES` | `3` | ConfigMap |
171+
172+
ChromaDB collections: `aiac-policies` and `aiac-domain-knowledge`.
173+
174+
---
175+
176+
## Error Handling
177+
178+
All upstream calls are retried up to `UPSTREAM_MAX_RETRIES` times with exponential backoff (`tenacity`) before propagating the error. The retry primitive is the project-level shared `run_upstream(fn)` helper (`aiac/shared/upstream.py`), which is transport-agnostic: it re-raises the original exception after the final attempt. Retry is applied at the **transport boundary**, not at the agent call sites — inside the idp-library `Configuration` (its `_request` helper), inside the provision MCP helper (`_mcp_tools_list`), and inside the provision Kubernetes seam (`uc/onboarding/provision/kube.py`). Each caller then maps the re-raised failure to the status below (e.g. an IdP/Kubernetes failure → `502`).
179+
180+
| Upstream | HTTP status on final failure |
181+
|---|---|
182+
| ChromaDB | `503 Service Unavailable` |
183+
| IdP Configuration Service | `502 Bad Gateway` |
184+
| PDP Policy Writer | `502 Bad Gateway` |
185+
| Kubernetes API | `502 Bad Gateway` |
186+
| LLM API | `504 Gateway Timeout` |
187+
188+
Upstream failures propagate as bare HTTP error responses (see table above), raised as FastAPI `HTTPException`s; the status code is authoritative and error responses carry FastAPI's default JSON error body (`{"detail": ...}`). All failure details are logged.
189+
190+
---
191+
192+
## Runtime
193+
194+
- Framework: FastAPI with uvicorn
195+
- Bind: `0.0.0.0:7070`
196+
- State: stateless — changes applied immediately, no pending session required
197+
- Base image: `python:3.12-slim`
198+
199+
---
200+
201+
## File Structure
202+
203+
```
204+
aiac/src/aiac/
205+
├── shared/ ← project-level shared: run_upstream (upstream.py) — transport retry primitive
206+
└── agent/
207+
├── controller/
208+
├── shared/ ← flatten_role (roles.py)
209+
├── uc/
210+
│ ├── onboarding/
211+
│ │ ├── orchestrator.py ← sequences provision → policy_builder, returns list[PolicyRule]
212+
│ │ ├── provision/ ← LLM sub-agent: classify, analyze, write to IdP; kube.py = retrying K8s seam
213+
│ │ └── policy_builder/ ← IdP reader + PRB invoker: read IdP, call PRB, return list[PolicyRule]
214+
│ ├── policy_update/
215+
│ │ ├── build/ ← calls PRB, returns list[PolicyRule]; TBD internals
216+
│ │ └── rebuild/ ← delegates to Build; TBD internals
217+
│ └── role_update/ ← calls PRB with (role, all_scopes), returns list[PolicyRule]
218+
└── policy_rules_builder/ ← shared; called by Service Policy Builder, Build, and Role sub-agent
219+
```
220+
221+
Docker build command (run from repo root):
222+
223+
```bash
224+
docker build -f aiac/src/aiac/agent/controller/Dockerfile \
225+
-t aiac-agent:latest \
226+
aiac/src/
227+
```
228+
229+
---
230+
231+
## Dependencies (`requirements.txt`)
232+
233+
```
234+
langgraph
235+
langchain-openai
236+
chromadb
237+
tenacity
238+
fastapi
239+
uvicorn[standard]
240+
requests
241+
python-dotenv
242+
kubernetes
243+
nats-py
244+
```

0 commit comments

Comments
 (0)