monitor.alert-mute
Whether to hold an alert back: its component is under maintenance, or a related alert on it is already firing.
1.0.0 · published 2026-10-03 by charlie · Anterra
Pinned by 19 tests, run in TypeScript, Python and Rust.
What it does
Whether to hold an alert back right now. It answers the question every alerting system has to ask before it pages someone, in the two ways Alertmanager does:
- **Maintenance** (a silence): the alert's component is inside a scheduled maintenance window and the alert's kind is one maintenance mutes. Planned work should not page for the downtime, slowness or error-budget burn it was planned to cause. - **Inhibition**: an alert that makes this one redundant is already firing on the same component. When a target is down, its latency alert adds nothing but a second page, so `down` inhibits `latency`.
For example
mute_alert(latency, false, , maintenance down, latency, burn-rate, inhibit ×1)→ muted false, reason —, by — nothing under maintenance and nothing else firing: not mutedmute_alert(latency, true, , maintenance down, latency, burn-rate, inhibit ×1)→ muted true, reason maintenance, by — under maintenance, a kind maintenance mutes is mutedmute_alert(cert-critical, true, , maintenance down, latency, burn-rate, inhibit ×1)→ muted false, reason —, by — under maintenance, a kind maintenance does not list still alerts (a certificate expiring is not planned work)
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
def mute_alert(kind: str, under_maintenance: bool, firing_kinds: Sequence[str], policy: MutePolicy) -> AlertMute
| kind | string | the alert's kind, e.g. down, latency or burn-rate |
| under_maintenance | bool | whether a maintenance window covers the alert's component now |
| firing_kinds | string[] | the kinds of the alerts firing on the same component now; kind itself, if listed, is ignored |
| policy | MutePolicy | which kinds maintenance holds back, and which alerts hold back which |
| returns | AlertMute |
The types it declares, generated into your project
@dataclass(frozen=True)
class MutePolicy:
"""What holds an alert back."""
#: the kinds held back while their component is under maintenance
maintenance: List[str]
#: checked in order; the first that applies names the inhibitor
inhibit: List[Inhibition]
@dataclass(frozen=True)
class Inhibition:
"""While an alert of kind source fires on a component, alerts of kind target on the same component are held back."""
source: str
#: not the same as source
target: str
MuteReason = Literal["maintenance", "inhibited"]
@dataclass(frozen=True)
class AlertMute:
"""Whether to hold the alert back, and why."""
muted: bool
#: maintenance wins over inhibited; null when not muted
reason: Optional[MuteReason]
#: the firing kind that inhibits it; null unless inhibited
by: Optional[str]
Your code names it in one line, in the file that uses it
from fune.monitor.alert_mute import mute_alert # monitor.alert-mute@^1
from typing import Sequence
from .monitor_alert_mute_types import AlertMute, MutePolicy
def mute_alert(kind: str, under_maintenance: bool, firing_kinds: Sequence[str], policy: MutePolicy) -> AlertMute:
"""Hold an alert back, as Alertmanager's silences and inhibit rules do:
during its component's maintenance if its kind is one maintenance mutes,
or while an alert that inhibits it fires on the same component. The whole
policy is checked every time, so a mistake in it fails loudly rather than
silently never muting. The caller passes `condition and not muted` to
monitor.alert-state."""
if kind == "":
raise ValueError("kind must not be empty")
for k in policy.maintenance:
if k == "":
raise ValueError("maintenance kinds must not be empty")
for rule in policy.inhibit:
if rule.source == "":
raise ValueError("inhibition source must not be empty")
if rule.target == "":
raise ValueError("inhibition target must not be empty")
if rule.source == rule.target:
raise ValueError("an alert kind cannot inhibit itself: %s" % (rule.source,))
if under_maintenance and kind in policy.maintenance:
return AlertMute(muted=True, reason="maintenance", by=None)
for rule in policy.inhibit:
# source != target, so an alert listed among firingKinds never inhibits itself.
if rule.target == kind and rule.source in firing_kinds:
return AlertMute(muted=True, reason="inhibited", by=rule.source)
return AlertMute(muted=False, reason=None, by=None)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add monitor.alert-mute
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./monitor.alert-mute-1.0.0-python.fune, or fetch it from a terminal with fune pull monitor.alert-mute@1.0.0:python.
The whole function, every language, is one file too: monitor.alert-mute-1.0.0.fune, 17,962 bytes, sha256 2ff913849ec88695dff7947db041136c9efcfe0adce2756ea7e73ae57951391b. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
# fune: before monitor.alert-mute
after — your function gets the result and the arguments, and returns the final result.
# fune: after monitor.alert-mute
replace — it requires no other capability, so there is no dependency to replace.
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show monitor.alert-mute --steps.
# fune: step monitor.alert-mute after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| nothing under maintenance and nothing else firing: not muted | latency, false, , maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
| under maintenance, a kind maintenance mutes is muted | latency, true, , maintenance down, latency, burn-rate, inhibit ×1 | → | muted true, reason maintenance, by — |
| under maintenance, a kind maintenance does not list still alerts (a certificate expiring is not planned work) | cert-critical, true, , maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
| a kind maintenance mutes, outside maintenance, alerts | burn-rate, false, , maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
| a firing down alert inhibits the latency alert on the same component | latency, false, down, maintenance down, latency, burn-rate, inhibit ×1 | → | muted true, reason inhibited, by down |
| a firing down alert does not inhibit a kind no rule names | burn-rate, false, down, maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
| the inhibitor must be firing: a rule alone does nothing | latency, false, burn-rate, maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
| maintenance wins over inhibition, and names no inhibitor | latency, true, down, maintenance down, latency, burn-rate, inhibit ×1 | → | muted true, reason maintenance, by — |
| the first inhibition in policy order names the inhibitor, whatever order the firing kinds come in | latency, false, burn-rate, down, maintenance , inhibit ×2 | → | muted true, reason inhibited, by down |
| an alert listed among the firing kinds does not inhibit itself | latency, false, latency, maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
Show the other 9 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| kinds match exactly: Latency is not latency | Latency, true, down, maintenance down, latency, burn-rate, inhibit ×1 | → | muted false, reason —, by — |
| an empty policy never mutes, even under maintenance with everything firing | down, true, latency, burn-rate, maintenance , inhibit | → | muted false, reason —, by — |
| a kind that is both inhibited and inhibits others is still muted by its own inhibitor | burn-rate, false, down, latency, maintenance , inhibit ×2 | → | muted true, reason inhibited, by latency |
| an empty kind is an error | , false, , maintenance down, latency, burn-rate, inhibit ×1 | → | error: kind must not be empty |
| an empty kind in the maintenance list is an error | latency, false, , maintenance down, , inhibit | → | error: maintenance kinds must not be empty |
| an inhibition with no source is an error | latency, false, , maintenance , inhibit ×1 | → | error: inhibition source must not be empty |
| an inhibition with no target is an error | latency, false, , maintenance , inhibit ×1 | → | error: inhibition target must not be empty |
| a kind inhibiting itself is an error | down, false, , maintenance , inhibit ×1 | → | error: an alert kind cannot inhibit itself: latency |
| the policy is checked even when maintenance already mutes the alert | latency, true, , maintenance latency, inhibit ×1 | → | error: an alert kind cannot inhibit itself: down |
More from the author
Pass the result on as the condition of `monitor.alert-state`: `condition and not muted`. A muted alert behaves as if its condition were false: it never fires, and one already firing resolves after its `clearForSeconds`, as the down alert does when a component goes into maintenance.
## Why it is shaped this way
- **Kinds are strings the caller chooses** (`down`, `latency`, `burn-rate`, `cert-critical`), matched exactly. The policy is data, usually read from the project's config, so a new kind of alert needs no new version. - **Maintenance lists what it mutes**, rather than muting everything: a certificate that is about to expire is not planned work, and should still alert during a database upgrade. - **Maintenance wins over inhibition** and `by` stays null, since the maintenance window is the reason that holds for the whole period. - **Inhibitions are checked in policy order**, not in the order the firing kinds arrive, so the answer does not depend on how the caller collected them. - **An alert never inhibits itself**: `source` and `target` must differ, so a kind listed in `firingKinds` for itself changes nothing. - **The whole policy is checked on every call**, even when the first maintenance kind already decides it. A mistake in the policy fails loudly instead of silently never muting. - Inhibition is per component (Alertmanager's `equal: [component]`): the caller passes only the kinds firing on the alert's own component.
## Errors
- `kind must not be empty` - `maintenance kinds must not be empty` - `inhibition source must not be empty` - `inhibition target must not be empty` - `an alert kind cannot inhibit itself: X`
## Sources
Prometheus Alertmanager: silences and inhibition (https://prometheus.io/docs/alerting/latest/alertmanager/#inhibition) and `inhibit_rules` in the configuration reference (https://prometheus.io/docs/alerting/latest/configuration/#inhibit_rule).
Files
| Path | Bytes |
|---|---|
| README.md | 2,564 |
| impl/python.py | 1,605 |
| impl/rust.rs | 2,942 |
| impl/typescript.ts | 1,568 |
| vectors.json | 5,012 |