Notifications Rules

Imposed rule: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.

Rules decide which platform events deserve a notification, how aggressively they should be throttled, and which channels/actions should run. They are tenant-scoped contracts that guarantee deterministic routing across Notify.Worker replicas.


1. Rule lifecycle

  1. Authoring. Operators create or update rules through the Notify WebService (POST /rules, PATCH /rules/{id}) or UI. Payloads are normalised to the current NotifyRule schema version.
  2. Evaluation. Notify.Worker evaluates enabled rules per incoming event. Tenancy is enforced first, followed by match filters, VEX gates, throttles, and digest handling.
  3. Delivery. Matching actions are enqueued with an idempotency key to prevent storm loops. Throttle rejections and digest coalescing are recorded in the delivery ledger.
  4. Audit. Every change carries createdBy/updatedBy plus timestamps; the delivery ledger references ruleId/actionId for traceability.

2. Rule schema reference

FieldTypeNotes
ruleIdstringStable identifier; clients may provide UUID/slug.
tenantIdstringMust match the tenant header supplied when the rule is created.
namestringDisplay label shown in UI and audits.
descriptionstring?Optional operator-facing note.
enabledboolDisabled rules remain stored but skipped during evaluation.
labelsmap<string,string>Sorted, trimmed key/value tags supporting filtering.
metadatamap<string,string>Reserved for automation; stored verbatim (sorted).
matchNotifyRuleMatchDeclarative filters applied before actions execute.
actions[]NotifyRuleActionOrdered set of channel dispatchers; minimum one.
createdBy/createdAtstring?, instantPopulated automatically when omitted.
updatedBy/updatedAtstring?, instantDefaults to creation values when unspecified.
schemaVersionstringAuto-upgraded during persistence; use for migrations.

Rules are immutable snapshots; updates produce a full document write so workers observing change streams can refresh caches deterministically.


3. Match filters

NotifyRuleMatch narrows which events trigger the rule. All string collections are trimmed, deduplicated, and sorted to guarantee deterministic evaluation.

FieldTypeBehaviour
eventKinds[]stringLower-cased; supports any canonical Notify event (scanner.report.ready, scheduler.rescan.delta, zastava.admission, etc.). Empty list matches all kinds.
namespaces[]stringExact match against event.scope.namespace. Supports glob-style filters via upstream enrichment (planned).
repositories[]stringMatches event.scope.repo.
digests[]stringLower-cased; matches event.scope.digest.
labels[]stringMatches event attributes or delta labels (kev, critical, license, …).
componentPurls[]stringMatches component identifiers inside the event payload when provided.
minSeveritystring?Lower-cased severity gate (e.g., medium, high, critical). Evaluated on new findings inside event deltas; events lacking severity bypass this gate unless set.
verdicts[]stringAccepts scan/report verdicts (fail, warn, block, escalate, deny).
kevOnlybool?When true, only KEV-tagged findings fire.
vexobjectAdditional gating aligned with VEX consensus; see below.

3.1 VEX gates

NotifyRuleMatchVex offers fine-grained control when VEX findings accompany events:

FieldDefaultEffect
includeAcceptedJustificationstrueInclude findings marked not_affected/acceptable in consensus.
includeRejectedJustificationsfalseSurface findings the consensus rejected.
includeUnknownJustificationsfalseAllow findings without explicit justification.
justificationKinds[][]Optional allow-list of justification codes (e.g., exploit_observed, component_not_present).

If the VEX block filters out every applicable finding, the rule is treated as a non-match and no actions run.


4. Actions, throttles, and digests

Each rule requires at least one action. Actions are deduplicated and sorted by actionId, so prefer deterministic identifiers.

FieldTypeNotes
actionIdstringStable identifier unique within the rule.
channelstringReference to a channel (channelId) configured in /channels.
templatestring?Template key to use for rendering; falls back to channel default when omitted.
digeststring?Digest window key (instant, 5m, 15m, 1h, 1d). instant bypasses coalescing.
throttleISO8601 duration?Optional throttle TTL (PT300S, PT1H). Prevents duplicate deliveries when the same idempotency hash appears before expiry.
localestring?BCP-47 tag (stored lower-case). Template lookup falls back to channel locale then en-us.
enabledboolDisabled actions skip rendering but remain stored.
metadatamap<string,string>Connector-specific hints (priority, layout, etc.).

4.0 Attestation lifecycle templates

Rules targeting attestation/signing events (attestor.verification.failed, attestor.attestation.expiring, authority.keys.revoked, attestor.transparency.anomaly) must reference the dedicated template keys documented in notifications/templates.md §7 so payloads remain deterministic across channels and Offline Kits:

Event kindRequired template keyNotes
attestor.verification.failedtmpl-attest-verify-failInclude failure code, Rekor UUID/index, last good attestation link.
attestor.attestation.expiringtmpl-attest-expiry-warningSurface issued/expires timestamps, time remaining, renewal instructions.
authority.keys.revoked / authority.keys.rotatedtmpl-attest-key-rotationList rotation batch ID, impacted services, remediation steps.
attestor.transparency.anomalytmpl-attest-transparency-anomalyHighlight Rekor/witness metadata and anomaly classification.

Locale-specific variants keep the same template key while varying locale; rule actions shouldn’t create ad-hoc templates for these events.

4.1 Evaluation order

  1. Verify channel exists and is enabled; disabled channels mark the delivery as Dropped.
  2. Apply throttle idempotency key: hash(ruleId|actionId|event.kind|scope.digest|delta.hash|dayBucket). Hits are logged as Throttled.
  3. If the action defines a digest window other than instant, append the event to the open window and defer delivery until flush.
  4. When delivery proceeds, the renderer resolves the template, locale, and metadata before invoking the connector.

5. Example rule payload

{
  "ruleId": "rule-critical-soc",
  "tenantId": "tenant-dev",
  "name": "Critical scanner verdicts",
  "description": "Route KEV-tagged critical findings to SOC Slack with zero delay.",
  "enabled": true,
  "match": {
    "eventKinds": ["scanner.report.ready"],
    "labels": ["kev", "critical"],
    "minSeverity": "critical",
    "verdicts": ["fail", "block"],
    "kevOnly": true
  },
  "actions": [
    {
      "actionId": "act-slack-critical",
      "channel": "chn-slack-soc",
      "template": "tmpl-critical",
      "digest": "instant",
      "throttle": "PT300S",
      "locale": "en-us",
      "metadata": {
        "priority": "p1"
      }
    }
  ],
  "labels": {
    "owner": "soc"
  },
  "metadata": {
    "revision": "12"
  }
}

Dry-run calls (POST /rules/{id}/test) accept the same structure along with a sample Notify event payload to exercise match logic without invoking connectors.


6. Operational guidance

  • Keep rule scopes narrow (namespace/repository) before relying on severity gates; this minimises noise and improves digest summarisation.
  • Always configure a throttle window for instant actions to protect against repeated upstream retries.
  • Use rule labels to organise dashboards and access control (e.g., owner:soc, env:prod).
  • Prefer tenant-specific rule IDs so Offline Kit exports remain deterministic across environments.
  • If a rule depends on derived metadata (e.g., policy verdict tags), list those dependencies in the rule description for audit readiness.

Imposed rule reminder: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.