Alert rules

Who gets told, when, and why · condition, dedupe key, throttle bucket, routing · this page authors and evaluates, module 01 stores and delivers

No module in i3 posts to Slack. A rule here evaluates a condition and emits alert.raise on the internal bus with a rendered dedupe_key and a computed throttle_bucket. Module 01 owns notification_rules, notifications and every delivery, and its UNIQUE (rule_id, dedupe_key, throttle_bucket) constraint is what makes a duplicate alert impossible. Ruling L12. Delivery side →
3 conditions muted right now, so quiet is not the same as healthy: influencer.return_overdue 4h 12m left grn.qc_fail_rate 1d 0h left ingest.p95_over_budget 1d 12h left All silences →
Conditions 18 of 51
Negative sellable balance
ledger.negative_balance · P1
live
Hash chain broken
audit.chain_broken · P1
live
Shadow rule wrote to the ledger
classifier.shadow_leak · P1
live
RFID gate silent
rfid.gate_absent · P1
live
Composite parent holds stock
ledger.composite_breach · P1
live
Classifier match rate under gate
classifier.match_rate_low · P2
live
Unmatched events rising
classifier.unmatched_spike · P2
live
Outbox dead letter unclaimed
fanout.dlq_unclaimed · P2
live
Subscriber backlog ageing
fanout.backlog_age · P2
live
Ingest p95 over budget
ingest.p95_over_budget · P2
muted 1d
Topic absent beyond baseline
ingest.topic_absent · P2
live
Rate bucket held over 10 min
ratelimit.sustained_hold · P2
live
HMAC failures in an hour
security.hmac_failures · P2
live
Balance cache unreconciled key
ledger.cache_unreconciled · P3
live
Rule never fired in 30 days
classifier.dead_rule · P3
live
Derived idempotency key used
ingest.derived_key · P3
shadow 4/7
Monthly upstream spend over budget
ratelimit.budget_overrun · P3
shadow 2/7
Carrier RTO without a scan
ops2.rto_unscanned · P2
gate-blocked

Basics condition code is immutable once live

v4 liveedited 24-07 by ahmad · v3 kept, never overwritten

Condition evaluated every 30s against the metrics store · Asia/Dubai

AND
AND
Must hold for 0s before firing · instant
The third clause matters. An unreconciled balance key drops out of every total under ruling L8, so alerting on it would page someone about a number i3 has already disowned. This condition only fires on a balance i3 stands behind.

Dedupe key and throttle bucket rendered here, enforced by module 01

ledger:neg:4182:BAS:STOCK
seconds
throttle_bucket = floor(epoch / 3600)
= floor(1785484882 / 3600) = 495967
The engine renders the key and computes the bucket. It never lets a caller supply either, which is what makes module 01's UNIQUE (rule_id, dedupe_key, throttle_bucket) sufficient. i2 had no alerting tables at all and did this in n8n, where dedupe was a convention rather than a constraint, which is why duplicate notifications kept recurring.

Severity P1 skips quiet hours · P2 and P3 respect the mute grid

minutes unacknowledged, to
a gate-bound condition stays in shadow until that gate has held 7 days

Suppression sentences, not settings

Suppress a re-fire for the same sku_id + location_code + stock_type within 60 min×
Group more than 10 fires / 5 min into one summary alert naming the blast radius×
Auto-resolve when inv.sellable >= 0 holds for 10 min, emitting alert.clear×
Never suppress when blast.value_aed > 50,000×

Mute schedule quiet hours in Dubai · P1 ignores this entirely

Mon
08-20
Tue
08-20
Wed
08-20
Thu
08-20
Fri
muted
Sat
10-18
Sun
off

A muted day still records the fire and still queues it into the next digest. Nothing is discarded, so a Friday incident is not invisible on Saturday morning. This rule is P1, so the grid is shown for reference and not applied.

Routing this module names the channels · module 01 delivers on them

