jaque has no hostgroup object. Hosts, services and contacts carry key/value labels, a selector is a predicate over those labels, and a saved view is a name bound to a selector. "Everything in prod" is one selector, and an acknowledgement or a downtime that targets it is one command rather than a loop over names. The cost is that there is no list to edit when membership changes; the benefit is that there is no list to go stale, because membership is whatever the labels currently say it is.

1. Labels are the model

hosts: web: {
	address: "10.0.0.1"
	check:   {type: "icmp", host: "10.0.0.1"}
	labels:  {env: "prod", role: "web"}
	services: api: {
		check:  {type: "tcp", address: "10.0.0.1:443"}
		labels: {env: "prod", tier: "backend"}
	}
}

Labels live in the core; groups do not. Membership is whichever objects share a label, and nothing else has to be kept in sync with it. The importer translates each Nagios hostgroup to a hostgroup.<name> label on every member (see From Nagios), so a group that existed in Nagios still exists as a selector. Contacts carry labels too, so the importer has somewhere to put a contactgroup; nothing indexes contact labels yet.

1.1 Key and value constraints

Keys and values are each constrained by a regex enforced in the CUE schema, so a malformed label fails validation with a field-level error naming the file and line, rather than a runtime error after decoding.

Part Pattern Note
Key =~"^[a-z][a-z0-9_.-]*$" Lowercase only, so a label can never read like a #Vars macro name, which is uppercase-enforced and a different thing.
Value =~"^[a-zA-Z0-9][a-zA-Z0-9_.:/-]*$" Uppercase allowed.
labels: {Env: "prod"}  // rejected: uppercase key

2. Selectors

A selector is a comma-separated list of requirements; the comma is AND.

env=prod, team=redes

A requirement is key=value, key!=value, key in (a, b), key notin (a, b), a bare key (the key is present) or !key (the key is absent). The empty selector is a parse error, not "everything": the first consumer of selectors schedules downtime, and an empty string arriving from an unset variable must not silence the estate. Everything has to be said deliberately.

Any command that targets objects, AcknowledgeProblem or ScheduleDowntime among them, accepts either one explicit object or a selector matching many. "Downtime every service tagged team=redes" is one ScheduleDowntime call with a selector target.

3. Saved views

A view is a name bound to a selector, never to a static membership list, so it cannot go stale as objects come and go:

views: {
	prod: selector: "env=prod"
	"Web Servers": {
		selector:    "hostgroup.web-servers"
		description: "Nagios hostgroup \"Web Servers\""
	}
}

The map key is the display name and is deliberately unconstrained (spaces and uppercase allowed) because it is never parsed: it is exactly what an operator wrote, or exactly what a Nagios group's alias was before import. selector is parsed at load time with the grammar above, so a malformed one is a config error. description is free text, defaulting to empty. The dashboard's #/groups page lists every saved view with a live member count; see Dashboard.

4. Security considerations

A selector is the blast radius of the command that carries it. A selector that matches more than intended acknowledges or silences more than intended, and jaque does not confirm: it matches. The empty-selector refusal above is the one guard the grammar provides; the rest is the operator's care with ScheduleDowntime over a broad label, and the -api-token that protects the command surface (see FIFO and API).