Skip to content

Commit 890c2a6

Browse files
fix(automation): describe the HTTP and approval flow nodes in business words (#28528)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XRERDPoU7siRNtZLg5zAnbF)_ Fixes #28234 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:流程设计器节点面板里两个节点的说明文字改成业务话:HTTP 节点的描述及 `method` / `durable` / `timeoutMs` / `signingSecret` / `body` 五条配置提示,审批节点的描述。原来的工程细节(outbox、重试/死信、HMAC、CEL 值信封、「字面量照写」)挪进各条旁边的代码注释,没有丢。 - **为什么改**:卡 #28234(分诊 6531330544),#27838 的服务端半边:管理员在 Studio 搭流程时读到的是这些字,原文满是内部术语。规则同 PR #28233:首句业务话,不出现 ADR 编号、内部字段名或枚举字面量。 - **风险与回滚**:只改文案。键、类型、默认值、可接受的值、行为都没动;`packages/spec/**` 与 `ui/**` 没碰。仓里没有断言旧文字的测试,也没有这些字符串的手写翻译。回滚即 revert。 - **先看**:`packages/services/service-automation/src/builtin/http-nodes.ts`(描述与五条提示及其注释);`packages/plugins/plugin-approvals/src/approval-node.ts`(审批描述)。 - **两分钟验证**:`git show HEAD -- packages/services/service-automation/src/builtin/http-nodes.ts`,预期只见 `description` 字符串与注释的改动,`configSchema` 的键与 `type` 一个没变。 - **需要你做的**:无,席位落地。 ## What changes The node description and config help on a flow node descriptor are the text the Studio flow palette and node cards show an admin. Two descriptors, copy only: **HTTP node** (`packages/services/service-automation/src/builtin/http-nodes.ts`) | Entry | Before | After | | :--- | :--- | :--- | | description | Call an external HTTP endpoint. With `durable: true`, the call is enqueued on the messaging outbox with retry / dead-letter; otherwise it runs inline and returns the response. | Send a request to an outside web service: wait for its answer, or hand it off to be delivered in the background with automatic retries. | | `method` | HTTP method (default GET; POST when durable) — a literal, read as written | The kind of request, such as GET or POST. GET by default; POST when the request is delivered in the background. | | `durable` | Fire-and-forget via the durable outbox (retry/dead-letter) instead of inline request/response | Deliver the request in the background and keep retrying if it fails, instead of waiting for the answer; the flow does not see the response. | | `timeoutMs` | Per-request timeout (ms) | How long to wait for an answer, in milliseconds. | | `signingSecret` | HMAC-SHA256 secret → X-Objectstack-Signature — the secret itself, read as written | A shared secret used to sign each request, so the receiving service can check it came from you (sent as the X-Objectstack-Signature header). | | `body` | Request body (JSON-serialised) — a CEL value envelope `{ dialect: 'cel', source }` evaluated per run to the payload, or a literal sent as written | What to send, as JSON: a fixed value, or an expression worked out on each run. | The engineer detail each old string carried now sits in a code comment directly above its entry: the outbox arm and its retry / dead-letter, the literal-read rule for `method` / `timeoutMs` / `signingSecret`, the CEL value envelope shape for `body`, the HMAC-SHA256 scheme and the `''` unsigned opt-out for `signingSecret`. `url` and `headers` are not in this card's ruling and are untouched. **Approval node** (`packages/plugins/plugin-approvals/src/approval-node.ts`) - Before: "Route a record for human approval; suspends the flow until a decision, then continues down the approve / reject branch." - After: "Ask people to approve or reject a record. The flow waits for their decision, then continues down the approve or reject path." The suspend / resume-edge detail is in a comment above it. Wording starts from the card's proposal. One change: `body` adds "as JSON", since the payload is JSON-serialised and an admin choosing a fixed value needs to know that. **Also:** `packages/services/service-automation/src/builtin/http-node-signing.test.ts` has a docblock that quoted the descriptor's old `signingSecret` text as a fact. It now says the spec's `HttpConfigSchema.signingSecret` reads that text and the descriptor says it signs each request. Comment only; no assertion changed. **Changeset:** `.changeset/28234-flow-node-help-business-words.md`, `patch` on `@objectstack/service-automation` and `@objectstack/plugin-approvals` (both ship `dist`, which carries these strings), with both declaration lines. ## Tests No red-first defect: this is copy. Pins searched on `origin/main` `23d1303d1f` with `git grep -F` for each old string across the repo (dogfood, docs and snapshots included): - No test, dogfood fixture or docs snapshot asserts any of the old descriptor strings. The `content/docs/references/automation/io-node-config.mdx` rows that look alike are generated from the spec's `HttpConfigSchema` describes, which this card does not touch. - `ui/packages/app-shell/.../useFlowNodePalette.test.ts` carries its own fixture text "Route a record for human approval", not read from the engine; `ui/**` is out of scope and the fixture stays green. - No hand translation of these strings exists (no translation bundle or locale file carries descriptor descriptions). - Pins changed: none. Comment updated: the `http-node-signing.test.ts` docblock above. Results at `d13beb7a`: - Build: `pnpm --filter '@objectstack/service-automation...' --filter '@objectstack/plugin-approvals...' build` under the verify lock → `VERDICT command-exit 0`. - `pnpm --filter @objectstack/service-automation --filter @objectstack/plugin-approvals test` → service-automation `Test Files 249 passed (249)`, `Tests 3162 passed (3162)`; plugin-approvals `Test Files 106 passed (106)`, `Tests 1366 passed (1366)`; `VERDICT command-exit 0`. - Same filter, `typecheck` → `VERDICT command-exit 0`. - `pnpm exec eslint --no-inline-config packages/services/service-automation packages/plugins/plugin-approvals` → exit 0. ## Acceptance notes - Merge overlap: server seat 2 has automation cards in flight (#28055, #28202). The 17 open PRs were listed and each one's file list read (REST `pulls/N/files`; the Version Packages PR read to its last page): none touches `http-nodes.ts`, `approval-node.ts` or `http-node-signing.test.ts`. The one open PR in the same package, #28357 (head `a91c84adc9`, `sys-connector-credential.object.ts`), merges cleanly with this head: `git merge-tree --write-tree` exit 0. - Out of scope, noted, not filed: the Console's hard-coded fallback palette entry in `ui/packages/app-shell/src/views/metadata-admin/previews/flow-canvas-parts.tsx` still says "Call an external API" for the HTTP node; the engine descriptor overlays it when present. Carrier: the ui half of #27838. ## Verification Derived gate list (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, 61 commands on this diff), run at `d13beb7a`, exit codes captured before any pipe, reconciled with `--ran`: `✓ dispatch-gates --ran: 61 derived famil(ies) accounted for — 60 run, 1 NOT-MEASURED`. <details><summary>Gate readings</summary> - 60 of 61 exit 0. - `node scripts/check-plugin-teardown-shape.mjs --self-test` first exited 3 on the shallow clone; after `git fetch --depth=1 origin 621a487…` it exits 0 (`48 cases pass`). - `pnpm check:i18n` first exited 3 (CLI not built); after the gate's own prerequisite build it exits 0: `check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)`. - NOT MEASURED: `pnpm check:dual-build-cjs-loads`, reason: needs a full workspace build (34 packages without `dist`); CI's Lint & Repo Gates runs it. </details> No `!` in the title and no `(narrowing)` arm, so no full dogfood battery. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. (no names added) - [x] 3. No formatter ran; `pnpm lint` passes. (scoped to the two packages) - [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. (not touched) - [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. --- _Generated by [Claude Code](https://claude.ai/code/session_01FEkQ5oZiDbKQCsyey7KM7z)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 75c2658 commit 890c2a6

4 files changed

Lines changed: 58 additions & 14 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/service-automation': patch
3+
'@objectstack/plugin-approvals': patch
4+
---
5+
6+
Clause-②: no
7+
Metadata-protocol: none
8+
9+
The HTTP and approval flow nodes now describe themselves in business words. The node description and
10+
config help are the text the Studio flow palette and node cards show an admin, and the HTTP node's
11+
spoke of the messaging outbox, retry / dead-letter, an HMAC-SHA256 secret, "a literal, read as
12+
written" and a CEL value envelope; the approval node's said it "suspends the flow". The HTTP node now
13+
reads "Send a request to an outside web service: wait for its answer, or hand it off to be delivered
14+
in the background with automatic retries", with plain help for the request kind, background delivery,
15+
the wait time, the signing secret and what to send; the approval node reads "Ask people to approve or
16+
reject a record". The engineer detail moved to code comments beside each entry. No key, type, default,
17+
accepted value or behaviour changed.

‎packages/plugins/plugin-approvals/src/approval-node.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,11 @@ export function registerApprovalNode(
135135
type: APPROVAL_NODE_TYPE,
136136
version: '1.0.0',
137137
name: 'Approval',
138-
description: 'Route a record for human approval; suspends the flow until a decision, '
139-
+ 'then continues down the approve / reject branch.',
138+
// Palette and node-card text an admin reads, in business words. The run
139+
// suspends here until a decision, then resumes down the `approve` /
140+
// `reject` edge (see `resumeAuthority` below).
141+
description: 'Ask people to approve or reject a record. The flow waits for their decision, '
142+
+ 'then continues down the approve or reject path.',
140143
icon: 'check-circle',
141144
category: 'human',
142145
paradigms: ['flow'],

‎packages/services/service-automation/src/builtin/http-node-signing.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ import { registerHttpNodes } from './http-nodes.js';
1919
* wire, on EVERY arm the node can take.
2020
*
2121
* The key is declared without qualification — `HttpConfigSchema.signingSecret`
22-
* and the descriptor's `configSchema` both read "HMAC-SHA256 secret →
23-
* X-Objectstack-Signature" — yet only the durable arm used to hand it to
24-
* anything that signs (the messaging outbox). The inline arm, and the durable
22+
* reads "HMAC-SHA256 secret → X-Objectstack-Signature", and the descriptor's
23+
* `configSchema` says it signs each request — yet only the durable arm used
24+
* to hand it to anything that signs (the messaging outbox). The inline arm,
25+
* and the durable
2526
* arm's no-outbox fallback that degrades to it, built their `fetch` from
2627
* `headers` alone: the request went out unsigned and the run said
2728
* `success: true`.

‎packages/services/service-automation/src/builtin/http-nodes.ts‎

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,15 @@ export function registerHttpNodes(engine: AutomationEngine, ctx: PluginContext):
123123
type: HTTP_TYPE,
124124
version: '1.0.0',
125125
name: 'HTTP',
126+
// The description and the config help below are the Studio flow
127+
// palette and node-card text an admin reads, so they speak business
128+
// words. The engineer detail they used to carry lives here: with
129+
// `durable: true` the call is enqueued on the messaging HTTP outbox
130+
// (retry / dead-letter) and the run does not see the response;
131+
// otherwise it runs inline and returns the response.
126132
description:
127-
'Call an external HTTP endpoint. With `durable: true`, the call is enqueued on the '
128-
+ 'messaging outbox with retry / dead-letter; otherwise it runs inline and returns the response.',
133+
'Send a request to an outside web service: wait for its answer, or hand it off to be '
134+
+ 'delivered in the background with automatic retries.',
129135
icon: 'globe',
130136
category: 'io',
131137
source: 'builtin',
@@ -166,9 +172,13 @@ export function registerHttpNodes(engine: AutomationEngine, ctx: PluginContext):
166172
+ 'path is the secret: call the service through a token-authenticated connector instead, such '
167173
+ 'as the `slack` connector with its bot token.',
168174
},
175+
// `method` is a literal, read as written (#19939): no `{{ }}`
176+
// rendering. Default `GET`; `POST` when `durable` is true.
169177
method: {
170178
type: 'string',
171-
description: 'HTTP method (default GET; POST when durable) — a literal, read as written',
179+
description:
180+
'The kind of request, such as GET or POST. GET by default; POST when the request is '
181+
+ 'delivered in the background.',
172182
},
173183
headers: {
174184
type: 'object',
@@ -180,19 +190,32 @@ export function registerHttpNodes(engine: AutomationEngine, ctx: PluginContext):
180190
+ 'authenticated upstream through a `connector_action` on a declarative connector whose '
181191
+ '`auth.credentialRef` names the secret.',
182192
},
193+
// `body` is JSON-serialised: a CEL value envelope
194+
// `{ dialect: 'cel', source }` evaluated per run to the payload,
195+
// or a literal sent as written.
183196
body: {
184-
description:
185-
'Request body (JSON-serialised) — a CEL value envelope `{ dialect: \'cel\', source }` '
186-
+ 'evaluated per run to the payload, or a literal sent as written',
197+
description: 'What to send, as JSON: a fixed value, or an expression worked out on each run.',
187198
},
199+
// `durable: true` is fire-and-forget through the messaging HTTP
200+
// outbox (retry / dead-letter) instead of an inline
201+
// request / response.
188202
durable: {
189203
type: 'boolean',
190-
description: 'Fire-and-forget via the durable outbox (retry/dead-letter) instead of inline request/response',
204+
description:
205+
'Deliver the request in the background and keep retrying if it fails, instead of '
206+
+ 'waiting for the answer; the flow does not see the response.',
191207
},
192-
timeoutMs: { type: 'number', description: 'Per-request timeout (ms)' },
208+
// Per-request timeout in milliseconds, on both arms; a literal
209+
// number, read as written.
210+
timeoutMs: { type: 'number', description: 'How long to wait for an answer, in milliseconds.' },
211+
// The HMAC-SHA256 secret that signs the request body into the
212+
// `X-Objectstack-Signature` header, on every arm; the secret
213+
// itself, read as written (`''` sends unsigned on purpose).
193214
signingSecret: {
194215
type: 'string',
195-
description: 'HMAC-SHA256 secret → X-Objectstack-Signature — the secret itself, read as written',
216+
description:
217+
'A shared secret used to sign each request, so the receiving service can check it came '
218+
+ 'from you (sent as the X-Objectstack-Signature header).',
196219
},
197220
},
198221
},

0 commit comments

Comments
 (0)