IAIn-appalways on for P1
attention queuenotification bell
SLSlackposted by module 01, never from here
#n8n-updates ×#n8n-alerts ×
EMEmailthrottled 1 per minute per rule
ahmad@minimalist.ae ×
DGDigestoff for P1 · a P1 in a digest is a P1 nobody saw

Backtest before you enable 30 days of history · writes nothing

Would fire
1,284
raw condition hits
After dedupe
206
distinct keys
After throttle
63
distinct key × bucket
A human sees
63
P1 ignores the mute grid

Four separate numbers, because a single "would fire" count is what makes a rule look either broken or harmless. A condition may not go live unless the last number over 30 days lands between 1 and 200: zero means it would never have fired and is untestable, over 200 means it is noise before it ships. This one is inside the band.

Fire history 30 days

30-06 · 34 fired30-06 · 3 emitted 01-07 · 42 fired01-07 · 4 emitted 02-07 · 28 fired02-07 · 2 emitted 03-07 · 62 fired03-07 · 6 emitted 04-07 · 88 fired · ops2 payload shape change04-07 · 11 emitted 05-07 · 46 fired05-07 · 4 emitted 06-07 · 24 fired06-07 · 2 emitted 07-07 · 19 fired07-07 · 1 emitted 08-07 · 26 fired08-07 · 2 emitted 09-07 · 22 fired09-07 · 1 emitted 10-07 · 36 fired10-07 · 3 emitted 11-07 · 17 fired11-07 · 1 emitted 12-07 · 15 fired12-07 · 1 emitted 13-07 · 31 fired13-07 · 2 emitted 14-07 · 24 fired14-07 · 2 emitted 15-07 · 12 fired15-07 · 1 emitted 16-07 · 19 fired16-07 · 1 emitted 17-07 · 29 fired17-07 · 2 emitted 18-07 · 10 fired18-07 · 1 emitted 19-07 · 17 fired19-07 · 1 emitted 20-07 · 7 fired20-07 · 1 emitted 21-07 · 14 fired21-07 · 1 emitted 22-07 · 5 fired22-07 · 1 emitted 23-07 · 10 fired23-07 · 1 emitted 24-07 · 3 fired 25-07 · 5 fired25-07 · 1 emitted 26-07 · 2 fired 27-07 · 1 fired 28-07 · 2 fired 29-07 · 4 fired29-07 · 1 emitted
30-0614-0729-07
fired emitted after suppression

The declining curve is opening balances landing per location. On 04-07 BAS was seeded and the condition fired 88 times in a day; suppression collapsed that into 11 alerts.

Signal to noise this condition

Acted on58 / 63
Acknowledged inside SLA54 / 63
Snoozed then re-fired3 / 63
Dismissed with no action2 / 63

Emitted, not delivered the boundary

alert.raise {
  rule_code: "ledger.negative_balance",
  dedupe_key: "ledger:neg:4182:BAS:STOCK",
  throttle_bucket: 495967,
  entity_kind: "sku_location",
  entity_ref: "AQB01@BAS",
  blast: { units: 41, value_aed: 18040 },
  deep_link: "/inventory-live?sku=AQB01"
}

That is the whole contract. Module 01 inserts, or increments occurrence_count on conflict, then decides channels and sends. This module has no Slack token and no webhook of its own.

Permission L11

Editing a condition needs role >= manager plus the i3.alerts_author feature. Promoting one out of shadow needs a second approver. Your role: admin.

All conditions

Fires and emits are 30-day counts · a condition that never fires is a liability, not a quiet success

