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.