Anyone who has maintained a Nagios estate past a few hundred hosts knows the failure this page is about: use chains four templates deep, a directive misspelled in a way Nagios accepts and silently ignores, and no way to know a config is broken until -v catches it, or does not. jaque's config is CUE: a typed language whose values unify instead of inheriting, with the schema in the language rather than beside it. This page states the problem, what CUE replaces it with, and the two distinct points at which a jaque config can fail.

1. The problem: template inheritance without a type system

Nagios configuration is templates (use) resolved by an ad hoc positional algorithm: ordered multiple inheritance, +value to append, null to unset, register 0 for abstract templates. It works, and it is untyped. A misspelled directive is either ignored or a runtime surprise, and there is no way to ask whether a config is valid without loading it into the daemon that will act on it.

2. What CUE gives instead

CUE is built around unification rather than inheritance. Two partial definitions of the same value merge; they do not override each other in a resolution order you have to remember. For "shared defaults, per-host overrides" that is a closer fit than use chains ever were, and it brings three things with it.

A real schema. jaque defines #Host, #Service, #Check and the rest as closed structs, so an unknown field such as chekc_interval is a build error naming the field, not a directive quietly dropped.

Validation as part of the language. Type constraints (string & !=""), enumerations (disjunctions shaped like "OK" | "WARNING" | "CRITICAL" | "UNKNOWN") and numeric ranges live in the schema itself, not in a linter bolted on after the fact.

Diffable, GitOps-friendly text. A CUE file is text you git diff, review and merge. Nothing is generated by a UI that only the UI can read back.

3. Two validation phases

A config fails in two different places, and the errors differ.

3.1 Constraints, at build

CUE evaluates the object graph against #Schema: types, required fields, closed-struct field names, and disjunction shape, where a check's type selects which of #TCPCheck, #HTTPCheck and the rest applies. A typo or a missing address is caught here, before the graph is fully concrete.

3.2 Concretion, at load

A config that satisfies every constraint can still be incomplete: CUE lets a value remain an open disjunction or an unresolved reference. Running jaque -config x.cue forces full concretion at load; anything still ambiguous fails there, with the field's path in the error.

In practice: run jaque -config yourfile.cue and read the error. It names the field, never only "config invalid".

4. What this replaces, concretely

The Nagios idiom of a generic-host template three levels of use deep becomes a CUE template you anchor structs on:

#CastDevice: #Host & {
	parents: ["router"]
	check: type: "tcp"
}

hosts: chromecast: #CastDevice & {
	address: "192.168.1.200"
	check: address: "192.168.1.200:8009"
}

#CastDevice is not inherited from; it is unified with. Two devices on that template cannot drift into "which one applied the override" the way a use chain can. Anchoring on #Host matters: a bare #CastDevice: {...} is implicitly closed and would reject address and check as unknown fields at the use site, while anchoring on the schema keeps it open exactly as the schema is. CUE in practice is the full walkthrough.

5. The decision, and its reversal criterion

CUE was not a settled bet. ADR-003 set an explicit reversal criterion: the Phase 0 spike had to model three hosts and ten services with a shared template and one override, loaded and validated in-process, or the decision reverted to YAML plus JSON Schema. It passed, and CUE is what runs today. A config language that could not clear that bar would have been replaced, not defended.