Package notifications¶
The work that a package derives has to reach people: a task is assigned, an
approval is waiting for a decision, a case is closed. Which Control Plane event
becomes a notification, to whom, with what text, links, and buttons, a package
describes with notification rules: objects of the NotificationRule kind in
the notification-rules/ folder. The article is for package authors: how to write
a rule, choose the recipient, add decision buttons, and ship rules with the
package. Rationale: TAI-ADR-0053 (item 4).
The full rule specification, the validation order, and the service API are in the article Notification rules.
Where a rule lives¶
A rule is a package object just like a task type, but it is applied to the notification service, not to the core: the service stores rule versions, reads core events, and creates notifications with its own account, with channels, recipient preferences, and a delivery log.
flowchart LR
P["Package<br/>notification-rules/*.yaml"] -->|package-sdk apply| NS["notification-service"]
CP["Control Plane<br/>event log"] --> NS
NS -->|"on.type, on.when → recipient → template"| N["Notification<br/>web, email, telegram"]
- A new kind of notification is a package edit and
apply, without a service release. - The service has no built-in "event → notification" table: as long as the tenant has no enabled rule, the service does not read events.
A package rule¶
# notification-rules/claim-resolved.yaml
apiVersion: taimen.ai/v1
kind: NotificationRule
key: claim-resolved
spec:
description: Владельцу задачи — обращение закрыто и принято.
"on":
type: task.verified
when: {eq: [{var: task.typeKey}, claim-resolution]}
recipient: {kind: taskOwner, fallback: taskAssignee}
notification:
type: claims.claim_resolved
title: "Обращение закрыто: {{task.publicId}} {{task.title}}"
body: |-
Исполнитель: {{task.assigneeId.displayName}}
links:
- {label: Открыть задачу, url: "${TASK_URL_BASE}/{{task.publicId}}"}
package-sdk add NotificationRule <key> writes a scaffold on task.created to
the task assignee.
| Field | What it sets |
|---|---|
on.type |
an event type from the core catalog (task.verified) or a prefix (approval.*) |
on.when |
a condition in the core rule grammar: true, false, or an object with one operator (and, or, not, eq, ne, lt, le, gt, ge, in, exists) over {var: <path>} with the roots payload, event, task; true by default |
recipient |
to whom (below) |
notification.type |
the notification type ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$; recipient preferences work by it; name it after the package domain |
notification.title, body |
templates for the title (up to 300 characters) and the text (up to 4000) with {{ path }} placeholders |
notification.links |
up to 5 links {label, url} |
notification.actions |
[approvalDecide]: the "Approve" and "Reject" buttons |
dedupKeyTemplate |
the deduplication key; rule:<key>:event:{{event.id}} by default |
close.on, close.outcome |
which events close the buttons of a notification with the same key, and with which outcome |
status |
enabled (default) or disabled |
Quote the on key
A YAML 1.1 loader reads a bare on as true, and the rule loses a required
field. Write "on":.
Templates have no conditions or loops: different texts are different rules with
different on.when. A missing value gives an empty string; a body line in which
all placeholders are empty is omitted entirely; a link with an empty placeholder or
a non-absolute URL is omitted. A path that ends in displayName after a principal
identifier substitutes the name from the core catalog.
Recipient¶
recipient.kind |
Who receives it | When to choose it |
|---|---|---|
taskOwner |
the owner of the event's task | the result of work goes to whoever is waiting for it |
taskAssignee |
the assignee of the event's task | work has come to the assignee |
assigned |
the assigned decider of the approval event; without one, the holders of the required role | decision requests |
role |
the holders of the role whose id is at the ref path, in the event's workspace |
group work |
principal |
a specific principal, ref is a UUID |
a service recipient of the installation |
fallback is taskOwner, taskAssignee, or none: the second recipient if the
first is empty.
A package does not know the people of an installation. Derive the recipient from
the event: the owner, the assignee, a role. If you need a specific principal, write
it as an installation variable of the principal kind:
# package.yaml
spec:
variables:
CLAIMS_ESCALATION_PRINCIPAL:
kind: principal
description: Кому уходят обращения без исполнителя
TASK_URL_BASE:
kind: url
description: База ссылки «Открыть задачу» интерфейса установки
The package installer substitutes ${NAME}; the service stores and accepts only
UUIDs.
Decision buttons¶
A decision request with buttons, and closing them with the outcome:
# notification-rules/claim-approval-requested.yaml
apiVersion: taimen.ai/v1
kind: NotificationRule
key: claim-approval-requested
spec:
"on":
type: approval.requested
when: {eq: [{var: task.typeKey}, claim-reply]}
recipient: {kind: assigned}
notification:
type: claims.reply_approval_requested
title: "Ответ на обращение ждёт решения: {{task.publicId}}"
body: |-
Комментарий: {{payload.comment}}
actions: [approvalDecide]
dedupKeyTemplate: "claims:approval:{{event.entityId}}"
close:
"on": [approval.approved, approval.rejected, approval.cancelled]
approvalDecideis allowed only for events of theapprovalentity: the service builds theapproveandrejectactions fromevent.entityId, the approval id. A channel with buttons executes them.- Closing finds the notification by the key rendered over the closing event.
Therefore the key is built from what the opening and closing events have in
common; for decisions, this is
event.entityId. - A decision by button is a human decision on the approval, as in the interface; an external write after it goes as the approval outcome (see Package skills).
Validation and apply¶
check without the service validates the schema ($defs.notificationRuleSpec),
the rule key, and the on.when grammar. Event types, the payload.…, event.…,
task.… paths, and the consistency of the rule are known only to the service:
:validate checks them during plan.
export CP_TOKEN=<access token audience control-plane>
export NOTIFICATION_SERVICE_URL=https://platform.example.com/notify
export NOTIFY_TOKEN=<access token audience notification-service, scope notifications:admin>
package-sdk plan --install packages.yaml --server https://platform.example.com --out plan.json
package-sdk apply --plan plan.json --server https://platform.example.com
plancallsPOST /api/v1/notification-rules:validatefor all rules of the installation. A rule that the service would not accept stops the whole plan. An installation with notification rules is not planned without the service address and a token.- The
notification-rulesplan section includes only the rules for which:validateansweredchanged: true; the service computes the version from the specification hash. apply --planapplies the sections in order: catalog, core, ontologies, notification rules, retirement. By the time the rules are written, the core has already been brought up to date.
The token is NOTIFY_TOKEN, or an exchange of the same IAM credential that the
installer uses for the core for the notification-service audience with the
notifications:admin scope (the PAT ceiling must allow this). The exchanged token
is taken before each request and renewed before it expires; NOTIFY_TOKEN is not
renewed.
Retirement is a key in retire.NotificationRule of the installation file: the
service moves the current version to retired; notifications already sent remain.
Exporting the current version into a package is package-sdk export --kind
NotificationRule --key <key> --package <directory>; it does not need the core.
Common problems¶
| Symptom | Cause and fix |
|---|---|
| no notifications about events at all | the tenant has no enabled rules: apply a package with rules |
the plan is not built: в установке есть правила уведомлений — нужен сервис уведомлений (the installation has notification rules: the notification service is needed) |
no NOTIFICATION_SERVICE_URL or no token for the notification-service audience |
422 invalid_notification_rule, unknown_event_type |
the event type is not from the core catalog known to the service |
invalid_spec at /on |
an unquoted on turned into true |
unknown_field on a task.… path |
the event has no task, or the field is not in the task projection |
| no "Open task" link | the link base variable is empty or the result is not an absolute URL |
| the buttons did not close after the decision | the deduplication keys of the opening and closing events differ: build them from event.entityId |
See also¶
- Notification rules: specification and API
- Notifications: channels, recipient preferences, log
- Telegram: decisions by buttons
- Events: the core event catalog
- Approvals
- Catalog packages