Deciding that an object is broken and telling someone are two different problems, and jaque keeps them in two different places. The state machine decides; a notification policy names who is told and under what rule. A policy is a named entry under notifications:, built from contacts, a set of time windows and a ladder of escalation levels, and it is attached to a host or service by name. An object with no policy never notifies, and that is the default. The ladder's threshold and renotify_interval are evaluated by the notify package on every notifiable transition; see Escalations and windows for the exact mechanics.

1. Contact types

A contact is one of four discriminated variants of #Contact, told apart by type. Every variant is a destination and nothing else; credentials never live on a contact.

contacts: {
	alice:       {type: "email", address: "alice@example.com"}
	oncall_hook: {type: "webhook", url: "https://hooks.example.com/oncall"}
	bob:         {type: "command", command: "page", args: ["-q"], address: "bob@example.com"}
	dave:        {type: "telegram", chat_id: "555444333"}
}
Type Required field Delivered by
email address SMTP, or the system mail binary when no relay is configured
webhook url an HTTP POST
command command a top-level command template, run as a process
telegram chat_id the Telegram Bot API

A command contact names a key in the top-level commands map, the same indirection a command check uses. args is passed through; address is what $CONTACTEMAIL$ expands to for that contact and defaults to the empty string. webhook and command are how anything without a native adapter is reached: a pager service, a chat incoming webhook, a script that speaks whatever the receiver speaks. If it accepts HTTP or command-line arguments, it is a contact.

A telegram contact carries only chat_id. The bot token is a process-level secret, configured once per jaque instance; see Adapters.

2. A policy

notifications: business_hours: {
	period: [
		{weekday: "monday", start: "09:00", end: "17:00"},
		{weekday: "tuesday", start: "09:00", end: "17:00"},
	]
	levels: [
		{contacts: ["alice"], threshold: 3, renotify_interval: "30m"},
		{contacts: ["oncall_hook", "bob", "dave"], renotify_interval: "10m"},
	]
}

period is a list of windows; empty, the default, means the policy is always open. levels is a non-empty list of escalation levels evaluated in order, each naming the contacts it reaches. A policy with no levels is a config error, not a silent policy. The semantics of threshold, renotify_interval and the windows are specified in Escalations and windows.

3. Wiring a policy to an object

A host or service refers to a policy by name through its notification field.

hosts: gw: {
	address: "192.168.1.1"
	check: {type: "icmp", host: "192.168.1.1"}
	notification: "business_hours"
	services: ssh: {
		check:        {type: "tcp", address: "192.168.1.1:22"}
		notification: "business_hours"
	}
}

notification defaults to "", and an empty name means the object never notifies. That is exactly the behaviour every configuration written before contacts and policies existed already had, so adding notifications is opt-in per object and never a silent change to an existing config. A service does not inherit its host's policy; each object names its own.

4. What a policy is not

A policy does not decide that something is broken. Problem-class notifications come only from hard transitions of the state model; a single failed check never reaches a policy. A policy also does not deliver: it produces a decision that the engine appends to the event log, and delivery is a separate concern with its own durability rules, described in Adapters.

5. Security considerations

A contact is a destination, never a credential. SMTP credentials and the Telegram bot token are process-level flags or environment variables and are not expressible in contacts:, so a config file can be shared without leaking a secret. A command contact runs a process on the host that delivers; the commands map is part of the trusted configuration, and args are passed to it verbatim.