feat: Add Storybook configuration and motion tokens implementation

- Introduced Storybook configuration files (`main.ts`, `preview.ts`, `tsconfig.json`) for Angular components.
- Created motion tokens in `motion-tokens.ts` to define durations, easing functions, and transforms.
- Developed a Storybook story for motion tokens showcasing their usage and reduced motion fallback.
- Added SCSS variables for motion durations, easing, and transforms in `_motion.scss`.
- Implemented accessibility smoke tests using Playwright and Axe for automated accessibility checks.
- Created portable and sealed bundle structures with corresponding JSON files for evidence locker.
- Added shell script for verifying notify kit determinism.
This commit is contained in:
StellaOps Bot
2025-12-04 21:36:06 +02:00
parent 600f3a7a3c
commit f214edff82
68 changed files with 1742 additions and 18 deletions

View File

@@ -0,0 +1,9 @@
{
"tenant_id": "tenant-123",
"delivery_id": "00000000-0000-4000-8000-000000000001",
"channel": "email",
"subject": "User signup",
"body": "User john@example.com joined",
"redacted_body": "User ***@example.com joined",
"pii_hash": "TBD"
}

View File

@@ -0,0 +1 @@
{"template_id":"tmpl-incident-start","locale":"en-US","channel":"email","expected_hash":"TBD","body_sample_path":"tmpl-incident-start.email.en-US.json"}

View File

@@ -0,0 +1,6 @@
{
"subject": "Incident started: ${incident_id}",
"body": "Incident ${incident_id} started at ${started_at}. Severity: ${severity}.",
"merge_fields": ["incident_id", "started_at", "severity"],
"preview_hash": "TBD"
}

View File

@@ -0,0 +1,10 @@
{
"trace_id": "00000000000000000000000000000001",
"tenant_id": "tenant-123",
"rule_id": "RULE-INCIDENT",
"channel_id": "email-default",
"attributes": {
"delivery_id": "00000000-0000-4000-8000-000000000001",
"status": "sent"
}
}

View File

@@ -0,0 +1,27 @@
groups:
- name: notify-slo
rules:
- alert: NotifyDeliverySuccessSLO
expr: sum(rate(notify_delivery_success_total[5m])) / sum(rate(notify_delivery_total[5m])) < 0.98
for: 10m
labels:
severity: page
annotations:
summary: "Notify delivery success below SLO"
description: "Success ratio below 98% over 10m"
- alert: NotifyBacklogDepthHigh
expr: notify_backlog_depth > 5000
for: 5m
labels:
severity: page
annotations:
summary: "Notify backlog too high"
description: "Backlog depth exceeded 5000 messages"
- alert: NotifyDlqGrowth
expr: rate(notify_dlq_depth[10m]) > 50
for: 10m
labels:
severity: ticket
annotations:
summary: "Notify DLQ growth"
description: "Dead letter queue growing faster than threshold"

View File

@@ -0,0 +1,9 @@
{
"title": "Notify SLO",
"panels": [
{ "title": "Delivery success", "target": "sum(rate(notify_delivery_success_total[5m])) / sum(rate(notify_delivery_total[5m]))" },
{ "title": "Backlog depth", "target": "notify_backlog_depth" },
{ "title": "DLQ depth", "target": "notify_dlq_depth" },
{ "title": "Latency p95", "target": "histogram_quantile(0.95, rate(notify_delivery_latency_seconds_bucket[5m]))" }
]
}

View File

@@ -0,0 +1,7 @@
# Quotas, backpressure, and DLQ (NR4)
- Per-tenant quotas: 500 deliveries/minute default; channel overrides: webhook 200/min, email 120/min, chat 240/min.
- Burst budget: 2x quota for 60 seconds, then hard clamp.
- Backpressure: reject enqueue when backlog > quota*10 or DLQ growth > 5%/min.
- DLQ schema: `docs/notifications/schemas/dlq-notify.schema.json`; redrive requires idempotent `delivery_id`/`dedupe_key`.
- Metrics to alert: backlog depth, DLQ depth, redrive success rate, enqueue reject count.

View File

