Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/30689-notify-click-through-default-describe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@objectstack/spec': patch
---

docs(spec): the `notify` node's `sourceObject`, `sourceId` and `actionUrl` descriptions now state the click-through default the runtime already applies

Clause-②: no
Metadata-protocol: none

A `notify` node that writes none of `sourceObject`, `sourceId` and `actionUrl` links to the run's trigger record when the run has one (a record change other than a delete, a time-relative sweep's row, an action started on a record). The `NotifyConfigSchema` descriptions, and the reference page generated from them, said only that the pair takes effect together. They now also say that an explicit pair or an `actionUrl` is kept as written, that a half-written pair is still dropped rather than defaulted, and that a run with no trigger record links nowhere. No key, shape, default or refinement changes.
6 changes: 3 additions & 3 deletions content/docs/references/automation/io-node-config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ const result = HttpConfigSchema.parse(data);
| **channels** | `string \| string[]` | optional | Channels to fan out to (default: inbox) |
| **topic** | `string` | optional | Event topic (default: "notify") |
| **severity** | `Enum<'info' \| 'warning' \| 'critical'>` | optional | Severity forwarded to the messaging service |
| **sourceObject** | `string` | optional | Object name of the record the notification links to (writes sys_notification.source_object) — a literal, read as written: a single-brace `{token}` is not resolved here and is refused, as is a CEL value envelope; write the value. Only takes effect together with sourceId — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link. |
| **sourceId** | `string \| { dialect: 'cel'; source: string; ast?: any; meta?: object }` | optional | Record id the notification links to (writes sys_notification.source_id) — a string, or a CEL value envelope `{ dialect: 'cel', source }` evaluated per run to one (`record.id`); a `{…}` template token is refused. Only takes effect together with sourceObject — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link. |
| **sourceObject** | `string` | optional | Object name of the record the notification links to (writes sys_notification.source_object) — a literal, read as written: a single-brace `{token}` is not resolved here and is refused, as is a CEL value envelope; write the value. Only takes effect together with sourceId — a half-specified click-through target is dropped at execute time, not defaulted, so the inbox never renders a dead link. When sourceObject, sourceId and actionUrl are all left out, a run started by a record (a record change other than a delete, a time-relative sweep, an action on a record) links to that record, and a run with no trigger record links nowhere; an explicit pair or an actionUrl is kept as written. |
| **sourceId** | `string \| { dialect: 'cel'; source: string; ast?: any; meta?: object }` | optional | Record id the notification links to (writes sys_notification.source_id) — a string, or a CEL value envelope `{ dialect: 'cel', source }` evaluated per run to one (`record.id`); a `{…}` template token is refused. Only takes effect together with sourceObject — a half-specified click-through target is dropped at execute time, not defaulted, so the inbox never renders a dead link. When sourceObject, sourceId and actionUrl are all left out, a run started by a record (a record change other than a delete, a time-relative sweep, an action on a record) links to that record, and a run with no trigger record links nowhere; an explicit pair or an actionUrl is kept as written. |
| **actorId** | `string` | optional | User id that caused the event (writes sys_notification.actor_id) — a literal, read as written: a single-brace `{token}` is not resolved here and is refused, as is a CEL value envelope; write the value |
| **actionUrl** | `string` | optional | Explicit click-through URL, rendered per run with `{{ }}` holes like `title` (`/crm_case/{{ record.id }}`); a single-brace `{token}` is refused. Overrides the link synthesized from sourceObject/sourceId. |
| **actionUrl** | `string` | optional | Explicit click-through URL, rendered per run with `{{ }}` holes like `title` (`/crm_case/{{ record.id }}`); a single-brace `{token}` is refused. Overrides the link synthesized from sourceObject/sourceId, and written alone keeps the notification from linking to the record that started the run. |
| **payload** | `Record<string, any>` | optional | Extra values merged into the notification payload — literals, read as written: a single-brace `{token}` in any string of one is not resolved here and is refused, as is a CEL value envelope written as a value; write the value |

