Files
worldmonitor/docs/api-notifications.mdx
Elie Habib 7f83e1e0c3 chore: remove dormant proactive-intelligence agent (superseded by digest) (#3325)
* chore: remove dormant proactive-intelligence agent (superseded by digest)

PR #2889 merged a Phase 4 "Proactive Intelligence Agent" in 2026-04 with
588 lines of code and a PR body explicitly requiring a 6h Railway cron
service. That service was never provisioned — no Dockerfile, no Railway
entry, no health-registry key, all 7 test-plan checkboxes unchecked.

In the meantime the daily Intelligence Brief shipped via
scripts/seed-digest-notifications.mjs (PR #3321 and earlier), covering
the same "personalized editorial brief across all channels" use-case
at a different cadence (30m rather than 6h). The proactive agent's
landscape-diff trigger was speculative; the digest is the shipped
equivalent.

This PR retires the dormant code and scrubs the aspirational
"post-launch classifier" references that docs + comments have been
quietly carrying:

- Deleted scripts/proactive-intelligence.mjs (588 lines).
- scripts/_energy-disruption-registry.mjs, scripts/seed-fuel-shortages.mjs,
  scripts/_fuel-shortage-registry.mjs, src/shared/shortage-evidence.ts:
  dropped "proactive-intelligence.mjs will extend this registry /
  classifier output" comments. Registries are curated-only; no classifier
  exists.
- docs/methodology/disruptions.mdx: replaced "post-launch classifier"
  prose with the accurate "curated-only" description of how the event
  log is maintained.
- docs/api-notifications.mdx: envelope version is shared across **two**
  producers now (notification-relay, seed-digest-notifications), not three.
- scripts/notification-relay.cjs: one cross-producer comment updated.
- proto/worldmonitor/supply_chain/v1/list_energy_disruptions.proto +
  list_fuel_shortages.proto: same aspirational wording scrubbed.
- docs/api/SupplyChainService.openapi.{yaml,json} auto-regenerated via
  `make generate` — text-only description updates, no schema changes.

Net: -626 lines, +36 lines. No runtime behavior change. 6573/6573 unit
tests pass locally.

* fix(proto): scrub stale ListFuelShortages RPC comment (PR #3325 review)

Reviewer caught a stale "classifier-extended post-launch" comment on
the ListFuelShortages RPC method in service.proto that this PR's
initial pass missed — I fixed the message-definition comment in
list_fuel_shortages.proto but not the RPC-method comment in
service.proto, which propagates into the published OpenAPI
operation description.

- proto/worldmonitor/supply_chain/v1/service.proto: rewrite the
  ListFuelShortages RPC comment to match the curated-only framing
  used elsewhere in this PR.
- docs/api/SupplyChainService.openapi.{yaml,json}: auto-regenerated
  via `make generate`. Text-only operation-description update;
  no schema / contract changes.

No runtime impact. Other `classifier` references remaining in the
OpenAPI are legitimate schema field names (classifierVersion,
classifierConfidence) and an unrelated auto-revision-log trigger
enum value, both of which describe real on-row fields that existed
before this cleanup.
2026-04-23 09:15:57 +04:00

116 lines
4.7 KiB
Plaintext

---
title: "Notifications & Integrations"
description: "Notification channels, webhook delivery, and Telegram/Slack/Discord/YouTube integration endpoints."
---
## Notification channels
Users can register multiple delivery channels (webhook, Telegram, Slack, Discord, email) and bind alert rules to them.
### `GET /api/notification-channels`
Lists the caller's registered channels and alert rules.
```json
{
"channels": [
{ "id": "chn_01", "type": "webhook", "url": "https://hooks.example.com/...", "active": true },
{ "id": "chn_02", "type": "telegram", "chatId": "@alerts_xyz", "active": true }
],
"alertRules": [
{ "id": "rul_01", "channelId": "chn_01", "trigger": "brief_ready", "filter": null }
]
}
```
### `POST /api/notification-channels`
Action-dispatched writer. The body's `action` field selects the mutation:
| action | Purpose |
|--------|---------|
| `create-pairing-token` | Mint a one-time pairing token (optional `variant`) for the mobile / Tauri client to bind a push channel. |
| `set-channel` | Register or update a channel. For `webhook` channels the `webhookEnvelope` URL is validated HTTPS-only, must not resolve to a private/loopback address, and is AES-256-GCM encrypted before storage. Optional `email`, `webhookLabel` (truncated to 100 chars). |
| `set-web-push` | Register a browser Web Push subscription for the signed-in user. |
| `delete-channel` | Remove a channel by type (`email`, `webhook`, `telegram`, `web-push`, etc.). |
| `set-alert-rules` | Replace the caller's alert-rules set in one shot. |
| `set-quiet-hours` | Set do-not-disturb windows. |
| `set-digest-settings` | Configure digest cadence and channel routing. |
All actions require Clerk bearer + PRO (`tier >= 1`). Invalid actions return `400 Unknown action`. Requests are forwarded to Convex via `RELAY_SHARED_SECRET`.
## Webhook delivery contract
When an alert fires, registered webhook URLs receive:
- **Method**: `POST`
- **Headers**:
- `Content-Type: application/json`
- `X-WM-Signature: sha256=<HMAC-SHA256(body, channelSecret)>`
- `X-WM-Delivery-Id: <ulid>`
- `X-WM-Event: <event-name>`
- **Body** (envelope v1):
```json
{
"envelope": 1,
"event": "brief_ready",
"deliveryId": "01HX...",
"occurredAt": "2026-04-19T06:00:00Z",
"data": { "issueDate": "2026-04-19", "magazineUrl": "..." }
}
```
Signature verification: `hmac_sha256(rawBody, channelSecret) == X-WM-Signature[7:]`.
<Warning>
The envelope version is **shared across two producers** (`notification-relay`, `seed-digest-notifications`). Bumping it requires coordinated updates.
</Warning>
### `POST /api/notify`
Internal ingestion endpoint called by Railway producers to enqueue a notification. Requires `RELAY_SHARED_SECRET`. Not a public API.
## Telegram
### `GET /api/telegram-feed?userId=...`
Returns the pre-rendered brief feed for a given Telegram-linked user. Used by the Telegram mini-app.
## YouTube
### `GET /api/youtube/embed?videoId=...`
SSR'd YouTube embed iframe with CSP-compatible wrapping. Used to bypass WKWebView autoplay restrictions on the desktop app.
### `GET /api/youtube/live?channel=<handle>` or `?videoId=<11-char-id>`
Returns live-stream metadata for a YouTube channel (`channel` — handle with or without `@` prefix) or a specific video (`videoId` — 11-char YouTube id). At least one of the two params is required; returns `400 Missing channel or videoId parameter` otherwise. Response cached 10 min for channel lookups, 1 hour for videoId lookups.
Proxies to the Railway relay first (residential proxy for YouTube scraping). On relay failure, falls back to YouTube oEmbed (for `videoId`) or direct channel scraping — both are unreliable from datacenter IPs.
## Slack integration
### `POST /api/slack/oauth/start`
Authenticated (Clerk JWT + PRO). Body is empty. Server generates a one-time CSRF state token, stores the caller's userId in Upstash keyed by that state (10-min TTL), and returns the Slack authorize URL for the frontend to open in a popup.
```json
{ "oauthUrl": "https://slack.com/oauth/v2/authorize?client_id=...&scope=incoming-webhook&..." }
```
Errors: 401 (missing/invalid JWT), 403 `pro_required`, 503 (OAuth not configured or Upstash unavailable).
### `GET /api/slack/oauth/callback`
Unauthenticated — the popup lands here after Slack redirects. Validates the state token, exchanges `code` for an incoming-webhook URL, AES-256-GCM encrypts the webhook, and stores it in Convex. Returns a tiny HTML page that `postMessage`s the opener and closes.
## Discord integration
### `POST /api/discord/oauth/start`
Authenticated (Clerk JWT + PRO). Same shape as the Slack start route — returns `{ oauthUrl }` for a popup.
### `GET /api/discord/oauth/callback`
Unauthenticated. Exchanges `code`, stores the guild webhook, and `postMessage`s the opener.