Skip to content

Commit e44873b

Browse files
committed
docs: add governance policy plane tutorial + reference
New tutorial chapter 30 walks from empty project to MS-YAML-enforced chat across six steps; reference page catalogs the GovernancePolicy SPI, both YAML schemas, operator/action semantics, and the /api/admin/governance/* HTTP surface; what's-new paragraph highlights the Microsoft Agent Governance Toolkit interop.
1 parent bfba0d9 commit e44873b

4 files changed

Lines changed: 586 additions & 0 deletions

File tree

docs/astro.config.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export default defineConfig({
8484
{ label: 'Clustering', slug: 'tutorial/16-clustering' },
8585
{ label: 'Durable Sessions', slug: 'tutorial/17-durable-sessions' },
8686
{ label: 'Observability', slug: 'tutorial/18-observability' },
87+
{ label: 'Governance Policy Plane', slug: 'tutorial/30-governance-policy-plane' },
8788
{ label: 'Migration 2.x → 4.0', slug: 'tutorial/22-migration' },
8889
],
8990
},
@@ -106,6 +107,7 @@ export default defineConfig({
106107
{ label: 'Kotlin DSL', slug: 'reference/kotlin' },
107108
{ label: 'Observability', slug: 'reference/observability' },
108109
{ label: 'Admin Control Plane', slug: 'reference/admin' },
110+
{ label: 'Governance Policy Plane', slug: 'reference/governance' },
109111
{ label: 'Durable Sessions', slug: 'reference/durable-sessions' },
110112
{ label: 'Durable Checkpoints', slug: 'reference/checkpoint' },
111113
{ label: 'Performance Benchmarks', slug: 'reference/benchmarks' },
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
---
2+
title: "Governance Policy Plane"
3+
description: "GovernancePolicy SPI, YAML schemas (native + Microsoft Agent Governance Toolkit), PolicyAdmissionGate, /api/admin/governance/* HTTP surface"
4+
---
5+
6+
Declarative governance layered on top of `AiGuardrail`. Loaded from YAML (Atmosphere-native or Microsoft Agent Governance Toolkit format), enforced on every `@AiEndpoint` through `AiPipeline`, queryable via HTTP, and interoperable with the Microsoft `/check` ASGI protocol.
7+
8+
Tutorial: [Governance Policy Plane](/docs/tutorial/30-governance-policy-plane/). Module: `atmosphere-ai`. Admin surface: `atmosphere-admin`.
9+
10+
---
11+
12+
## SPIs
13+
14+
### `GovernancePolicy`
15+
16+
Package: `org.atmosphere.ai.governance`. Core declarative SPI — a named, versioned, source-identified `evaluate(PolicyContext) → PolicyDecision` function.
17+
18+
```java
19+
public interface GovernancePolicy {
20+
String POLICIES_PROPERTY = "org.atmosphere.ai.governance.policies";
21+
22+
String name(); // stable identifier for audit trail
23+
String source(); // yaml:/path/to/file.yaml | classpath:file.yaml | code:<fqn>
24+
String version(); // semver, ISO date, or content hash — operator choice
25+
PolicyDecision evaluate(PolicyContext context);
26+
}
27+
```
28+
29+
Implementations MUST be thread-safe, side-effect-free (except metrics / logging), and MUST NOT throw — exceptions fail-closed to `Deny` at every admission seam.
30+
31+
### `PolicyContext`
32+
33+
```java
34+
public record PolicyContext(Phase phase, AiRequest request, String accumulatedResponse) {
35+
public enum Phase { PRE_ADMISSION, POST_RESPONSE }
36+
public static PolicyContext preAdmission(AiRequest request);
37+
public static PolicyContext postResponse(AiRequest request, String accumulatedResponse);
38+
}
39+
```
40+
41+
### `PolicyDecision`
42+
43+
Sealed type:
44+
45+
```java
46+
sealed interface PolicyDecision {
47+
record Admit() implements PolicyDecision { }
48+
record Transform(AiRequest modifiedRequest) implements PolicyDecision { }
49+
record Deny(String reason) implements PolicyDecision { }
50+
static PolicyDecision admit();
51+
static PolicyDecision transform(AiRequest r);
52+
static PolicyDecision deny(String reason);
53+
}
54+
```
55+
56+
`Transform` on the post-response path is non-operational (streamed text is not retroactively rewritable) — the pipeline logs a warning and downgrades to `Admit`.
57+
58+
### `PolicyParser`
59+
60+
Pluggable YAML / Rego / Cedar parser. Discovered via `java.util.ServiceLoader`.
61+
62+
```java
63+
public interface PolicyParser {
64+
String format(); // "yaml" | "rego" | "cedar"
65+
List<GovernancePolicy> parse(String source, InputStream in) throws IOException;
66+
}
67+
```
68+
69+
**One implementation ships in-tree:** `YamlPolicyParser` (SnakeYAML `SafeConstructor`, no arbitrary class instantiation). Auto-detects Atmosphere-native vs MS schema by root-key inspection.
70+
71+
### `PolicyRegistry`
72+
73+
Maps YAML `type:` names to factory functions.
74+
75+
```java
76+
var registry = new PolicyRegistry(); // built-ins pre-registered
77+
registry.register("my-domain-policy", descriptor ->
78+
new MyDomainPolicy(descriptor.name(), descriptor.source(),
79+
descriptor.version(), descriptor.config()));
80+
var parser = new YamlPolicyParser(registry);
81+
```
82+
83+
Built-in types:
84+
85+
| `type:` | Wraps | Config keys |
86+
|---|---|---|
87+
| `pii-redaction` | `PiiRedactionGuardrail` | `mode: redact \| block` |
88+
| `cost-ceiling` | `CostCeilingGuardrail` | `budget-usd: <number>` |
89+
| `output-length-zscore` | `OutputLengthZScoreGuardrail` | `window-size`, `z-threshold`, `min-samples` |
90+
91+
### `PolicyAdmissionGate`
92+
93+
Static utility — runs the policy chain on an `AiRequest` **outside** `AiPipeline`. For code paths that produce responses locally (demo responders, canned replies) and therefore never reach the pipeline.
94+
95+
```java
96+
var result = PolicyAdmissionGate.admit(resource, new AiRequest(message));
97+
switch (result) {
98+
case PolicyAdmissionGate.Result.Denied denied -> /* session.error(...) */;
99+
case PolicyAdmissionGate.Result.Admitted admitted -> /* use admitted.request() */;
100+
}
101+
```
102+
103+
Fail-closed — a throwing policy becomes `Denied` with the exception message.
104+
105+
### Adapters
106+
107+
- `GuardrailAsPolicy(AiGuardrail)` — expose any `AiGuardrail` as a `GovernancePolicy`.
108+
- `PolicyAsGuardrail(GovernancePolicy)` — expose any `GovernancePolicy` as an `AiGuardrail`. Used internally by `AiEndpointProcessor` to merge policies into the guardrail list consumed by `AiPipeline`.
109+
110+
---
111+
112+
## YAML schemas
113+
114+
### Atmosphere-native (type-dispatch)
115+
116+
```yaml
117+
version: "1.0"
118+
policies:
119+
- name: <unique-id>
120+
type: pii-redaction | cost-ceiling | output-length-zscore | <custom>
121+
version: "1.0"
122+
config: { ... }
123+
```
124+
125+
The document `version:` is the fallback used when a policy entry omits its own `version:`.
126+
127+
### Microsoft Agent Governance Toolkit (rules-over-context)
128+
129+
Faithful port of MS's `_match_condition` + `PolicyEvaluator.evaluate` semantics. Documents with a top-level `rules:` sequence trigger the MS branch.
130+
131+
```yaml
132+
version: "1.0"
133+
name: <policy-document-name>
134+
description: <optional>
135+
rules:
136+
- name: <rule-id>
137+
condition:
138+
field: <key>
139+
operator: eq | ne | gt | lt | gte | lte | in | contains | matches
140+
value: <scalar | list | regex>
141+
action: allow | deny | audit | block
142+
priority: <integer> # higher wins; pre-sorted descending at load
143+
message: <surfaced on Deny>
144+
defaults:
145+
action: allow | deny | audit | block
146+
```
147+
148+
**Operator semantics:**
149+
150+
| Operator | Behavior |
151+
|---|---|
152+
| `eq` / `ne` | Loose equality (numeric cross-type aware — `1 == 1.0`) |
153+
| `gt`, `lt`, `gte`, `lte` | `Comparable`-based ordering |
154+
| `in` | Value appears in target list (target must be a sequence) |
155+
| `contains` | Substring (string context) or membership (collection context) |
156+
| `matches` | Regex via `java.util.regex.Pattern.matcher().find()` — compiled at parse time |
157+
158+
**Action mapping:**
159+
160+
| YAML | `PolicyDecision` |
161+
|---|---|
162+
| `allow` | `Admit` |
163+
| `deny` / `block` | `Deny(message)` |
164+
| `audit` | `Admit` + structured INFO log |
165+
166+
**Context map** — rule `field:` references resolve against:
167+
168+
| Key | Source |
169+
|---|---|
170+
| `message`, `system_prompt`, `model` | `AiRequest` direct fields |
171+
| `user_id`, `session_id`, `agent_id`, `conversation_id` | `AiRequest` direct fields |
172+
| `phase` | `pre_admission` / `post_response` |
173+
| `response` | Accumulated response text (post-response only) |
174+
| *anything else* | `AiRequest.metadata()` entries by exact key |
175+
176+
**Schema exclusivity** — documents that mix `rules:` and `policies:` raise `IOException` at parse time. Pick one.
177+
178+
**Conformance** — `MsAgentOsYamlConformanceTest` parses MS's own example YAMLs (copied unmodified from `microsoft/agent-governance-toolkit@April-2026` under `docs/tutorials/policy-as-code/examples/`) and asserts MS's documented behavior. Upstream schema drift fails the test.
179+
180+
---
181+
182+
## AiPipeline wiring
183+
184+
The canonical `AiPipeline` constructor accepts both guardrails and policies:
185+
186+
```java
187+
new AiPipeline(runtime, systemPrompt, model,
188+
memory, toolRegistry,
189+
guardrails, policies, contextProviders,
190+
metrics, responseType);
191+
```
192+
193+
Pre-admission order: guardrails first, policies second. Exceptions in a policy's `evaluate()` method fail-closed to `Deny` (Correctness Invariant #2). Post-response evaluation merges policies into `GuardrailCapturingSession` via `PolicyAsGuardrail` — one loop, deterministic ordering.
194+
195+
`AiEndpointProcessor` merges policies from three sources with dedup by `name()`:
196+
197+
1. `ServiceLoader<GovernancePolicy>` (for framework-less / Quarkus deployments)
198+
2. Framework-property bag (`POLICIES_PROPERTY` — populated by YAML loaders or Spring auto-config)
199+
3. Annotation-declared classes on `@AiEndpoint(guardrails = {...})` continue to work via `AiGuardrail` unchanged
200+
201+
---
202+
203+
## Spring Boot auto-configuration
204+
205+
`AtmosphereAiAutoConfiguration` bridges Spring-managed beans onto framework properties:
206+
207+
- Every `@Component` / `@Bean` of type `AiGuardrail` → `AiGuardrail.GUARDRAILS_PROPERTY`
208+
- Every `@Component` / `@Bean` of type `GovernancePolicy` → `GovernancePolicy.POLICIES_PROPERTY`
209+
210+
Direct YAML loading (typical pattern):
211+
212+
```java
213+
@Configuration
214+
public class PoliciesConfig {
215+
@Bean
216+
Object atmospherePolicyPlaneLoader(AtmosphereFramework framework) throws IOException {
217+
try (var in = new ClassPathResource("atmosphere-policies.yaml").getInputStream()) {
218+
var policies = new YamlPolicyParser()
219+
.parse("classpath:atmosphere-policies.yaml", in);
220+
framework.getAtmosphereConfig().properties()
221+
.put(GovernancePolicy.POLICIES_PROPERTY, policies);
222+
return policies;
223+
}
224+
}
225+
}
226+
```
227+
228+
---
229+
230+
## HTTP surface
231+
232+
Exposed by `AtmosphereAdminEndpoint` when `atmosphere-admin` is on the classpath. Wire-compatible with Microsoft Agent Governance Toolkit's `PolicyProviderHandler` ASGI app.
233+
234+
### `GET /api/admin/governance/policies`
235+
236+
Lists the live policy chain.
237+
238+
```json
239+
[
240+
{ "name": "string", "source": "string",
241+
"version": "string", "className": "string" }
242+
]
243+
```
244+
245+
### `GET /api/admin/governance/summary`
246+
247+
```json
248+
{ "policyCount": 0, "sources": ["string"] }
249+
```
250+
251+
### `POST /api/admin/governance/check`
252+
253+
MS `/check`-compatible decision endpoint.
254+
255+
Request:
256+
257+
```json
258+
{ "agent_id": "string", "action": "string", "context": { "<key>": "<value>" } }
259+
```
260+
261+
Response:
262+
263+
```json
264+
{
265+
"allowed": true,
266+
"decision": "allow | deny | transform",
267+
"reason": "string",
268+
"matched_policy": "string | null",
269+
"matched_source": "string | null",
270+
"evaluation_ms": 0.0
271+
}
272+
```
273+
274+
Maps `agent_id` → `AiRequest.agentId`, each `context` entry onto `AiRequest.metadata()`. External gateways pointed at MS's ASGI app work against Atmosphere without payload translation.
275+
276+
---
277+
278+
## Correctness invariants
279+
280+
| Invariant | How honored |
281+
|---|---|
282+
| **#2 Terminal-path completeness** | Policy exceptions fail-closed to `Deny` at every admission seam |
283+
| **#5 Runtime truth** | `GovernanceController` reports installed policies, not classpath or YAML intent |
284+
| **#7 Mode parity** | `PolicyPlaneSourceParityTest` — same admission decision whether policies came from YAML, code, or ServiceLoader |
285+
286+
---
287+
288+
## Related
289+
290+
- Tutorial: [Governance Policy Plane](/docs/tutorial/30-governance-policy-plane/)
291+
- Sample: [`samples/spring-boot-ms-governance-chat`](https://github.com/Atmosphere/atmosphere/tree/main/samples/spring-boot-ms-governance-chat)
292+
- In-tree detailed docs: [`docs/governance-policy-plane.md`](https://github.com/Atmosphere/atmosphere/blob/main/docs/governance-policy-plane.md)
293+
- Module reference: [`modules/ai/README.md`](https://github.com/Atmosphere/atmosphere/blob/main/modules/ai/README.md#governance-policy-plane-phase-a)
294+
- Upstream toolkit: [github.com/microsoft/agent-governance-toolkit](https://github.com/microsoft/agent-governance-toolkit)

0 commit comments

Comments
 (0)