### Nested Shape: `NotifyConfig.recipients[option 4]`
Expand Down
34 changes: 26 additions & 8 deletions packages/spec/src/automation/io-node-config.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,16 @@ function notifyLiteralRefusals(cfg: {
* click-through target is dropped so the inbox never renders a dead link.
* The schema keeps both optional rather than refining, because the executor
* tolerates (drops) the half-specified shape rather than rejecting it.
* - Click-through default: a node that writes none of `sourceObject`,
* `sourceId` and `actionUrl` links to the run's trigger record, when the
* run has one — a record change other than a delete, a time-relative
* sweep's row, an action started on a record. A schedule tick, an inbound
* API post and a manual start with no record have none and link nowhere;
* a delete's record is gone once the write lands, so it is not linked. An
* explicit pair or an explicit `actionUrl` is the author's word and wins,
* and a half-written pair is still dropped, never defaulted. The executor
* owns the default (`defaultClickThroughOf` in service-automation's
* `notify-node.ts`); the schema shape is unchanged by it.
* - The historical aliases (`to`/`subject`/`body`/`url`, nested
* `source: { object, id }`) are NOT part of this contract: the ADR-0087 D2
* conversion `flow-node-notify-config-aliases` rewrote them at load for
Expand Down Expand Up @@ -744,26 +754,34 @@ export const NotifyConfigSchema = lazySchema(() => strictObject({
.describe('Severity forwarded to the messaging service'),
/**
* Click-through target object — only effective together with `sourceId`
* (#2675). A literal, read as written (#19939, "notify literals" above).
* (#2675); a half-written pair is dropped, never defaulted. A literal, read
* as written (#19939, "notify literals" above). Left out together with
* `sourceId` and `actionUrl`, the link defaults to the run's trigger record
* (see "click-through default" above).
*/
sourceObject: notifyLiteralStringSchema('sourceObject').optional()
.describe('Object name of the record the notification links to (writes sys_notification.source_object) — a literal, read as written: a single-brace `{token}` is not resolved here and is refused, as is a CEL value envelope; write the value. Only takes effect together with sourceId — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link.'),
.describe('Object name of the record the notification links to (writes sys_notification.source_object) — a literal, read as written: a single-brace `{token}` is not resolved here and is refused, as is a CEL value envelope; write the value. Only takes effect together with sourceId — a half-specified click-through target is dropped at execute time, not defaulted, so the inbox never renders a dead link. When sourceObject, sourceId and actionUrl are all left out, a run started by a record (a record change other than a delete, a time-relative sweep, an action on a record) links to that record, and a run with no trigger record links nowhere; an explicit pair or an actionUrl is kept as written.'),
/**
* Click-through target record id — only effective together with
* `sourceObject`. A `value` slot (#19939, {@link NotifySourceIdSlotSchema}).
* `sourceObject`; a half-written pair is dropped, never defaulted. A `value`
* slot (#19939, {@link NotifySourceIdSlotSchema}). Left out together with
* `sourceObject` and `actionUrl`, the link defaults to the run's trigger
* record (see "click-through default" above).
*/
sourceId: NotifySourceIdSlotSchema.optional()
.describe('Record id the notification links to (writes sys_notification.source_id) — a string, or a CEL value envelope `{ dialect: \'cel\', source }` evaluated per run to one (`record.id`); a `{…}` template token is refused. Only takes effect together with sourceObject — a half-specified click-through target is dropped at execute time, so the inbox never renders a dead link.'),
.describe('Record id the notification links to (writes sys_notification.source_id) — a string, or a CEL value envelope `{ dialect: \'cel\', source }` evaluated per run to one (`record.id`); a `{…}` template token is refused. Only takes effect together with sourceObject — a half-specified click-through target is dropped at execute time, not defaulted, so the inbox never renders a dead link. When sourceObject, sourceId and actionUrl are all left out, a run started by a record (a record change other than a delete, a time-relative sweep, an action on a record) links to that record, and a run with no trigger record links nowhere; an explicit pair or an actionUrl is kept as written.'),
/** User id that caused the event. A literal, read as written (#19939, "notify literals" above). */
actorId: notifyLiteralStringSchema('actorId').optional()
.describe('User id that caused the event (writes sys_notification.actor_id) — a literal, read as written: a single-brace `{token}` is not resolved here and is refused, as is a CEL value envelope; write the value'),
/**
* Explicit click-through URL; overrides the sourceObject/sourceId link. A
* TEXT slot (#19939): rendered per run with `{{ }}` holes, like `title`; a
* single-brace `{token}` is refused by the `superRefine` below.
* Explicit click-through URL; overrides the sourceObject/sourceId link, and
* written alone keeps the trigger-record default from applying (see
* "click-through default" above). A TEXT slot (#19939): rendered per run with
* `{{ }}` holes, like `title`; a single-brace `{token}` is refused by the
* `superRefine` below.
*/
actionUrl: z.string().optional()
.describe('Explicit click-through URL, rendered per run with `{{ }}` holes like `title` (`/crm_case/{{ record.id }}`); a single-brace `{token}` is refused. Overrides the link synthesized from sourceObject/sourceId.'),
.describe('Explicit click-through URL, rendered per run with `{{ }}` holes like `title` (`/crm_case/{{ record.id }}`); a single-brace `{token}` is refused. Overrides the link synthesized from sourceObject/sourceId, and written alone keeps the notification from linking to the record that started the run.'),
/**
* Extra values merged into the notification payload — literals, read as
* written (#19939, "notify literals" above).
Expand Down
Loading