Add a notifier
A notifier delivers one alert on one channel. Like collectors, adding one does not touch the loop.
The contract
Section titled “The contract”interface Notifier { readonly channel: ChannelType notify(notification: Notification): Promise<void>}A Notification carries the watch id and name, the owner, the channel, an
optional destination override, the title, the summary, the evidence (quote, URL,
date), the confidence and the tier.
Failure semantics — read this first
Section titled “Failure semantics — read this first”-
Add the channel type to the channel list in the domain models. It is a closed set, so existing watches keep validating.
-
Write the adapter in
src/adapters/notifiers/:export class MyNotifier implements Notifier {readonly channel: ChannelType = 'mychannel'constructor(private readonly options: MyNotifierOptions) {}async notify(notification: Notification): Promise<void> {const response = await this.options.fetch(this.endpoint, {method: 'POST',headers: { 'content-type': 'application/json' },body: JSON.stringify(formatMyMessage(notification)),})if (!response.ok) {// Status only — never the token, the URL or the payload.throw new Error(`MyChannel delivery failed (${response.status})`)}}} -
Export a pure formatting function (
formatMyMessage) separately from the transport. It makes the interesting part testable without mocking anything. -
Honour the tier. A
weakalert must be visibly different from astrongone — the existing channels prefix “Possible update (unconfirmed)”. A reader must never mistake an unconfirmed alert for a confirmed one. -
Honour
chatId, the per-watch destination override, if your channel has any concept of a destination. -
Register it in
entrypoints/wiring.ts, with a console fallback when its configuration is absent — that is the established pattern, and it keeps development working with no credentials. -
Add configuration through the central config module. Never read the environment directly from an adapter.
-
Document it in Channels and the watch schema.
Tests to write
Section titled “Tests to write”| Test | Why |
|---|---|
| Formats a strong alert correctly | The message is the product |
| Formats a weak alert differently | The tier must be unmistakable |
| Includes the evidence quote and URL when present, omits them when not | Optional fields |
Uses chatId when given, the default otherwise |
Routing |
| Throws on a non-2xx response | Retry depends on it |
| Never includes the token in the error | Credential safety |
Inject a fake transport. No test may touch the network.
Security notes
Section titled “Security notes”- Credentials arrive through configuration, never from a watch definition.
- Never log a token, and never include one in an error message or a URL that might be logged.
- Alert content includes collected page text. If your channel posts somewhere shared, say so in the documentation so people know what they are exposing.