@@ -0,0 +1,7 @@
# Retry and idempotency policy (NR5)
- `delivery_id`: UUIDv7; `dedupe_key`: hash(event_id + rule_id + channel_id).
- Backoff: exponential with jitter; base 2s, factor 2, max 5 attempts, cap 5 minutes between attempts.
- Connectors must be idempotent; retries reuse the same `dedupe_key` and must not duplicate sends.
- Out-of-order acks ignored: only monotonic `attempt` accepted.
- Record retry outcomes in receipts and include attempt count + reason.

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/channel.schema.json",
"title": "Notify Channel Configuration",
"type": "object",
"required": ["schema_version", "tenant_id", "channel_id", "kind", "config"],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"channel_id": { "type": "string", "pattern": "^[A-Z0-9_-]{4,64}$" },
"kind": { "type": "string", "enum": ["email", "slack", "teams", "webhook", "sms"] },
"config": { "type": "object" },
"secrets_ref": { "type": "object", "additionalProperties": { "type": "string" } },
"rate_limit": { "type": "object" },
"enabled": { "type": "boolean", "default": true },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/dlq-notify.schema.json",
"title": "Notify Dead Letter Entry",
"type": "object",
"required": ["schema_version", "tenant_id", "delivery_id", "reason", "payload", "first_failed_at"],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"delivery_id": { "type": "string", "pattern": "^[0-9a-fA-F-]{18,36}$" },
"reason": { "type": "string" },
"payload": { "type": "object" },
"backoff_attempts": { "type": "integer", "minimum": 0 },
"dedupe_key": { "type": "string" },
"first_failed_at": { "type": "string", "format": "date-time" },
"last_failed_at": { "type": "string", "format": "date-time" },
"redrive_after": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,26 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/event-envelope.schema.json",
"title": "Notify Event Envelope",
"type": "object",
"required": [
"schema_version",
"tenant_id",
"event_id",
"occurred_at",
"kind",
"payload"
],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"event_id": { "type": "string", "pattern": "^[0-9a-fA-F-]{18,36}$" },
"occurred_at": { "type": "string", "format": "date-time" },
"kind": { "type": "string", "minLength": 1 },
"correlation_id": { "type": "string" },
"source": { "type": "string" },
"payload": { "type": "object" },
"attributes": { "type": "object", "additionalProperties": { "type": ["string", "number", "boolean", "null"] } }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,14 @@
{
"catalog": "notify-schemas-catalog.json",
"hash_algorithm": "blake3-256",
"canonicalization": "json-normalized-utf8",
"entries": [
{ "file": "event-envelope.schema.json", "digest": "TBD" },
{ "file": "rule.schema.json", "digest": "TBD" },
{ "file": "template.schema.json", "digest": "TBD" },
{ "file": "channel.schema.json", "digest": "TBD" },
{ "file": "receipt.schema.json", "digest": "TBD" },
{ "file": "webhook.schema.json", "digest": "TBD" },
{ "file": "dlq-notify.schema.json", "digest": "TBD" }
]
}

View File

@@ -0,0 +1,6 @@
{
"payloadType": "application/vnd.notify.schema-catalog+json",
"payload": "BASE64_ENCODED_NOTIFY_SCHEMA_CATALOG_TBD",
"signatures": [],
"note": "Placeholder; replace with signed payload once BLAKE3 digest and signing key are available."
}

View File