Rule codeSevKindOwnerDedupe key templateThrottle FiresEmitsActedModeHealth
ledger.negative_balanceP1threshold13ledger:neg:{sku_id}:{location_code}:{stock_type}1h1,2846358live v4healthy
audit.chain_brokenP1event14audit:chain:{break_at_id}0000live v1never fired · correct, but untested
classifier.shadow_leakP1schedule14clf:leak:{version_id}0000live v2never fired · verified by fault injection 18-07
rfid.gate_absentP1absence12rfid:device:{device_id}30m1466live v3healthy
ledger.composite_breachP1schedule03clf:composite:{sku_id}6h422live v1healthy · L2
classifier.match_rate_lowP2threshold14clf:rate:{business_date}6h1833live v2healthy
classifier.unmatched_spikeP2threshold14clf:unmatched:{topic}2h621412live v3healthy
fanout.dlq_unclaimedP2threshold14fanout:dlq:{subscriber_code}4h3187live v1healthy
fanout.backlog_ageP2threshold14fanout:backlog:{subscriber_code}1h2255live v1healthy
ingest.p95_over_budgetP2threshold14ingest:p95:{topic}1h2044118muted 1d44% dismissed · needs a tighter condition
ingest.topic_absentP2absence14ingest:absent:{source_code}:{topic}2h1166live v2healthy
ratelimit.sustained_holdP2threshold14rl:hold:{bucket_code}30m481211live v1healthy
security.hmac_failuresP2threshold14sec:hmac:{source_code}15m644live v1healthy
ledger.cache_unreconciledP3schedule02bal:unrec:{business_date}24h776live v1healthy · L8
classifier.dead_ruleP3schedule14clf:dead:{rule_code}:{business_date}7d2466live v2healthy
ingest.derived_keyP3threshold14ingest:derivedkey:{source_code}:{business_date}24h1800shadow 4/7evaluating, delivering nothing
ratelimit.budget_overrunP3schedule14rl:budget:{bucket_code}:{month}24h300shadow 2/7gate-bound to GATE-8
ops2.rto_unscannedP2event08ops2:rto:{awb}4h000disabledtopic not emittable upstream · L18
grn.qc_fail_rateP2threshold06grn:qcfail:{supplier_id}12h922muted 1dmuted by khaled 28-07
51 conditions · 19 shownThree P1 conditions have never fired. Two were verified by deliberate fault injection; audit.chain_broken has not been, which is recorded here rather than assumed away.
Loading

The three panes keep their widths, so the editor does not jump when the condition lands.

Empty and zero-filter
No conditions yet.

i3 ships 51 seeded conditions, so an empty list means the seed did not run. Zero-filter names the chips: P1 Fanout together hide all 51.

Error and permission
Condition failed to compile.

Draft v5 references inv.sellable_pct, which is not a metric. The live v4 keeps evaluating: a broken draft never displaces a working condition. A viewer sees every number and no editable field.

Maintenance

During the nightly chain verify the engine keeps evaluating and queues emissions. It does not skip ticks, because a gap in alert_evaluations is indistinguishable from a condition that did not fire.

Gate-blocked

Six conditions are bound to a phase gate and stay in shadow until it has held 7 days. They evaluate, they record, they deliver nothing. The card shows the gate name rather than hiding the rule.

Keyboard
J K moveN newE edit B backtestM muteT test fire ⌘S save draft⌘K palette

The rule this page enforces

R-14.33 and R-14.35

One condition, one dedupe key, one throttle bucket, one alert. The engine renders both; no caller may supply either. Module 01 then only needs one constraint:

UNIQUE (rule_id, dedupe_key, throttle_bucket)

Replaying the same source event 100 times changes neither the alert count nor the Slack count, because both derive from the same tuple. Dedupe is a constraint, not a convention, which is the structural fix for the duplicate-notification failure mode i2 kept hitting in n8n.

And a condition may not go live until its 30-day backtest shows between 1 and 200 alerts a human would actually have seen. Zero is untestable. Over 200 is noise before it ships.

Notifications Classifier
Connected to Notifications Classifier rules Classifier stats Event inbox Sibling events Webhook outbox Rate limits Audit log Drift Stock health RFID Settings