let d = import "adr-defaults.ncl" in d.make_adr { id = "adr-019", title = "Schema-Aware AI and RAG — Nickel Contracts Constrain AI Config Generation", status = 'Accepted, date = "2026-01-08", context = "Infrastructure configuration generation via LLM is unreliable without grounding: generic AI produces plausible but structurally invalid configs (wrong field names, invalid enum values, incompatible option combinations). Two risks: (1) hallucination — AI generates configs that fail schema validation; (2) security — AI agents with unrestricted access to secrets and deployment operations create unaudited paths. The platform has Nickel schemas for all configuration surfaces and Cedar for authorization — both can be used to constrain AI behavior.", decision = "AI config generation is constrained by Nickel schemas at generation time and by Cedar policies at authorization time. The ai-service is the HTTP entry point for all AI operations. RAG indexes Nickel schemas, documentation, and past deployments as retrieval context — AI generates WITH schema context, making hallucination structurally harder. Cedar policy forbids ai-service from accessing any secret and requires `context.human_approved == true` before any deployment operation. The mcp-server exposes tool calling (nickel_validate, schema_query, best_practices) to LLM agents.", rationale = [ { claim = "Schema-constrained generation eliminates invalid config hallucination", detail = "Generic LLMs generate `engine = 'postgresql'` when the contract says `engine | [| 'postgres, 'mysql |]`. Providing the schema as RAG context gives the model the exact valid values. Post-generation nickel export validates the output against the same contract.", }, { claim = "Cedar is the enforcement layer — not prompt engineering", detail = "Prompting AI to 'not access secrets' is not a security boundary. Cedar policy `forbid(principal == Service::\"ai-service\", action == Action::\"read\", resource in Secret::\"*\")` is enforced at the platform layer regardless of what the LLM requests.", }, { claim = "RAG over project artifacts is more accurate than generic LLM for project-specific configs", detail = "Indexing `schemas/`, `docs/`, and past successful deployments means AI answers are grounded in actual project patterns — not generic infrastructure knowledge that may conflict with project constraints.", }, ], consequences = { positive = [ "AI cannot generate configs that fail Nickel schema validation — structural correctness enforced", "Cedar prevents AI from accessing secrets or deploying without human approval", "RAG over project artifacts reduces hallucination on project-specific options", "MCP tool calling (nickel_validate, schema_query) enables LLM agents to self-correct", ], negative = [ "RAG index must be kept current as schemas and docs evolve — stale index degrades answer quality", "ai-service adds a service dependency for all AI-assisted operations", "Cost tracking required: rate limiting at 60 req/min, 1M tokens/day, $100/day", ], }, alternatives_considered = [ { option = "Generic LLM without schema grounding (GitHub Copilot style)", why_rejected = "Generates syntactically valid but semantically wrong configs — wrong enum values, missing required fields, invalid option combinations. Schema validation must happen after generation and frequently fails.", }, { option = "Fine-tuned model on project schemas", why_rejected = "Fine-tuning is expensive, requires retraining on every schema change, and does not generalize across projects. RAG is dynamic and always reflects the current schema state.", }, ], constraints = [ { id = "ai-cannot-access-secrets", claim = "ai-service must have a Cedar policy explicitly forbidding access to any Secret resource", scope = "platform/crates/control-center/src/policies/", severity = 'Hard, check = { tag = 'Grep, pattern = "ai-service.*Secret|Secret.*ai-service", paths = ["platform/"], must_be_empty = false }, rationale = "AI agents with secret access create unaudited credential exposure. The constraint must be at the authorization layer, not in the LLM prompt.", }, { id = "ai-deployment-requires-human-approval", claim = "Any deployment action triggered by ai-service must have context.human_approved == true in the Cedar evaluation context", scope = "platform/crates/orchestrator/src/", severity = 'Hard, check = { tag = 'Grep, pattern = "human_approved", paths = ["platform/"], must_be_empty = false }, rationale = "Autonomous deployment without human review is an unacceptable risk for production infrastructure. The approval gate is enforced by Cedar, not by AI self-restraint.", }, { id = "ai-generation-validates-against-schema", claim = "All AI-generated Nickel configs must be validated via nickel export before being presented to the user or submitted to the orchestrator", scope = "platform/crates/ai-service/src/", severity = 'Hard, check = { tag = 'Grep, pattern = "nickel.*export|nickel_validate", paths = ["platform/crates/ai-service/"], must_be_empty = false }, rationale = "Post-generation validation closes the loop — if the LLM generates an invalid config despite schema grounding, the user sees a validation error, not a deployment failure.", }, ], related_adrs = ["adr-014-solid-enforcement", "adr-017-typedialog-web-ui", "adr-018-secretumvault-integration"], ontology_check = { decision_string = "AI config generation is constrained by Nickel schemas (RAG grounding) and Cedar policies (secret isolation, human approval gate)", invariants_at_risk = ["solid-boundaries", "type-safety-nickel"], verdict = 'Safe, }, }