The platform exposes POST /api/v1/policies/compile — a stateless adapter that turns YAML, JSON, Markdown, or English prose into a PolicyCreate-shaped object that the regular CRUD endpoints accept. Prose and Markdown are routed through Gemini (gemini-2.5-flash); YAML/JSON skip the LLM and validate directly.
The compiler never raises — every parse failure, schema mismatch, or missing field is returned as a structured errors array so the UI can render them inline.
Endpoint
POST /api/v1/policies/compile
Content-Type: application/json
{
"source": "Refunds over $200 must be approved by a manager. Block deletes on production tables.",
"source_format": "prose"
}
source_format is one of yaml, yml, json, prose, markdown, md.
Response:
{
"ok": true,
"policy": {
"name": "refund-approval-and-prod-deletes",
"denied_actions": ["delete_production_database"],
"require_approval": true,
"approval_actions": ["issue_refund"],
"approval_threshold_usd": 200.0
},
"warnings": [
"policy compiled from prose by Gemini — review every field before activating"
],
"errors": []
}
ok is true only when errors is empty. The returned policy slot is not persisted — POST it to /api/v1/policies/ to save.
YAML / JSON
Validated against the same Pydantic schema (PolicyCreate) used by the create/update endpoints, then re-serialized with sorted keys. Unknown top-level keys become warnings (so you can compile a partial extract from a larger document) and contradictions surface as soft warnings:
allowed_actions and denied_actions overlap (deny wins at runtime).
approval_* fields set with require_approval=False (approvals will not fire).
Prose / Markdown
Calls Gemini with a system prompt that:
- Maps verbs to fields (
block/deny/forbid → denied_actions; only allow → allowed_actions; require approval → require_approval=True).
- Converts amounts (
$500 → 500.0).
- Maps prerequisites (
must look up before refunding) into evidence_requirements.
- Refuses to invent action names not present in the source.
The model output is then run through the same PolicyCreate validator as YAML/JSON, so a hallucinated field cannot produce an invalid policy — it surfaces as a structured error.
Environment: the platform must have GEMINI_API_KEY (or GOOGLE_API_KEY) set. Without it, prose compiles return:
{ "ok": false, "errors": ["prose source_format requires GEMINI_API_KEY (or GOOGLE_API_KEY) to be set in the platform environment"] }
Round-trip serialization
The companion helper policy_to_yaml(policy) (importable from app.services.policy_compiler) renders any policy dict back to canonical YAML — sorted keys, None stripped, block style. Useful for showing policies in the UI or exporting them.
Console integration
The console policy editor calls this endpoint directly:
- Author types/pastes prose, YAML, or JSON.
- Editor POSTs to
/api/v1/policies/compile on every paste/blur.
errors render as red squiggles inline; warnings show as yellow callouts.
- On commit, the compiled
policy is sent to POST /api/v1/policies/.
See Console → Settings → Policies for the UI-side workflow and Concepts → Policies for the underlying IR.
Always review prose-compiled policies before activating them. Gemini occasionally invents fields the author did not request — the warning string above is your prompt to audit every key.
See also