feat(docs): Add comprehensive documentation for Vexer, Vulnerability Explorer, and Zastava modules
- Introduced AGENTS.md, README.md, TASKS.md, and implementation_plan.md for Vexer, detailing mission, responsibilities, key components, and operational notes. - Established similar documentation structure for Vulnerability Explorer and Zastava modules, including their respective workflows, integrations, and observability notes. - Created risk scoring profiles documentation outlining the core workflow, factor model, governance, and deliverables. - Ensured all modules adhere to the Aggregation-Only Contract and maintain determinism and provenance in outputs.
This commit is contained in:
		| @@ -1,92 +1,92 @@ | ||||
| # Notifications Digests | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Digests coalesce multiple matching events into a single notification when rules request batched delivery. They protect responders from alert storms while preserving a deterministic record of every input. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Digest lifecycle | ||||
|  | ||||
| 1. **Window selection.** Rule actions opt into a digest cadence by setting `actions[].digest` (`instant`, `5m`, `15m`, `1h`, `1d`). `instant` skips digest logic entirely. | ||||
| 2. **Aggregation.** When an event matches, the worker appends it to the open digest window (`tenantId + actionId + window`). Events include the canonical scope, delta counts, and references. | ||||
| 3. **Flush.** When the window expires or hits the worker’s safety cap (configurable), the worker renders a digest template and emits a single delivery with status `Digested`. | ||||
| 4. **Audit.** The delivery ledger links back to the digest document so operators can inspect individual items and the aggregated summary. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Storage model | ||||
|  | ||||
| Digest state lives in Mongo (`digests` collection) and mirrors the schema described in [ARCHITECTURE_NOTIFY.md](../ARCHITECTURE_NOTIFY.md#7-data-model-mongo): | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "_id": "tenant-dev:act-email-compliance:1h", | ||||
|   "tenantId": "tenant-dev", | ||||
|   "actionKey": "act-email-compliance", | ||||
|   "window": "1h", | ||||
|   "openedAt": "2025-10-24T08:00:00Z", | ||||
|   "status": "open", | ||||
|   "items": [ | ||||
|     { | ||||
|       "eventId": "00000000-0000-0000-0000-000000000001", | ||||
|       "scope": { | ||||
|         "namespace": "prod-payments", | ||||
|         "repo": "ghcr.io/acme/api", | ||||
|         "digest": "sha256:…" | ||||
|       }, | ||||
|       "delta": { | ||||
|         "newCritical": 1, | ||||
|         "kev": 1 | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| - `status` reflects whether the window is currently collecting (`open`) or has been completed (`closed`). Future revisions may introduce `flushing` for in-progress operations. | ||||
| - `items[].delta` captures aggregated counts for reporting (e.g., new critical findings, KEV, quieted). | ||||
| - Workers use optimistic concurrency on the document ID to avoid duplicate flushes across replicas. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. Rendering and templates | ||||
|  | ||||
| - Digest deliveries use the same template engine as instant notifications. Templates receive an additional `digest` object with `window`, `openedAt`, `itemCount`, and `items` (findings grouped by namespace/repository when available). | ||||
| - Provide digest-specific templates (e.g., `tmpl-digest-hourly`) so the body can enumerate top offenders, summarise totals, and link to detailed dashboards. | ||||
| - When no template is specified, Notify falls back to channel defaults that emphasise summary counts and redirect to Console for detail. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. API surface | ||||
|  | ||||
| | Endpoint | Description | Notes | | ||||
| |----------|-------------|-------| | ||||
| | `POST /digests` | Issues administrative commands (e.g., force flush, reopen) for a specific action/window. | Request body specifies the command target; requires `notify.admin`. | | ||||
| | `GET /digests/{actionKey}` | Returns the currently open window (if any) for the referenced action. | Supports operators/CLI inspecting pending digests; requires `notify.read`. | | ||||
| | `DELETE /digests/{actionKey}` | Drops the open window without notifying (emergency stop). | Emits an audit record; use sparingly. | | ||||
|  | ||||
| All routes honour the tenant header and reuse the standard Notify rate limits. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Worker behaviour and safety nets | ||||
|  | ||||
| - **Idempotency.** Flush operations generate a deterministic digest delivery ID (`digest:<tenant>:<actionId>:<window>:<openedAt>`). Retries reuse the same ID. | ||||
| - **Throttles.** Digest generation respects action throttles; setting an aggressive throttle together with a digest window may result in deliberate skips (logged as `Throttled` in the delivery ledger). | ||||
| - **Quiet hours.** Future sprint work (`NOTIFY-SVC-39-004`) integrates quiet-hour calendars. When enabled, flush timers pause during quiet windows and resume afterwards. | ||||
| - **Back-pressure.** When the window reaches the configured item cap before the timer, the worker flushes early and starts a new window immediately. | ||||
| - **Crash resilience.** Workers rebuild in-flight windows from Mongo on startup; partially flushed windows remain closed after success or reopened if the flush fails. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Operator guidance | ||||
|  | ||||
| - Choose hourly digests for high-volume compliance events; daily digests suit executive reporting. | ||||
| - Pair digests with incident-focused instant rules so critical items surface immediately while less urgent noise is summarised. | ||||
| - Monitor `/stats` output for `openDigestCount` to ensure windows are flushing; spikes may indicate downstream connector failures. | ||||
| - When testing new digest templates, open a small (`5m`) window, trigger sample events, then call `POST /digests/{actionId}/flush` to validate rendering before moving to longer cadences. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
| # Notifications Digests | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Digests coalesce multiple matching events into a single notification when rules request batched delivery. They protect responders from alert storms while preserving a deterministic record of every input. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Digest lifecycle | ||||
|  | ||||
| 1. **Window selection.** Rule actions opt into a digest cadence by setting `actions[].digest` (`instant`, `5m`, `15m`, `1h`, `1d`). `instant` skips digest logic entirely. | ||||
| 2. **Aggregation.** When an event matches, the worker appends it to the open digest window (`tenantId + actionId + window`). Events include the canonical scope, delta counts, and references. | ||||
| 3. **Flush.** When the window expires or hits the worker’s safety cap (configurable), the worker renders a digest template and emits a single delivery with status `Digested`. | ||||
| 4. **Audit.** The delivery ledger links back to the digest document so operators can inspect individual items and the aggregated summary. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Storage model | ||||
|  | ||||
| Digest state lives in Mongo (`digests` collection) and mirrors the schema described in [modules/notify/architecture.md](../modules/notify/architecture.md#7-data-model-mongo): | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "_id": "tenant-dev:act-email-compliance:1h", | ||||
|   "tenantId": "tenant-dev", | ||||
|   "actionKey": "act-email-compliance", | ||||
|   "window": "1h", | ||||
|   "openedAt": "2025-10-24T08:00:00Z", | ||||
|   "status": "open", | ||||
|   "items": [ | ||||
|     { | ||||
|       "eventId": "00000000-0000-0000-0000-000000000001", | ||||
|       "scope": { | ||||
|         "namespace": "prod-payments", | ||||
|         "repo": "ghcr.io/acme/api", | ||||
|         "digest": "sha256:…" | ||||
|       }, | ||||
|       "delta": { | ||||
|         "newCritical": 1, | ||||
|         "kev": 1 | ||||
|       } | ||||
|     } | ||||
|   ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| - `status` reflects whether the window is currently collecting (`open`) or has been completed (`closed`). Future revisions may introduce `flushing` for in-progress operations. | ||||
| - `items[].delta` captures aggregated counts for reporting (e.g., new critical findings, KEV, quieted). | ||||
| - Workers use optimistic concurrency on the document ID to avoid duplicate flushes across replicas. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. Rendering and templates | ||||
|  | ||||
| - Digest deliveries use the same template engine as instant notifications. Templates receive an additional `digest` object with `window`, `openedAt`, `itemCount`, and `items` (findings grouped by namespace/repository when available). | ||||
| - Provide digest-specific templates (e.g., `tmpl-digest-hourly`) so the body can enumerate top offenders, summarise totals, and link to detailed dashboards. | ||||
| - When no template is specified, Notify falls back to channel defaults that emphasise summary counts and redirect to Console for detail. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. API surface | ||||
|  | ||||
| | Endpoint | Description | Notes | | ||||
| |----------|-------------|-------| | ||||
| | `POST /digests` | Issues administrative commands (e.g., force flush, reopen) for a specific action/window. | Request body specifies the command target; requires `notify.admin`. | | ||||
| | `GET /digests/{actionKey}` | Returns the currently open window (if any) for the referenced action. | Supports operators/CLI inspecting pending digests; requires `notify.read`. | | ||||
| | `DELETE /digests/{actionKey}` | Drops the open window without notifying (emergency stop). | Emits an audit record; use sparingly. | | ||||
|  | ||||
| All routes honour the tenant header and reuse the standard Notify rate limits. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Worker behaviour and safety nets | ||||
|  | ||||
| - **Idempotency.** Flush operations generate a deterministic digest delivery ID (`digest:<tenant>:<actionId>:<window>:<openedAt>`). Retries reuse the same ID. | ||||
| - **Throttles.** Digest generation respects action throttles; setting an aggressive throttle together with a digest window may result in deliberate skips (logged as `Throttled` in the delivery ledger). | ||||
| - **Quiet hours.** Future sprint work (`NOTIFY-SVC-39-004`) integrates quiet-hour calendars. When enabled, flush timers pause during quiet windows and resume afterwards. | ||||
| - **Back-pressure.** When the window reaches the configured item cap before the timer, the worker flushes early and starts a new window immediately. | ||||
| - **Crash resilience.** Workers rebuild in-flight windows from Mongo on startup; partially flushed windows remain closed after success or reopened if the flush fails. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Operator guidance | ||||
|  | ||||
| - Choose hourly digests for high-volume compliance events; daily digests suit executive reporting. | ||||
| - Pair digests with incident-focused instant rules so critical items surface immediately while less urgent noise is summarised. | ||||
| - Monitor `/stats` output for `openDigestCount` to ensure windows are flushing; spikes may indicate downstream connector failures. | ||||
| - When testing new digest templates, open a small (`5m`) window, trigger sample events, then call `POST /digests/{actionId}/flush` to validate rendering before moving to longer cadences. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
|   | ||||
| @@ -1,76 +1,76 @@ | ||||
| # Notifications Overview | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Notifications Studio turns raw platform events into concise, tenant-scoped alerts that reach the right responders without overwhelming them. The service is sovereign/offline-first, follows the Aggregation-Only Contract (AOC), and produces deterministic outputs so the same configuration yields identical deliveries across environments. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Mission & value | ||||
|  | ||||
| - **Reduce noise.** Only materially new or high-impact changes reach chat, email, or webhooks thanks to rule filters, throttles, and digest windows. | ||||
| - **Explainable results.** Every delivery is traceable back to a rule, action, and event payload stored in the delivery ledger; operators can audit what fired and why. | ||||
| - **Safe by default.** Secrets remain in external stores, templates are sandboxed, quiet hours and throttles prevent storms, and idempotency guarantees protect downstream systems. | ||||
| - **Offline-aligned.** All configuration, templates, and plug-ins ship with Offline Kits; no external SaaS is required to send notifications. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Core capabilities | ||||
|  | ||||
| | Capability | What it does | Key docs | | ||||
| |------------|--------------|----------| | ||||
| | Rules engine | Declarative matchers for event kinds, severities, namespaces, VEX context, KEV flags, and more. | [`notifications/rules.md`](rules.md) | | ||||
| | Channel catalog | Slack, Teams, Email, Webhook connectors loaded via restart-time plug-ins; metadata stored without secrets. | [`notifications/architecture.md`](architecture.md) | | ||||
| | Templates | Locale-aware, deterministic rendering via safe helpers; channel defaults plus tenant-specific overrides. | [`notifications/templates.md`](templates.md) | | ||||
| | Digests | Coalesce bursts into periodic summaries with deterministic IDs and audit trails. | [`notifications/digests.md`](digests.md) | | ||||
| | Delivery ledger | Tracks rendered payload hashes, attempts, throttles, and outcomes for every action. | [`ARCHITECTURE_NOTIFY.md`](../ARCHITECTURE_NOTIFY.md#7-data-model-mongo) | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. How it fits into Stella Ops | ||||
|  | ||||
| 1. **Producers emit events.** Scanner, Scheduler, VEX Lens, Attestor, and Zastava publish canonical envelopes (`NotifyEvent`) onto the internal bus. | ||||
| 2. **Notify.Worker evaluates rules.** For each tenant, the worker applies match filters, VEX gates, throttles, and digest policies before rendering the action. | ||||
| 3. **Connectors deliver.** Channel plug-ins send the rendered payload to Slack/Teams/Email/Webhook targets and report back attempts and outcomes. | ||||
| 4. **Consumers investigate.** Operators pivot from message links into Console dashboards, SBOM views, or policy overlays with correlation IDs preserved. | ||||
|  | ||||
| The Notify WebService fronts worker state with REST APIs used by the UI and CLI. Tenants authenticate via StellaOps Authority scopes `notify.read` and `notify.admin`. All operations require the tenant header (`X-StellaOps-Tenant`) to preserve sovereignty boundaries. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Operating model | ||||
|  | ||||
| | Area | Guidance | | ||||
| |------|----------| | ||||
| | **Tenancy** | Each rule, channel, template, and delivery belongs to exactly one tenant. Cross-tenant sharing is intentionally unsupported. | | ||||
| | **Determinism** | Configuration persistence normalises strings and sorts collections. Template rendering produces identical `bodyHash` values when inputs match. | | ||||
| | **Scaling** | Workers scale horizontally; per-tenant rule snapshots are cached and refreshed from Mongo change streams. Redis (or equivalent) guards throttles and locks. | | ||||
| | **Offline** | Offline Kits include plug-ins, default templates, and seed rules. Operators can edit YAML/JSON manifests before air-gapped deployment. | | ||||
| | **Security** | Channel secrets use indirection (`secretRef`), Authority-protected OAuth clients secure API access, and delivery payloads are redacted before storage where required. | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Getting started (first 30 minutes) | ||||
|  | ||||
| | Step | Goal | Reference | | ||||
| |------|------|-----------| | ||||
| | 1 | Deploy Notify WebService + Worker with Mongo and Redis | [`ARCHITECTURE_NOTIFY.md`](../ARCHITECTURE_NOTIFY.md#1-runtime-shape--projects) | | ||||
| | 2 | Register OAuth clients/scopes in Authority | [`etc/authority.yaml.sample`](../etc/authority.yaml.sample) | | ||||
| | 3 | Install channel plug-ins and capture secret references | [`plugins/notify`](../../plugins) | | ||||
| | 4 | Create a tenant rule and test preview | [`POST /channels/{id}/test`](../ARCHITECTURE_NOTIFY.md#8-external-apis-webservice) | | ||||
| | 5 | Inspect deliveries and digests | `/api/v1/notify/deliveries`, `/api/v1/notify/digests` | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Alignment with implementation work | ||||
|  | ||||
| | Backlog item | Impact on docs | Status | | ||||
| |--------------|----------------|--------| | ||||
| | `NOTIFY-SVC-38-001..004` | Foundational correlation, throttling, simulation hooks. | **In progress** – align behaviour once services publish beta APIs. | | ||||
| | `NOTIFY-SVC-39-001..004` | Adds correlation engine, digest generator, simulation API, quiet hours. | **Pending** – revisit rule/digest sections when these tasks merge. | | ||||
|  | ||||
| Action: coordinate with the Notifications Service Guild when `NOTIFY-SVC-39-001..004` land to validate payload fields, quiet-hours semantics, and any new connector metadata that should be documented here and in the channel-specific guides. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
| # Notifications Overview | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Notifications Studio turns raw platform events into concise, tenant-scoped alerts that reach the right responders without overwhelming them. The service is sovereign/offline-first, follows the Aggregation-Only Contract (AOC), and produces deterministic outputs so the same configuration yields identical deliveries across environments. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Mission & value | ||||
|  | ||||
| - **Reduce noise.** Only materially new or high-impact changes reach chat, email, or webhooks thanks to rule filters, throttles, and digest windows. | ||||
| - **Explainable results.** Every delivery is traceable back to a rule, action, and event payload stored in the delivery ledger; operators can audit what fired and why. | ||||
| - **Safe by default.** Secrets remain in external stores, templates are sandboxed, quiet hours and throttles prevent storms, and idempotency guarantees protect downstream systems. | ||||
| - **Offline-aligned.** All configuration, templates, and plug-ins ship with Offline Kits; no external SaaS is required to send notifications. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Core capabilities | ||||
|  | ||||
| | Capability | What it does | Key docs | | ||||
| |------------|--------------|----------| | ||||
| | Rules engine | Declarative matchers for event kinds, severities, namespaces, VEX context, KEV flags, and more. | [`notifications/rules.md`](rules.md) | | ||||
| | Channel catalog | Slack, Teams, Email, Webhook connectors loaded via restart-time plug-ins; metadata stored without secrets. | [`notifications/architecture.md`](architecture.md) | | ||||
| | Templates | Locale-aware, deterministic rendering via safe helpers; channel defaults plus tenant-specific overrides. | [`notifications/templates.md`](templates.md) | | ||||
| | Digests | Coalesce bursts into periodic summaries with deterministic IDs and audit trails. | [`notifications/digests.md`](digests.md) | | ||||
| | Delivery ledger | Tracks rendered payload hashes, attempts, throttles, and outcomes for every action. | [`modules/notify/architecture.md`](../modules/notify/architecture.md#7-data-model-mongo) | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. How it fits into Stella Ops | ||||
|  | ||||
| 1. **Producers emit events.** Scanner, Scheduler, VEX Lens, Attestor, and Zastava publish canonical envelopes (`NotifyEvent`) onto the internal bus. | ||||
| 2. **Notify.Worker evaluates rules.** For each tenant, the worker applies match filters, VEX gates, throttles, and digest policies before rendering the action. | ||||
| 3. **Connectors deliver.** Channel plug-ins send the rendered payload to Slack/Teams/Email/Webhook targets and report back attempts and outcomes. | ||||
| 4. **Consumers investigate.** Operators pivot from message links into Console dashboards, SBOM views, or policy overlays with correlation IDs preserved. | ||||
|  | ||||
| The Notify WebService fronts worker state with REST APIs used by the UI and CLI. Tenants authenticate via StellaOps Authority scopes `notify.read` and `notify.admin`. All operations require the tenant header (`X-StellaOps-Tenant`) to preserve sovereignty boundaries. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Operating model | ||||
|  | ||||
| | Area | Guidance | | ||||
| |------|----------| | ||||
| | **Tenancy** | Each rule, channel, template, and delivery belongs to exactly one tenant. Cross-tenant sharing is intentionally unsupported. | | ||||
| | **Determinism** | Configuration persistence normalises strings and sorts collections. Template rendering produces identical `bodyHash` values when inputs match. | | ||||
| | **Scaling** | Workers scale horizontally; per-tenant rule snapshots are cached and refreshed from Mongo change streams. Redis (or equivalent) guards throttles and locks. | | ||||
| | **Offline** | Offline Kits include plug-ins, default templates, and seed rules. Operators can edit YAML/JSON manifests before air-gapped deployment. | | ||||
| | **Security** | Channel secrets use indirection (`secretRef`), Authority-protected OAuth clients secure API access, and delivery payloads are redacted before storage where required. | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Getting started (first 30 minutes) | ||||
|  | ||||
| | Step | Goal | Reference | | ||||
| |------|------|-----------| | ||||
| | 1 | Deploy Notify WebService + Worker with Mongo and Redis | [`modules/notify/architecture.md`](../modules/notify/architecture.md#1-runtime-shape--projects) | | ||||
| | 2 | Register OAuth clients/scopes in Authority | [`etc/authority.yaml.sample`](../etc/authority.yaml.sample) | | ||||
| | 3 | Install channel plug-ins and capture secret references | [`plugins/notify`](../../plugins) | | ||||
| | 4 | Create a tenant rule and test preview | [`POST /channels/{id}/test`](../modules/notify/architecture.md#8-external-apis-webservice) | | ||||
| | 5 | Inspect deliveries and digests | `/api/v1/notify/deliveries`, `/api/v1/notify/digests` | | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Alignment with implementation work | ||||
|  | ||||
| | Backlog item | Impact on docs | Status | | ||||
| |--------------|----------------|--------| | ||||
| | `NOTIFY-SVC-38-001..004` | Foundational correlation, throttling, simulation hooks. | **In progress** – align behaviour once services publish beta APIs. | | ||||
| | `NOTIFY-SVC-39-001..004` | Adds correlation engine, digest generator, simulation API, quiet hours. | **Pending** – revisit rule/digest sections when these tasks merge. | | ||||
|  | ||||
| Action: coordinate with the Notifications Service Guild when `NOTIFY-SVC-39-001..004` land to validate payload fields, quiet-hours semantics, and any new connector metadata that should be documented here and in the channel-specific guides. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
|   | ||||
| @@ -1,130 +1,130 @@ | ||||
| # Notifications Templates | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Templates shape the payload rendered for each channel when a rule action fires. They are deterministic, locale-aware artefacts stored alongside rules so Notify.Worker replicas can render identical messages regardless of environment. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Template lifecycle | ||||
|  | ||||
| 1. **Authoring.** Operators create templates via the API (`POST /templates`) or UI. Each template binds to a channel type (`Slack`, `Teams`, `Email`, `Webhook`, `Custom`) and a locale. | ||||
| 2. **Reference.** Rule actions opt in by referencing the template key (`actions[].template`). Channel defaults apply when no template is specified. | ||||
| 3. **Rendering.** During delivery, the worker resolves the template (locale fallbacks included), executes it using the safe Handlebars-style engine, and passes the rendered payload plus metadata to the connector. | ||||
| 4. **Audit.** Rendered payloads stored in the delivery ledger include the `templateId` so operators can trace which text was used. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Template schema reference | ||||
|  | ||||
| | Field | Type | Notes | | ||||
| |-------|------|-------| | ||||
| | `templateId` | string | Stable identifier (UUID/slug). | | ||||
| | `tenantId` | string | Must match the tenant header in API calls. | | ||||
| | `channelType` | [`NotifyChannelType`](../ARCHITECTURE_NOTIFY.md#5-channels--connectors-plug-ins) | Determines connector payload envelope. | | ||||
| | `key` | string | Human-readable key referenced by rules (`tmpl-critical`). | | ||||
| | `locale` | string | BCP-47 tag, stored lower-case (`en-us`, `bg-bg`). | | ||||
| | `body` | string | Template body; rendered strictly without executing arbitrary code. | | ||||
| | `renderMode` | enum | `Markdown`, `Html`, `AdaptiveCard`, `PlainText`, or `Json`. Guides connector sanitisation. | | ||||
| | `format` | enum | `Slack`, `Teams`, `Email`, `Webhook`, or `Json`. Signals delivery payload structure. | | ||||
| | `description` | string? | Optional operator note. | | ||||
| | `metadata` | map<string,string> | Sorted map for automation (layout hints, fallback text). | | ||||
| | `createdBy`/`createdAt` | string?, instant | Auto-populated. | | ||||
| | `updatedBy`/`updatedAt` | string?, instant | Auto-populated. | | ||||
| | `schemaVersion` | string | Auto-upgraded on persistence. | | ||||
|  | ||||
| Templates are normalised: string fields trimmed, locale lower-cased, metadata sorted to preserve determinism. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. Variables, helpers, and context | ||||
|  | ||||
| Templates receive a structured context derived from the Notify event, rule match, and rendering metadata. | ||||
|  | ||||
| | Path | Description | | ||||
| |------|-------------| | ||||
| | `event.*` | Canonical event envelope (`kind`, `tenant`, `ts`, `actor`). | | ||||
| | `event.scope.*` | Namespace, repository, digest, image, component identifiers, labels, attributes. | | ||||
| | `payload.*` | Raw event payload (e.g., `payload.verdict`, `payload.delta.*`, `payload.links.*`). | | ||||
| | `rule.*` | Rule descriptor (`ruleId`, `name`, `labels`, `metadata`). | | ||||
| | `action.*` | Action descriptor (`actionId`, `channel`, `digest`, `throttle`, `metadata`). | | ||||
| | `policy.*` | Policy metadata when supplied (`revisionId`, `name`). | | ||||
| | `topFindings[]` | Top-N findings summarised for convenience (vulnerability ID, severity, reachability). | | ||||
| | `digest.*` | When rendering digest flushes: `window`, `openedAt`, `itemCount`. | | ||||
|  | ||||
| Built-in helpers mirror the architecture dossier: | ||||
|  | ||||
| | Helper | Usage | | ||||
| |--------|-------| | ||||
| | `severity_icon severity` | Returns emoji/text badge representing severity. | | ||||
| | `link text url` | Produces channel-safe hyperlink. | | ||||
| | `pluralize count "finding"` | Adds plural suffix when `count != 1`. | | ||||
| | `truncate text maxLength` | Cuts strings while preserving determinism. | | ||||
| | `code text` | Formats inline code (Markdown/HTML aware). | | ||||
|  | ||||
| Connectors may expose additional helpers via partials, but must remain deterministic and side-effect free. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Sample templates | ||||
|  | ||||
| ### 4.1 Slack (Markdown + block kit) | ||||
|  | ||||
| ```hbs | ||||
| {{#*inline "findingLine"}} | ||||
| - {{severity_icon severity}} {{vulnId}} ({{severity}}) in `{{component}}` | ||||
| {{/inline}} | ||||
|  | ||||
| *:rotating_light: {{payload.summary.total}} findings {{#if payload.delta.newCritical}}(new critical: {{payload.delta.newCritical}}){{/if}}* | ||||
|  | ||||
| {{#if topFindings.length}} | ||||
| Top findings: | ||||
| {{#each topFindings}}{{> findingLine}}{{/each}} | ||||
| {{/if}} | ||||
|  | ||||
| {{link "Open report in Console" payload.links.ui}} | ||||
| ``` | ||||
|  | ||||
| ### 4.2 Email (HTML + text alternative) | ||||
|  | ||||
| ```hbs | ||||
| <h2>{{payload.verdict}} for {{event.scope.repo}}</h2> | ||||
| <p>{{payload.summary.total}} findings ({{payload.summary.blocked}} blocked, {{payload.summary.warned}} warned)</p> | ||||
| <table> | ||||
|   <thead><tr><th>Finding</th><th>Severity</th><th>Package</th></tr></thead> | ||||
|   <tbody> | ||||
|     {{#each topFindings}} | ||||
|     <tr> | ||||
|       <td>{{this.vulnId}}</td> | ||||
|       <td>{{this.severity}}</td> | ||||
|       <td>{{this.component}}</td> | ||||
|     </tr> | ||||
|     {{/each}} | ||||
|   </tbody> | ||||
| </table> | ||||
| <p>{{link "View full analysis" payload.links.ui}}</p> | ||||
| ``` | ||||
|  | ||||
| When delivering via email, connectors automatically attach a plain-text alternative derived from the rendered content to preserve accessibility. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Preview and validation | ||||
|  | ||||
| - `POST /channels/{id}/test` accepts an optional `templateId` and sample payload to produce a rendered preview without dispatching the event. Results include channel type, target, title/summary, locale, body hash, and connector metadata. | ||||
| - UI previews rely on the same API and highlight connector fallbacks (e.g., Teams adaptive card vs. text fallback). | ||||
| - Offline Kit scenarios can call `/internal/notify/templates/normalize` to ensure bundled templates match the canonical schema before packaging. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Best practices | ||||
|  | ||||
| - Keep channel-specific limits in mind (Slack block/character quotas, Teams adaptive card size, email line length). Lean on digests to summarise long lists. | ||||
| - Provide locale-specific versions for high-volume tenants; Notify selects the closest locale, falling back to `en-us`. | ||||
| - Store connector-specific hints (`metadata.layout`, `metadata.emoji`) in template metadata rather than rules when they affect rendering. | ||||
| - Version template bodies through metadata (e.g., `metadata.revision: "2025-10-28"`) so tenants can track changes over time. | ||||
| - Run test previews whenever introducing new helpers to confirm body hashes remain stable across environments. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
| # Notifications Templates | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Templates shape the payload rendered for each channel when a rule action fires. They are deterministic, locale-aware artefacts stored alongside rules so Notify.Worker replicas can render identical messages regardless of environment. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Template lifecycle | ||||
|  | ||||
| 1. **Authoring.** Operators create templates via the API (`POST /templates`) or UI. Each template binds to a channel type (`Slack`, `Teams`, `Email`, `Webhook`, `Custom`) and a locale. | ||||
| 2. **Reference.** Rule actions opt in by referencing the template key (`actions[].template`). Channel defaults apply when no template is specified. | ||||
| 3. **Rendering.** During delivery, the worker resolves the template (locale fallbacks included), executes it using the safe Handlebars-style engine, and passes the rendered payload plus metadata to the connector. | ||||
| 4. **Audit.** Rendered payloads stored in the delivery ledger include the `templateId` so operators can trace which text was used. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Template schema reference | ||||
|  | ||||
| | Field | Type | Notes | | ||||
| |-------|------|-------| | ||||
| | `templateId` | string | Stable identifier (UUID/slug). | | ||||
| | `tenantId` | string | Must match the tenant header in API calls. | | ||||
| | `channelType` | [`NotifyChannelType`](../modules/notify/architecture.md#5-channels--connectors-plug-ins) | Determines connector payload envelope. | | ||||
| | `key` | string | Human-readable key referenced by rules (`tmpl-critical`). | | ||||
| | `locale` | string | BCP-47 tag, stored lower-case (`en-us`, `bg-bg`). | | ||||
| | `body` | string | Template body; rendered strictly without executing arbitrary code. | | ||||
| | `renderMode` | enum | `Markdown`, `Html`, `AdaptiveCard`, `PlainText`, or `Json`. Guides connector sanitisation. | | ||||
| | `format` | enum | `Slack`, `Teams`, `Email`, `Webhook`, or `Json`. Signals delivery payload structure. | | ||||
| | `description` | string? | Optional operator note. | | ||||
| | `metadata` | map<string,string> | Sorted map for automation (layout hints, fallback text). | | ||||
| | `createdBy`/`createdAt` | string?, instant | Auto-populated. | | ||||
| | `updatedBy`/`updatedAt` | string?, instant | Auto-populated. | | ||||
| | `schemaVersion` | string | Auto-upgraded on persistence. | | ||||
|  | ||||
| Templates are normalised: string fields trimmed, locale lower-cased, metadata sorted to preserve determinism. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. Variables, helpers, and context | ||||
|  | ||||
| Templates receive a structured context derived from the Notify event, rule match, and rendering metadata. | ||||
|  | ||||
| | Path | Description | | ||||
| |------|-------------| | ||||
| | `event.*` | Canonical event envelope (`kind`, `tenant`, `ts`, `actor`). | | ||||
| | `event.scope.*` | Namespace, repository, digest, image, component identifiers, labels, attributes. | | ||||
| | `payload.*` | Raw event payload (e.g., `payload.verdict`, `payload.delta.*`, `payload.links.*`). | | ||||
| | `rule.*` | Rule descriptor (`ruleId`, `name`, `labels`, `metadata`). | | ||||
| | `action.*` | Action descriptor (`actionId`, `channel`, `digest`, `throttle`, `metadata`). | | ||||
| | `policy.*` | Policy metadata when supplied (`revisionId`, `name`). | | ||||
| | `topFindings[]` | Top-N findings summarised for convenience (vulnerability ID, severity, reachability). | | ||||
| | `digest.*` | When rendering digest flushes: `window`, `openedAt`, `itemCount`. | | ||||
|  | ||||
| Built-in helpers mirror the architecture dossier: | ||||
|  | ||||
| | Helper | Usage | | ||||
| |--------|-------| | ||||
| | `severity_icon severity` | Returns emoji/text badge representing severity. | | ||||
| | `link text url` | Produces channel-safe hyperlink. | | ||||
| | `pluralize count "finding"` | Adds plural suffix when `count != 1`. | | ||||
| | `truncate text maxLength` | Cuts strings while preserving determinism. | | ||||
| | `code text` | Formats inline code (Markdown/HTML aware). | | ||||
|  | ||||
| Connectors may expose additional helpers via partials, but must remain deterministic and side-effect free. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Sample templates | ||||
|  | ||||
| ### 4.1 Slack (Markdown + block kit) | ||||
|  | ||||
| ```hbs | ||||
| {{#*inline "findingLine"}} | ||||
| - {{severity_icon severity}} {{vulnId}} ({{severity}}) in `{{component}}` | ||||
| {{/inline}} | ||||
|  | ||||
| *:rotating_light: {{payload.summary.total}} findings {{#if payload.delta.newCritical}}(new critical: {{payload.delta.newCritical}}){{/if}}* | ||||
|  | ||||
| {{#if topFindings.length}} | ||||
| Top findings: | ||||
| {{#each topFindings}}{{> findingLine}}{{/each}} | ||||
| {{/if}} | ||||
|  | ||||
| {{link "Open report in Console" payload.links.ui}} | ||||
| ``` | ||||
|  | ||||
| ### 4.2 Email (HTML + text alternative) | ||||
|  | ||||
| ```hbs | ||||
| <h2>{{payload.verdict}} for {{event.scope.repo}}</h2> | ||||
| <p>{{payload.summary.total}} findings ({{payload.summary.blocked}} blocked, {{payload.summary.warned}} warned)</p> | ||||
| <table> | ||||
|   <thead><tr><th>Finding</th><th>Severity</th><th>Package</th></tr></thead> | ||||
|   <tbody> | ||||
|     {{#each topFindings}} | ||||
|     <tr> | ||||
|       <td>{{this.vulnId}}</td> | ||||
|       <td>{{this.severity}}</td> | ||||
|       <td>{{this.component}}</td> | ||||
|     </tr> | ||||
|     {{/each}} | ||||
|   </tbody> | ||||
| </table> | ||||
| <p>{{link "View full analysis" payload.links.ui}}</p> | ||||
| ``` | ||||
|  | ||||
| When delivering via email, connectors automatically attach a plain-text alternative derived from the rendered content to preserve accessibility. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Preview and validation | ||||
|  | ||||
| - `POST /channels/{id}/test` accepts an optional `templateId` and sample payload to produce a rendered preview without dispatching the event. Results include channel type, target, title/summary, locale, body hash, and connector metadata. | ||||
| - UI previews rely on the same API and highlight connector fallbacks (e.g., Teams adaptive card vs. text fallback). | ||||
| - Offline Kit scenarios can call `/internal/notify/templates/normalize` to ensure bundled templates match the canonical schema before packaging. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Best practices | ||||
|  | ||||
| - Keep channel-specific limits in mind (Slack block/character quotas, Teams adaptive card size, email line length). Lean on digests to summarise long lists. | ||||
| - Provide locale-specific versions for high-volume tenants; Notify selects the closest locale, falling back to `en-us`. | ||||
| - Store connector-specific hints (`metadata.layout`, `metadata.emoji`) in template metadata rather than rules when they affect rendering. | ||||
| - Version template bodies through metadata (e.g., `metadata.revision: "2025-10-28"`) so tenants can track changes over time. | ||||
| - Run test previews whenever introducing new helpers to confirm body hashes remain stable across environments. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user