@@ -0,0 +1,15 @@
{
"catalog_version": "v1.0",
"hash_algorithm": "blake3-256",
"canonicalization": "json-normalized-utf8",
"generated_at": "2025-12-04T00:00:00Z",
"schemas": [
{ "id": "event-envelope", "file": "event-envelope.schema.json", "version": "v1.0", "digest": "TBD" },
{ "id": "rule", "file": "rule.schema.json", "version": "v1.0", "digest": "TBD" },
{ "id": "template", "file": "template.schema.json", "version": "v1.0", "digest": "TBD" },
{ "id": "channel", "file": "channel.schema.json", "version": "v1.0", "digest": "TBD" },
{ "id": "receipt", "file": "receipt.schema.json", "version": "v1.0", "digest": "TBD" },
{ "id": "webhook", "file": "webhook.schema.json", "version": "v1.0", "digest": "TBD" },
{ "id": "dlq", "file": "dlq-notify.schema.json", "version": "v1.0", "digest": "TBD" }
]
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/receipt.schema.json",
"title": "Notify Delivery Receipt",
"type": "object",
"required": ["schema_version", "tenant_id", "delivery_id", "rule_id", "channel", "status", "sent_at"],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"delivery_id": { "type": "string", "pattern": "^[0-9a-fA-F-]{18,36}$" },
"rule_id": { "type": "string" },
"channel": { "type": "string" },
"status": { "type": "string", "enum": ["sent", "delivered", "failed", "queued", "acknowledged"] },
"attempt": { "type": "integer", "minimum": 1 },
"sent_at": { "type": "string", "format": "date-time" },
"ack_url": { "type": "string", "format": "uri" },
"response": { "type": "object" },
"errors": { "type": "array", "items": { "type": "string" } }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/rule.schema.json",
"title": "Notify Rule",
"type": "object",
"required": [
"schema_version",
"tenant_id",
"rule_id",
"name",
"sources",
"predicates",
"actions",
"approvals_required"
],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"rule_id": { "type": "string", "pattern": "^[A-Z0-9_-]{4,64}$" },
"name": { "type": "string", "minLength": 1 },
"description": { "type": "string" },
"severity": { "type": "string", "enum": ["info", "low", "medium", "high", "critical"] },
"sources": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
"predicates": { "type": "array", "items": { "type": "object" }, "minItems": 1 },
"actions": {
"type": "array",
"items": { "type": "object" },
"minItems": 1
},
"approvals_required": { "type": "integer", "minimum": 0, "maximum": 3 },
"quiet_hours": { "type": "array", "items": { "type": "string" } },
"simulation_required": { "type": "boolean", "default": true },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/template.schema.json",
"title": "Notify Template",
"type": "object",
"required": ["schema_version", "tenant_id", "template_id", "channel", "locale", "body"],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"template_id": { "type": "string", "pattern": "^[A-Z0-9_-]{4,64}$" },
"channel": { "type": "string", "enum": ["email", "slack", "teams", "webhook", "sms"] },
"locale": { "type": "string", "pattern": "^[a-z]{2}(-[A-Z]{2})?$" },
"subject": { "type": "string" },
"body": { "type": "string" },
"helpers": { "type": "object", "additionalProperties": { "type": "string" } },
"merge_fields": { "type": "array", "items": { "type": "string" } },
"preview_hash": { "type": "string" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://stella-ops.org/notify/schemas/webhook.schema.json",
"title": "Notify Webhook Payload",
"type": "object",
"required": ["schema_version", "tenant_id", "delivery_id", "signature", "body"],
"properties": {
"schema_version": { "type": "string", "pattern": "^v[0-9]+\\.[0-9]+$" },
"tenant_id": { "type": "string", "minLength": 1 },
"delivery_id": { "type": "string", "pattern": "^[0-9a-fA-F-]{18,36}$" },
"signature": { "type": "string" },
"hmac_id": { "type": "string" },
"body": { "type": "object" },
"sent_at": { "type": "string", "format": "date-time" },
"nonce": { "type": "string" },
"audience": { "type": "string" },
"expires_at": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}

View File

@@ -0,0 +1,6 @@
# Redaction and PII catalog (NR7)
- Classify merge fields: identifiers (hash), secrets (strip), PII (mask), operational metadata (retain).
- Storage and previews must use redacted forms by default; full bodies allowed only with `Notify.Audit` permission.
- Log payloads must omit secrets; hashes use BLAKE3-256 over UTF-8 normalized values.
- Fixtures under `docs/notifications/fixtures/redaction/` show expected redacted shapes for templates and receipts.

View File

@@ -0,0 +1,6 @@
# Tenant scoping and approvals (NR2)
- All Notify APIs require `tenant_id` in request and ledger records.
- High-impact actions (escalations, PII-bearing templates, cross-tenant fan-out) need N-of-M approvals: default 2 of 3 approvers with `Notify.Approver` role.
- Approvals captured as DSSE-signed records (future hook) and stored alongside rule change requests.
- Rejection reasons must be logged and returned in error payloads; audit log keeps requester, approver IDs, timestamps, and rule/template IDs.

View File

@@ -0,0 +1,6 @@
# Webhook and ack security (NR6)
- Webhooks must use HMAC-SHA256 with per-tenant rotating secrets or mTLS/DPoP. `hmac_id` maps to secret material.
- Ack URLs carry signed tokens (nonce, audience, tenant_id, delivery_id, expires_at) and are single-use. Reject replay or expired tokens.
- Enforce allowlists for domains and paths per tenant; deny wildcards.
- Capture failures in observability pipeline and DLQ with redrive after investigation.