Skip to content

Commit a00f335

Browse files
feat: hooks and record-triggered flows set the save notice (ctx.notice, save_notice node) (#28753)
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH Fixes #28251 Clause-②: yes (widening) Metadata-protocol: added ## 维护者速读 - 钩子(含沙箱化的 shipped body)现在可以调用 `ctx.notice(message)` 设置保存提示,新建/更新的应答里带回 `notice`。 - 记录触发的流程新增 `save_notice` 节点,同样设置这次写入的 `notice`。只有一条通道,就是写入自己的 `onNotice`。 - `save_notice` 只能放在由记录新建/更新触发的流程里,其他地方在发布时就会被响亮拒绝。审批或等待之后恢复的分支、子流程、map 子项在运行时会按名字拒绝。 - 流程提示可翻译,键是 `flows.<flow>.notices.<node_id>.message`,按写入者的语言渲染。`notice` 在线上仍是纯字符串。 - 范围只限单条记录的新建和更新。批量、克隆和流程节点自己的写入都不带提示。 - 钩子这一面的提示要不要也有翻译目录(`hooks.<hook>.notices.<KEY>.message`):席位已裁定 A,暂不做(卡上 `6562118323`),等第一个需要本地化提示的钩子再立卡。不需要你拍板。 ## What changes **Hook face: `ctx.notice(message)`.** `HookContextSchema` gains an optional `notice` member (`HookNoticeSetter`). ObjectQL's `triggerHooks` binds it on every `beforeInsert` / `afterInsert` / `beforeUpdate` / `afterUpdate` dispatch (`packages/objectql/src/hook-notice.ts`). Every dispatch site therefore has it, and no site can forget it. A call reads the write's own `onNotice` listener off `ctx.input.options` at call time and hands it the message. A write that passed no listener sets nothing. A message that is not a non-empty string throws a `TypeError` naming the call, so the write fails loudly instead of silently dropping the notice. Delete and read dispatches carry no member. The body runner passes the member through, and the QuickJS runner installs it as a host function, so shipped bodies (and handlers the build lowers into bodies) can call it. It needs no capability. **Flow face: the `save_notice` node.** `FlowNodeAction` gains `save_notice`, and its config is `SaveNoticeConfigSchema` (`{ message }`, one `{{ }}` text slot). The record-change trigger already runs the flow inside the write that fired it. It now hands the run that write's channel (`AutomationContext.saveNotice`, wrapping `ctx.notice`) and closes the channel when the handler returns. The executor renders the message and sets it. - At publish, the `FlowSchema` parse refuses the node at any region depth unless the flow's start is a create or update record trigger (`save-notice-placement.ts`). The refusal is `INVALID_METADATA` 422 at `nodes.<i>.type`. - At run, the node refuses by name when the run holds no channel: a leg restored after an approval or a wait, a direct `execute`, or a subflow or map child (both delete the channel from the child context). It also refuses when the channel is closed, which covers a leg resumed in memory after the write answered. **Why a node, not a member of an existing node (measured).** The flow that asked for this (HotCRM's deal submission, #27380 row 3) sends the record for approval. Its graph has no record-write node before the `approval` node that suspends the run, so a member on `update_record` would have nowhere to go. A member on `end` would run only after the approval, on a resumed leg, when the write has already answered. A dedicated node is the smallest surface that can stand before the suspension. **Message form (Wanted 3).** The platform's translatable-message shape is a catalog key that the server renders in the reader's locale (`flows.<flow>.refusals.<node_id>.message`, `hooks.<hook>.refusals.<CODE>.message`). The flow notice uses that shape: `flows.<flow>.notices.<node_id>.message` (`flowNoticeMessageKey`), picked in the writer's locale before the holes are filled. `os i18n extract` scaffolds the key, the coverage gate demands it, and `objectstack validate` refuses a key over an unknown flow or node or a non-`save_notice` node. So the wire `notice` stays a plain string, and no widening of `CreateDataResponseSchema.notice` / `UpdateDataResponseSchema.notice` is needed. A hook-side catalogue is left to a maintainer decision (see the report). **Other surfaces.** - The translation liveness row for `flows.notices` is `live`, cited to its reader. - The lint closed-binder set includes `save_notice`, which binds nothing. - The showcase demonstrates the node on `showcase_task_completed` (with zh-CN). - The platform checklist matrix item counts the new variant. - Spec artifacts were regenerated. ## Why #28247 added `notice` to the create and update answers, but only an in-process caller passing `onNotice` could set it. Shipped hook bodies and flows had no way in, so HotCRM faked the sentence with a `record:alert` banner. ## Verification - **Red first through REST (live showcase, `pnpm --filter @objectstack/example-showcase dev -- --fresh`).** The probe installed `app.probe1`, saved an object, a hook whose body calls `ctx.notice`, and a `record-after-update` flow with a `save_notice` node, then created and updated a record. - Base `4bf48a8c` (spec, objectql, runtime, trigger and automation rebuilt from base): `POST` → 201 with no `notice`; `PATCH` → 200 with no `notice`. A `save_notice` flow under `record-after-delete` was saved, 200. - Head: `POST` → 201 `"notice":"Sent Acme for approval"`; `PATCH` → 200 `"notice":"Moved Acme to review"`; the delete-trigger flow → 422 `INVALID_METADATA`, `issues[0].path` `nodes.1.type`. - **New tests.** - `spec/src/automation/save-notice-node.test.ts` (17) - `objectql/src/protocol-save-notice-answer.test.ts`, the #28251 block (8 of 22) - `runtime/src/sandbox/hook-notice.integration.test.ts`: real ObjectQL, SqlDriver and a QuickJS body - `trigger-record-change`: `save-notice-channel.test.ts` (3), plus 2 cases in `record-change-integration.test.ts` - `service-automation/src/builtin/save-notice-node.test.ts` (7, real in-memory i18n) - `lint/.../validate-translation-references.test.ts` (3) - `cli/test/i18n-flow-save-notice-coverage.test.ts` (6) - **Ablations, all red then restored to the HEAD blob** (`scripts/ablation-replace.mjs`). Each one below was broken in turn: - the `bindHookNotice` call - the member's refusal of a non-string - the QuickJS install - the body-runner spread - the trigger's channel spread - the trigger's channel close - the subflow delete and the map delete - the executor's no-channel refusal - the placement refusal - the lint collect and orphan check - the CLI extract - **Gates.** `dispatch-gates.mjs --commands`: 116 derived; `--ran` reconciles 115 run and 1 NOT MEASURED (`check:dual-build-cjs-loads`, exit 3, needs every package's dist). `eslint` over the seven touched packages: exit 0. ## Acceptance notes - A notice set from a fire-and-forget async `after*` hook that resolves after the write answered is dropped, because the answer has already gone. Await the work, or set the notice in a `before*` hook. - When several hooks or nodes set a notice on one write, the last one wins, as the #28247 contract says. - `ctx.notice(42)` in a body fails the write as a hook body fault, the same way any other `TypeError` in a body does. - The Studio palette (`ui/.../flow-canvas-parts.tsx` `NODE_PALETTE`) does not list `save_notice` yet. The node still saves and runs, and the actions feed carries its descriptor. - HotCRM can drop its `record:alert` workaround once this lands. That belongs to the hotcrm lane; this PR does not touch hotcrm. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5omF1nCbQTGgW1nbRA)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b48eeb1 commit a00f335

63 files changed

Lines changed: 1664 additions & 54 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/objectql': patch
4+
'@objectstack/runtime': patch
5+
'@objectstack/trigger-record-change': patch
6+
'@objectstack/service-automation': patch
7+
'@objectstack/cli': patch
8+
'@objectstack/lint': patch
9+
---
10+
11+
feat: a hook (shipped body included) and a record-triggered flow can set the save notice the create / update answer carries (#28251)
12+
13+
Clause-②: yes (widening: `HookContext` gains the optional `notice` member, `FlowNodeAction` gains `save_notice`, the `flows` translation group gains `notices`, and `AutomationContext` gains the optional `saveNotice`; nothing an existing stack writes is refused)
14+
Metadata-protocol: added
15+
16+
A single-record create or update has answered with an optional `notice` since #28247, but only an in-process caller that passed `onNotice` could set it. A shipped hook body could not reach that listener, and neither could a flow, so an app that wanted to tell the saver "Sent Acme to your manager for approval" had to fake it with a `record:alert` banner. Now there is one channel with two faces:
17+
18+
- **Hooks: `ctx.notice(message)`.** `HookContextSchema.notice` is the member. The engine binds it at every insert / update dispatch (`beforeInsert`, `afterInsert`, `beforeUpdate`, `afterUpdate`), so every dispatch site gets it, and it calls the write's own `onNotice` listener. A write nobody listens on (a batch, a seed, an import, a flow node's own write) sets nothing, and the call is a no-op there. Anything but a non-empty string is refused with a `TypeError` naming the call, and the write with it. Delete and read dispatches carry no member. The sandbox marshals the member into a shipped body (QuickJS), and no capability is needed to call it.
19+
- **Flows: the `save_notice` node.** `{ type: 'save_notice', config: { message } }` renders its `{{ }}` message and sets it on the write that started the run. The record-change trigger runs the flow inside that write and hands the run the write's channel (`AutomationContext.saveNotice`). The flow parse refuses the node, at any region depth, in a flow whose start is not a create or update record trigger (`record-(before|after)-(create|insert|update|write)`), because no write waits on such a run. At run time the node is refused by name where the parse cannot see it: a leg resumed after an approval or a wait, a subflow or map child (they get no channel), and a run started any other way.
20+
- **Translatable where it is rendered.** A flow notice is picked from `flows.<flow>.notices.<node_id>.message` (`flowNoticeMessageKey`) in the writer's locale before its holes are filled, and the authored template renders when nothing is picked. `os i18n extract` scaffolds the key, the coverage gate demands it, and `objectstack validate` refuses a key over an unknown flow, an unknown node, or a node that is not a `save_notice`. The answer's `notice` stays a plain string: the sentence arrives already rendered.
21+
22+
Scope: single-record create and update only. A batch, a clone and a write a flow node makes pass no listener, so they carry no notice.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ A write-path strip event: caller-supplied fields legally dropped from the payloa
557557
| **droppedFields** | `{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed' \| 'stored_expression'> }[]` | optional | Write-observability: caller-supplied fields that were LEGALLY stripped before the record was written — a non-system create cannot seed a static `readonly` column (the strip runs inside `engine.insert`, after the `beforeInsert` hooks, `isSystem`-gated), so those keys are dropped and the field re-derives its default. Present ONLY when ≥1 field was dropped; the create still succeeded without them (status/success semantics unchanged). REST additionally surfaces this as the `X-ObjectStack-Dropped-Fields` response header. Optional — omit-when-empty keeps the shape backward-compatible for existing clients. |
558558
| **warnings** | `{ field: string; code: string; message: string; rule?: string; … }[]` | optional | The advisory validation-rule hits of THIS write: each `severity: 'warning'` or `'info'` rule whose verdict on the written record was "violated", with its `rule` name, `severity`, `field`, `code` and author-written `message` — the same element the validate-only preview reports in a row's `warnings`. Advisory rules never block, so the write succeeded; this is the advisory to show the person who made it. Present ONLY when at least one advisory rule hit; a write with no hit omits the key. Hits of a nested write a hook or a flow made inside this one are not included, and a rule that could not be evaluated is not a hit. |
559559
| **withheldMentions** | `string[]` | optional | The ids of the users this write @mentioned whose mention notification was WITHHELD by the mention recipient gate, so they will not be notified: the gate's own verdict on THIS write, ids only, never the reason. Only a `sys_comment` write fills it, and only on the requester's own answer to the request that made the write; on an update, only a mention the update added is judged. The comment was saved with every mention; the requester is told here that the named users will not be notified. Response-only: never stored on the comment (a stored member would tell every reader who cannot read the thread). Present ONLY when at least one mention was withheld; otherwise the key is omitted. |
560-
| **notice** | `string` | optional | One author-written message telling the person who made THIS write what the save set in motion (for example "Sent to your manager for approval"), set by a hook running on the write through the write's own `onNotice` listener (`WriteObservabilityOptions`). Info severity: it reports an outcome, never a problem, so it is distinct from `warnings` (the advisory validation-rule hits) and a client shows it with, or in place of, its own success message. One message: when more than one call sets it, the LAST one wins. Present ONLY when a hook set a non-empty message on a write that succeeded; otherwise the key is omitted. A nested write a hook or a flow made inside this one carries its own options, so its notice never reaches this answer. Response-only: never stored on the record. |
560+
| **notice** | `string` | optional | One author-written message telling the person who made THIS write what the save set in motion (for example "Sent to your manager for approval"), set by a hook running on the write through the write's own `onNotice` listener (`WriteObservabilityOptions`; a hook body calls it as `ctx.notice(message)`), or by a `save_notice` node of a flow the write triggered. Info severity: it reports an outcome, never a problem, so it is distinct from `warnings` (the advisory validation-rule hits) and a client shows it with, or in place of, its own success message. One message: when more than one call sets it, the LAST one wins. Present ONLY when a hook set a non-empty message on a write that succeeded; otherwise the key is omitted. A nested write a hook or a flow made inside this one carries its own options, so its notice never reaches this answer. Response-only: never stored on the record. |
561561

562562
### Nested Shape: `CreateDataResponse.droppedFields[number]`
563563

@@ -3077,7 +3077,7 @@ Uninstall package response
30773077
| **droppedFields** | `{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key' \| 'computed' \| 'stored_expression'> }[]` | optional | Write-observability: caller-supplied fields the engine LEGALLY stripped from the write before persisting — static `readonly` or a TRUE `readonlyWhen` predicate. Present ONLY when ≥1 field was dropped; the update still succeeded without them (status/success semantics unchanged — stripping is legitimate, not an error). REST additionally surfaces this as the `X-ObjectStack-Dropped-Fields` response header. Optional — omit-when-empty keeps the shape backward-compatible for existing clients that only read `record`. |
30783078
| **warnings** | `{ field: string; code: string; message: string; rule?: string; … }[]` | optional | The advisory validation-rule hits of THIS write: each `severity: 'warning'` or `'info'` rule whose verdict on the written record was "violated", with its `rule` name, `severity`, `field`, `code` and author-written `message` — the same element the validate-only preview reports in a row's `warnings`. Advisory rules never block, so the write succeeded; this is the advisory to show the person who made it. Present ONLY when at least one advisory rule hit; a write with no hit omits the key. Hits of a nested write a hook or a flow made inside this one are not included, and a rule that could not be evaluated is not a hit. |
30793079
| **withheldMentions** | `string[]` | optional | The ids of the users this write @mentioned whose mention notification was WITHHELD by the mention recipient gate, so they will not be notified: the gate's own verdict on THIS write, ids only, never the reason. Only a `sys_comment` write fills it, and only on the requester's own answer to the request that made the write; on an update, only a mention the update added is judged. The comment was saved with every mention; the requester is told here that the named users will not be notified. Response-only: never stored on the comment (a stored member would tell every reader who cannot read the thread). Present ONLY when at least one mention was withheld; otherwise the key is omitted. |
3080-
| **notice** | `string` | optional | One author-written message telling the person who made THIS write what the save set in motion (for example "Sent to your manager for approval"), set by a hook running on the write through the write's own `onNotice` listener (`WriteObservabilityOptions`). Info severity: it reports an outcome, never a problem, so it is distinct from `warnings` (the advisory validation-rule hits) and a client shows it with, or in place of, its own success message. One message: when more than one call sets it, the LAST one wins. Present ONLY when a hook set a non-empty message on a write that succeeded; otherwise the key is omitted. A nested write a hook or a flow made inside this one carries its own options, so its notice never reaches this answer. Response-only: never stored on the record. |
3080+
| **notice** | `string` | optional | One author-written message telling the person who made THIS write what the save set in motion (for example "Sent to your manager for approval"), set by a hook running on the write through the write's own `onNotice` listener (`WriteObservabilityOptions`; a hook body calls it as `ctx.notice(message)`), or by a `save_notice` node of a flow the write triggered. Info severity: it reports an outcome, never a problem, so it is distinct from `warnings` (the advisory validation-rule hits) and a client shows it with, or in place of, its own success message. One message: when more than one call sets it, the LAST one wins. Present ONLY when a hook set a non-empty message on a write that succeeded; otherwise the key is omitted. A nested write a hook or a flow made inside this one carries its own options, so its notice never reaches this answer. Response-only: never stored on the record. |
30813081

30823082
### Nested Shape: `UpdateDataResponse.droppedFields[number]`
30833083

‎content/docs/references/automation/builtin-node-config.mdx‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ Deliberately absent:
104104
## TypeScript Usage
105105

106106
```typescript
107-
import { AssignmentConfigSchema, AssignmentExpressionValueSchema, AssignmentValueSchema, CreateRecordConfigSchema, DeleteRecordConfigSchema, EndConfigSchema, FlowCollectionSlotSchema, FlowValueSlotSchema, GetRecordConfigSchema, MapConfigSchema, ScreenConfigSchema, ScreenFieldConfigSchema, UpdateRecordConfigSchema } from '@objectstack/spec/automation';
108-
import type { AssignmentConfig, AssignmentExpressionValue, AssignmentValue, CreateRecordConfig, DeleteRecordConfig, EndConfig, FlowCollectionSlot, FlowValueSlot, GetRecordConfig, MapConfig, ScreenConfig, ScreenFieldConfig, UpdateRecordConfig } from '@objectstack/spec/automation';
107+
import { AssignmentConfigSchema, AssignmentExpressionValueSchema, AssignmentValueSchema, CreateRecordConfigSchema, DeleteRecordConfigSchema, EndConfigSchema, FlowCollectionSlotSchema, FlowValueSlotSchema, GetRecordConfigSchema, MapConfigSchema, SaveNoticeConfigSchema, ScreenConfigSchema, ScreenFieldConfigSchema, UpdateRecordConfigSchema } from '@objectstack/spec/automation';
108+
import type { AssignmentConfig, AssignmentExpressionValue, AssignmentValue, CreateRecordConfig, DeleteRecordConfig, EndConfig, FlowCollectionSlot, FlowValueSlot, GetRecordConfig, MapConfig, SaveNoticeConfig, ScreenConfig, ScreenFieldConfig, UpdateRecordConfig } from '@objectstack/spec/automation';
109109

110110
// Validate data
111111
const result = AssignmentConfigSchema.parse(data);
@@ -254,6 +254,17 @@ A value: a CEL value envelope `{ dialect: 'cel', source }` evaluated by the expr
254254
| **outputVariable** | `string` | optional | Each item's subflow output, collected in order — bound to a name without a leading `$` (the `$` names are the flow engine's own), read as `{{ name }}`. An item whose run was cancelled keeps its slot as the closed marker `{ $status: 'cancelled', $itemRunId }`: never an output, and not counted as a run |
255255

256256

257+
---
258+
259+
## SaveNoticeConfig
260+
261+
### Properties
262+
263+
| Property | Type | Required | Description |
264+
| :--- | :--- | :--- | :--- |
265+
| **message** | `string` | ✅ | The sentence the person who made the triggering write reads, answered as the create/update `notice` — a template rendered at run time like an `end` refusal `message`: `{{ }}` holes over the flow's variables (`Sent {{ record.name }} for approval`). Translated through flows.`<flow>`.notices.`<node_id>`.message in the writer's locale. One notice per write: a later notice on the same write replaces it. |
266+
267+
257268
---
258269

259270
## ScreenConfig

‎content/docs/references/automation/flow.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ const result = FlowSchema.parse(data);
205205
* `get_record`
206206
* `http`
207207
* `notify`
208+
* `save_notice`
208209
* `script`
209210
* `screen`
210211
* `wait`

‎content/docs/references/data/hook.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ L2 sandboxed JS body — runs inside an isolated VM with declared capabilities
111111
| **transaction** | `any` | optional | Database transaction handle |
112112
| **ql** | `any` | ✅ | ObjectQL Engine Reference |
113113
| **api** | `any` | optional | Cross-object data access (IScopedContext — `object(name)` + `transaction(cb)`) |
114+
| **notice** | `any` | optional | Tell the person who made THIS insert/update what the save set in motion: `ctx.notice(message)` hands one non-empty string to the write's own `onNotice` listener, answered as the create/update `notice`. Bound by the engine on every insert/update dispatch (a no-op when nothing listens); absent on find/delete |
114115
| **user** | `{ id?: string; name?: string; email?: string; organizationId?: string }` | optional | Current user info shortcut |
115116

116117
### Nested Shape: `HookContext.dispatch`

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Protocol reference — every schema by module
33
navTitle: Protocol Reference
4-
description: Every schema published by @objectstack/spec — 1587 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1588 schemas across 14 protocol modules
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -22,7 +22,7 @@ counts are sums of the rows they head. Regenerate with
2222
| :--- | ---: | ---: | :--- |
2323
| [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. |
2424
| [API Protocol](/docs/references/api) | 34 | 463 | REST contracts, endpoints, routing, realtime, batch, discovery. |
25-
| [Automation Protocol](/docs/references/automation) | 15 | 77 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
25+
| [Automation Protocol](/docs/references/automation) | 15 | 78 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
2626
| [Data Protocol](/docs/references/data) | 31 | 174 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
2727
| [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. |
2828
| [Integration Protocol](/docs/references/integration) | 1 | 23 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. |
@@ -34,7 +34,7 @@ counts are sums of the rows they head. Regenerate with
3434
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3535
| [System Protocol](/docs/references/system) | 37 | 287 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
3636
| [UI Protocol](/docs/references/ui) | 16 | 175 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **205** | **1587** | 14 protocol modules |
37+
| **Total** | **205** | **1588** | 14 protocol modules |
3838

3939
---
4040

@@ -108,7 +108,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery.
108108

109109
## Automation Protocol
110110

111-
**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **15 pages, 77 schemas**
111+
**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **15 pages, 78 schemas**
112112

113113
Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records.
114114

@@ -118,7 +118,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu
118118
| [`approval.zod.ts`](/docs/references/automation/approval) | `ApprovalDecision`, `ApprovalEscalation`, `ApprovalNodeApprover`, `ApprovalNodeConfig`, `ApproverType`, `DecisionOutputDef` |
119119
| [`assignment-rule.zod.ts`](/docs/references/automation/assignment-rule) | `AssignmentRule`, `AssignmentRuleEntry`, `AssignmentStrategy`, `AssignmentTarget` |
120120
| [`bpmn-interop.zod.ts`](/docs/references/automation/bpmn-interop) | `BpmnDiagnostic`, `BpmnElementMapping`, `BpmnExportOptions`, `BpmnImportOptions`, `BpmnInteropResult`, `BpmnUnmappedStrategy`, `BpmnVersion` |
121-
| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `EndConfig`, `FlowCollectionSlot`, `FlowValueSlot`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` |
121+
| [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `EndConfig`, `FlowCollectionSlot`, `FlowValueSlot`, `GetRecordConfig`, `MapConfig`, `SaveNoticeConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` |
122122
| [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` |
123123
| [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary` |
124124
| [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` |

0 commit comments

Comments
 (0)