# notify — Changelog

## v1.0.0 — 2026-06-23

Initial release. Suite glue — pipe jfind/v1 findings in, dispatch them
to configured channels. Tool #2 in the post-fleet-review productivity
queue (after cmdb).

### What it does

Reads jfind/v1 (single finding / bundle / JSON array / NDJSON stream)
from stdin or `--input <path>`. For each finding, walks the configured
channel list and dispatches to every channel whose per-channel filter
matches. Channel kinds: `slack`, `teams`, `webhook`, `file`, `stdout`.

Mail delivery is explicitly **not** in this tool — pipe to `mailforge`
instead of teaching notify SMTP.

### Subcommands

```
notify route   --input <PATH>           # or read stdin
              [--only <channel>]
              [--dry-run]
              [-F human|json]
notify test <channel>                   # synthetic finding → one channel
notify channels list [-F human|json]
notify config show | validate | path | init [--force]
```

### Config (`$XDG_CONFIG_HOME/cli-johlem/notify.toml`)

```toml
[defaults]
timeout_seconds = 10

[[channel]]
name = "secops-slack"
kind = "slack"
webhook = "https://hooks.slack.com/services/AAAA/BBBB/CCCC"

[channel.filter]
severity_min = "medium"
categories = ["credential-exposure", "ict-incident", "vulnerability"]

[channel.template]
title = "[{{ severity | upper }}] {{ tool }} — {{ title }}"
body  = "{{ description }}\nAsset: {{ asset.identifier }}\nEngagement: {{ engagement_id }}"
```

`notify config init` writes a starter file with documented examples.

### Per-channel filters (AND semantics)

| Field | Type | Description |
|---|---|---|
| `severity_min` | string | one of info / low / medium / high / critical |
| `categories` | string[] | jfind categories the finding must be in |
| `tools` | string[] | tool names the finding must come from |
| `frameworks` | string[] | at least one of the finding's controls must use this framework |
| `mitre_techniques` | string[] | at least one technique must be in the set |

Empty filter = match everything (default for `stdout` and `file`).

### Templating

A minimal `{{ key | filter }}` interpolator — no Tera, no handlebars
dep. Supports `tool` / `title` / `description` / `severity` /
`category` / `asset.identifier` / `engagement_id` / a few more. Filters:
`upper`, `lower`. Unknown keys pass through unchanged.

### Exit codes (suite standard)

| Code | When |
|---|---|
| 0 | empty input or all channels filtered out (no-op) |
| 1 | at least one channel actually delivered a finding |
| 2 | usage error (clap) |
| 3 | delivery failed for at least one channel / bad config / bad input / unknown channel |

### Slack payload shape (pinned by unit test)

```json
{
  "text": "[HIGH] credsweep — Leaked AWS key",
  "attachments": [{
    "color": "#E85A1A",
    "title": "[HIGH] credsweep — Leaked AWS key",
    "text": "<finding.description>",
    "fields": [
      { "title": "category",   "value": "credential-exposure", "short": true },
      { "title": "tool",       "value": "credsweep",           "short": true },
      { "title": "asset",      "value": "core-banking",        "short": false },
      { "title": "engagement", "value": "ENG-X",               "short": true }
    ],
    "footer": "cli.johlem.net / notify",
    "ts": <epoch>
  }]
}
```

Severity → colour mapping uses the johlem-brand orange ramp.

### Teams payload shape

Standard Microsoft Office365 `MessageCard` envelope with `themeColor`
matching the severity colour (minus `#`), title, text, and a facts
section for category / tool / asset / engagement.

### Suite interop

| Producer | → | notify | → | Consumer |
|---|---|---|---|---|
| credsweep / urlrecon / phishprobe / specter / avwatch / socialscope / inciclass / roigen / tiberscope / cmdb | | | | Slack / Teams / file / generic webhook |

Pipeline example:

```
credsweep --output jfind --path repo/ \
  | notify route --only secops-slack
```

### Build

- Library crate (`notify_cli`) + thin CLI binary (`notify`).
- 20 unit + 14 integration tests, all green.
- Zero compiler warnings.
- TLS via `rustls` (matches the rest of the suite).
- Suite-standard exit codes (0 / 1 / 2 / 3).
- NixOS-only platform gate in `install.sh`.

### Deferred to v1.1

- Live wiremock integration test (had a tokio-runtime-versus-blocking-
  subprocess interaction in v1.0; pinning the slack payload shape
  via unit test for now).
- Retry queue with exponential backoff for failed webhook deliveries.
- HMAC-signed outgoing webhooks (`X-Signature: hmac-sha256=…`).
- Per-channel deduplication window (drop if same finding_id was seen
  in the last N minutes).
- Discord / Telegram / Mattermost channels.
- Mail destination via mailforge integration (currently: pipe yourself).
- `notify ratchet` — promote a finding's severity if it persists
  across N runs.
