Restructure solution layout by module
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -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`](../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. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user