Skip to content

Commit 7a09eee

Browse files
objectstack-fleet[bot]os-warrenclaude
authored
docs(skills): objectstack-automation routes an outbound credential to a connector credentialRef and drops the Slack webhook-url example (#20778)
Fixes #20657 Clause-②: no The skills half of the #20590 family (the parent is completed; the spec, runtime and guide faces landed in #20654 / #20655 / #20672). `skills/objectstack-automation` now tells an author what a flow definition serves and where an outbound credential goes, and its long example no longer teaches a secret-bearing Slack webhook url. ## What changed **`skills/objectstack-automation/SKILL.md`** - The `http` row of the node table now says: `url` and `headers` are served as written with the definition to every member who can read flows — only `signingSecret` and a start node's `secret` are withheld — so never a credential there; routed by shape — a header credential → a declarative connector (`bearer` / `basic` / `api-key`) with `auth.credentialRef`; a query-string key → `api-key` with `paramName`; a path-secret webhook url → a token-authenticated connector such as `slack` — called via `connector_action`. The same row carries the `timeoutMs` lesson that used to live only in the example's `http` node (the executor arms a timer only when `timeoutMs` is set — `packages/services/service-automation/src/builtin/http-nodes.ts:281`). - The `script` note's dispatch sentence routes Slack to `connector_action` on the `slack` connector instead of "or `http` webhook". - The CRM Blueprint's lead sentence carries the metadata-first rule — "Default approach for metadata apps — model the business lifecycle in Flow/Approval metadata first, reserve custom code for edge-case integrations — in this CRM-style structure:" — because that rule had no other home in the skill. - Removed as token payment (content with a home elsewhere or redundant in place, never a re-wrap): the `http_request` alias clause on the `http` row — the clause was accurate: the ADR-0087 conversion `flow-node-http-callout-rename` (`packages/spec/src/conversions/registry.ts:58`) still rewrites `http_request` / `http_call` / `webhook` to `http` on build/validate even though the node-type enum (`packages/spec/src/automation/flow.zod.ts:41`) names only `http`; the skill teaches the canonical `http` and the alias needs no author action, so the deletion stands; the `FlowNodeAction` seed-set aside; the engine-internal `resolveTriggerBinding` name; the trigger-intro clause that duplicated the Trigger Types table's ObjectQL-hook column; the "It is not a way past a guardrail" one-liner that duplicated the paragraph under the routing table; the three-line `previous` / `record` blockquote reduced to a one-line pointer at objectstack-formula §5 (`skills/objectstack-formula/SKILL.md:283-296` carries it); the word "verbatim" in the three moved-to pointers; and word trims in four sentences. **`skills/objectstack-automation/references/examples-flows.md`** - The `notify_manager` node is a `connector_action` on the `slack` connector (`chat.postMessage`, `input: { channel, text }`) — the shape `examples/app-showcase/src/automation/flows/index.ts:329-339` dispatches and `packages/connectors/connector-slack/src/slack-connector.ts:100-114` declares. A three-line comment says why it is not an `http` node with a webhook url. The block keeps its `os:check` marker and type-checks (readings below). - Removed as token payment: the four-line schedule comment is two lines of the same facts, and the `filter` comment that duplicated the blockquote at the top of the file. Wording matches the landed sources: the `HttpConfigSchema.headers` describe (`packages/spec/src/automation/io-node-config.zod.ts:363-370`, the #20654 describe the dispatch could not locate; its sibling for `connectorConfig.input` is `flow.zod.ts:628-634`), the `http` node descriptor describes (`packages/services/service-automation/src/builtin/http-nodes.ts:111-133`) and the flows guide callout (`content/docs/automation/flows.mdx:274`, whose "Only `signingSecret` (and a start node's `secret`) is withheld" and "`bearer`, `basic` or `api-key`" the row now matches; the redaction paths are `packages/metadata-protocol/src/metadata-redaction.ts:427` / `:477` / `:521`). Per the wording guard on the card, no sentence routes a path-secret webhook url to `auth.credentialRef`. Not touched, by the card's serial constraint: SKILL.md's `api` trigger row and the inbound-webhook `secret` row are #20569's; #20569 remains open. ## `skills/**` readings Tokens are `scripts/check-skills-token-ratchet.mjs` readings (ceil of utf8 bytes / 4); lines are `wc -l`. Before = `c9c182ed`, after = `6b3450a0` (the `skills/**` bytes are identical at `7d4fcc50`). | File | Lines before → after | Tokens before → after (ceiling) | |---|---|---| | `skills/objectstack-automation/SKILL.md` | 448 → 439 | 5785 → 5777 (5785, headroom 8) | | `skills/objectstack-automation/references/examples-flows.md` | 136 → 135 | 1436 → 1426 (1436, headroom 10) | | Whole package — every `skills/**/SKILL.md` | 4404 → 4395 | ratcheted authored total 143347 → 143329 | | Whole package — every `SKILL.md` plus the touched reference file | 4540 → 4530 (net −10 against the PM's +4 budget) | bundle total 153900 → 153882 | No ceiling row moved. `evals/flows-triggers-approvals.json` is untouched (1255 / 1255). ## Verification (all on head `6b3450a0`; exit codes captured before any pipe) Head `6b3450a0` is `7d4fcc50` (the review-round commit) merged with `origin/main` `91e8fa19`; the merge touched nothing under `skills/**`, recorded no regen deferral, and the diff against `origin/main` is exactly the two files above. The merge is the derivation tool's own prescription: at `7d4fcc50` it reported a STALE TREE (`origin/main` had moved to `eead9dcf` with `.github/workflows/release.yml` and `scripts/release-pending-publish.mjs` changed, both inputs of derived families). - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths) derived the same 25 commands at `b6041881`, `7d4fcc50` and `6b3450a0`; all 25 ran on `6b3450a0` with exit 0, recorded as `cmd :: exit N`, and `dispatch-gates --ran` answers "25 derived families accounted for — 25 run, 0 NOT-MEASURED" with no stale-tree warning: `check-ci-filter-parity`, `check-closing-keyword-parity` (+ `--self-test`), `check-comment-mask-corpus`, `check-doc-route-spelling --advisory` (+ `--self-test`), `check-skills-token-ratchet` (+ `--self-test`), lint `check:doc-formula-expressions`, spec `check:skill-docs`, spec `check:skill-refs`, root `check:agent-test-spelling`, `check:corpus-claim-drift`, `check:cross-package-test-inputs`, `check:doc-authoring`, `check:driver-memory-census`, `check:gitlink-declared`, `check:nul-bytes`, `check:pm-governed-merges`, `check:refd-timer-probe`, `check:role-word`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:skill-identifier-liveness`, `check:watch-hint-literal`. The same 25 also ran on `7d4fcc50`, all exit 0. - Implicated beyond the derived list: spec `check:skill-examples` (the edited example carries an `os:check` marker): exit 0 on `6b3450a0` — "259 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them" (skills + docs surface: 225 blocks), after `turbo run build --filter=@objectstack/spec --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/client --filter=@objectstack/client-react` under the verify lock. - Ablation of the edited block, both legs through `scripts/ablation-replace.mjs`, run at `b6041881` on the `examples-flows.md` blob `6e725a55` — the same blob at `6b3450a0`, so the reading stands (anchor hit 1 → 0 on disk, blob hash changed, restored to the `HEAD` blob with `git diff HEAD` empty; no build step involved because the checker reads the markdown itself): - `actionId:` → `actionIdX:` inside `connectorConfig`: exit 1 — `examples-flows.md:56:9 error TS2561: Object literal may only specify known properties, but 'actionIdX' does not exist in type '{ connectorId: string; actionId: string; input?: ... }'` (the `input` type is a Record of string to unknown, spelled in words here because the platform eats angle-bracket fragments). The block's `connectorConfig` shape is measured. - `type: 'connector_action'` → `type: 'connector_actionX'`: exit 0, still 259 green. The node `type` string is not constrained by the type-check (plugin-registered node types such as `approval` mean it admits any string), so that half of the example rests on the showcase app and the spec enum, not on this gate. Reported as observed. - A control-character grep (the C0 range and DEL) over both touched files: no match. No HTML comment and no angle-bracket fragment is added by this diff. - `check:skill-docs` / `check:skill-refs` are green without regeneration: the frontmatter is unchanged, so no generated artifact moved. ## Changeset `skip-changeset`, measured: `git grep -n -w skills -- '*/package.json'` → 0 hits at `6b3450a0` (positive control: `git grep -n '"files"' -- 'packages/*/package.json'` hits `packages/adapters/hono`, `packages/apps/account`, `packages/apps/setup`), so nothing under `skills/**` ships in any released package's `files[]`. Precedent: the last skills-only PR that touched these files (#19738) carried the same label. ## Acceptance notes - `SKILL.md` `:216` (a reference outside a filter — message body, `http` url, write payload — renders an empty string) is a template-interpolation statement, not a credential statement; left as is, the `http` row carries the steer. - The eval `evals/flows-triggers-approvals.json:47` expects an `http` node to set `timeoutMs`; that lesson now lives on the `http` row (it left the example when the node became a `connector_action`). - `content/docs/automation/flows.mdx:274`, which the services seat's wording guard said carried an over-reach, already routes by shape on `main` at `c9c182ed`; nothing to correct, and this PR does not touch it. - Noted, not filed: `check:doc-formula-expressions` and `check:skill-examples` each refuse (exit 3) on a fresh worktree until their producer packages are built; both say so loudly and name the build. Working as designed. ## 维护者速读(草稿) **改了什么**:`objectstack-automation` 技能的 `http` 节点行现在说明:flow 定义(含 `http` 节点的 `url` 与 `headers`)会原样提供给所有能读 flow 的成员——只有 `signingSecret` 与 start 节点的 `secret` 会被隐去——因此凭据不能写在那里;并按凭据所在位置给出去向:请求头凭据 → 声明式 connector 的 `auth.credentialRef`(`bearer` / `basic` / `api-key`),查询串密钥 → `api-key` 的 `paramName`,路径即密钥的 webhook → 带 token 的 connector(如 `slack`),再用 `connector_action` 调用。长示例里的 Slack incoming-webhook url 换成了 `slack` connector 的 `chat.postMessage`,与 showcase 示例应用的写法一致。「元数据优先、自定义代码只留给边缘集成」这条规则并入了 CRM 蓝图的首句(它在技能里没有别的落脚点)。为守住 token 上限,删掉的都是别处已有归属或原地重复的内容(如 `previous` / `record` 的说明缩成指向 objectstack-formula §5 的一行)。 **为什么改**:这是 #20590 家族的技能面。AI 作者直接从这份技能复制,原示例教的是把秘密写进 url;spec 描述、运行时描述与文档已在别的 PR 落地同一措辞,技能面是最后一块。 **风险与代价(含回滚)**:纯文档改动,不动运行时、不发布任何包(`skip-changeset` 已实测)。示例块经 `check:skill-examples` 类型检查通过,并用消融证明 `connectorConfig` 的形状确实在检。SKILL.md 剩余 token 余量为 8,下一张改此文件的卡(#20569)仍需自付 token。回滚即 revert 本 PR,无迁移。 **席位意见**:(留空) **你要做的**:`skills/**` 是 Tier H 受管面——需要维护者的 APPROVED review 后由席位落地;本 PR 保持 draft,不进队列、不开 auto-merge。 --- _Generated by [Claude Code](https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg)_ --------- Co-authored-by: os-warren <warren@objectstack.ai> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4b4ee88 commit 7a09eee

2 files changed

Lines changed: 30 additions & 40 deletions

File tree

‎skills/objectstack-automation/SKILL.md‎

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ metadata:
3838

3939
## Flows — Visual Logic Orchestration
4040

41-
A **Flow** is a directed graph of nodes that execute sequentially or in
42-
parallel. Flows are the primary automation building block in ObjectStack.
41+
A **Flow** is a directed graph of nodes — the primary automation building block.
4342

4443
### Flow Types
4544

@@ -53,8 +52,8 @@ parallel. Flows are the primary automation building block in ObjectStack.
5352

5453
### Flow Node Types
5554

56-
Flows are built from **20 built-in node types** (the `FlowNodeAction` seed set —
57-
plugins register more via `registerNodeExecutor`, e.g. `approval` below):
55+
Flows are built from **20 built-in node types** (plugins register more via
56+
`registerNodeExecutor`, e.g. `approval` below):
5857

5958
#### Control Flow
6059

@@ -85,7 +84,7 @@ plugins register more via `registerNodeExecutor`, e.g. `approval` below):
8584

8685
| Node | Purpose |
8786
|:-----|:--------|
88-
| `http` | Call an external HTTP API — canonical since protocol 11.0; `http_request` survives only as a deprecation-window alias |
87+
| `http` | Call an external HTTP API — always set `timeoutMs` (unset = no timeout). `url` and `headers` are served as written with the definition to every member who can read flows — only `signingSecret` and a start node's `secret` are withheld — so never a credential there. Route by shape — a header credential → a declarative connector (`bearer` / `basic` / `api-key`) with `auth.credentialRef`; a query-string key → `api-key` with `paramName`; a path-secret webhook url → a token-authenticated connector such as `slack` — called via `connector_action` |
8988
| `notify` | Send a notification through the messaging service (inbox channel by default) |
9089
| `connector_action` | Invoke a pre-built integration connector |
9190
| `script` | Call a **registered** function named by `config.function` (see *Valid-but-silently-wrong* #3) |
@@ -140,7 +139,7 @@ variables: [
140139
],
141140
```
142141

143-
→ Moved verbatim to [references/examples-flows.md](./references/examples-flows.md) § Flow Example — Auto-Escalate Overdue Cases.
142+
→ Moved to [references/examples-flows.md](./references/examples-flows.md) § Flow Example — Auto-Escalate Overdue Cases.
144143

145144
### Failure routing & `runAs`
146145

@@ -154,8 +153,6 @@ variables: [
154153
> replays the flow from the start — prefer a fault edge when the failure is
155154
> local. The handler reads `{<nodeId>.error}` (or run-wide `{$error}`). The run
156155
> then reports success, and the failed step stays in the trace.
157-
>
158-
> **It is not a way past a guardrail.**
159156
160157
| ROUTES (runtime failure) | Does NOT route (fatal either way) |
161158
|:--|:--|
@@ -221,9 +218,8 @@ a query instead of narrowing it.
221218

222219
## Valid-but-silently-wrong (passes build, fails at runtime)
223220

224-
These are *legal* metadata that authors — AI especially — get wrong. Most are now
225-
caught by `objectstack build` (a hard error, or an advisory warning), but write
226-
them right the first time:
221+
Legal metadata that authors — AI especially — get wrong; most are caught by
222+
`objectstack build`, but write them right the first time:
227223

228224
1. **Flow node VALUE interpolation uses SINGLE braces.** Value fields on a node's
229225
`config` (`fields`, `inputs`, notify `message`/`title`, …) interpolate
@@ -256,7 +252,7 @@ them right the first time:
256252
function fails loudly.
257253

258254
There is no other dispatch form: use **`notify`** for delivery, a
259-
**`connector_action`** or `http` webhook for Slack, a function for logic.
255+
**`connector_action`** on the `slack` connector for Slack, a function for logic.
260256

261257
**A flow `function` is a PURE compute step — it does NOT read/write the
262258
database.** It receives `ctx.input` and **returns** a value; `config.outputVariable`
@@ -309,17 +305,15 @@ them right the first time:
309305

310306
---
311307

312-
→ State Machines & Approvals moved verbatim to [references/state-machines-and-approvals.md](./references/state-machines-and-approvals.md) (State Machine — a `state_machine` validation rule · Approvals (Flow Nodes) · Send-back for revision · Recording a decision · Approver Types · Dynamic approvers (`type: 'expression'`) · Node Config (`ApprovalNodeConfigSchema`) · Branching, side-effects & rejection · Approval Best Practices) — its state-introspection route is `GET /api/v1/meta/object/:name/state/:field?from=:state`.
308+
→ State Machines & Approvals moved to [references/state-machines-and-approvals.md](./references/state-machines-and-approvals.md) (State Machine — a `state_machine` validation rule · Approvals (Flow Nodes) · Send-back for revision · Recording a decision · Approver Types · Dynamic approvers (`type: 'expression'`) · Node Config (`ApprovalNodeConfigSchema`) · Branching, side-effects & rejection · Approval Best Practices) — its state-introspection route is `GET /api/v1/meta/object/:name/state/:field?from=:state`.
313309

314310
---
315311

316312
## Triggers — Event-Driven Automation
317313

318314
A `record_change` flow fires automatically on a data event. There is **no
319315
standalone trigger object and no top-level `trigger` / `event` key** — the
320-
binding lives entirely in the flow's **`start` node `config`**, which the
321-
automation engine parses (`resolveTriggerBinding`) and wires to the matching
322-
ObjectQL lifecycle hook.
316+
binding lives entirely in the flow's **`start` node `config`**.
323317

324318
### Prerequisite — declare the capabilities your nodes need
325319

@@ -396,27 +390,25 @@ owns `label`, `type`, `nodes` and the `edges` `FlowSchema` requires):
396390
> `record-before-*` flow that throws silently blocks the write — give it a
397391
> user-facing message.
398392
399-
> **`previous`** and **`record`** are the CEL variables available in update
400-
> triggers — `previous.x` is the value before the change, `record.x` is the
401-
> value after. See [objectstack-formula](../objectstack-formula/SKILL.md).
393+
> `previous` (before the change) vs `record` (after) in an update trigger's
394+
> condition: see [objectstack-formula](../objectstack-formula/SKILL.md) §5.
402395
403-
→ Moved verbatim to [references/examples-flows.md](./references/examples-flows.md) § Time-relative triggers — scheduled per-record date sweep.
396+
→ Moved to [references/examples-flows.md](./references/examples-flows.md) § Time-relative triggers — scheduled per-record date sweep.
404397

405398
---
406399

407400
## CRM Automation Blueprint
408401

409-
For enterprise automation design, align with this CRM-style structure:
402+
Default approach for metadata apps — model the business lifecycle in
403+
Flow/Approval metadata first, reserve custom code for edge-case integrations —
404+
in this CRM-style structure:
410405

411406
| Automation Type | Typical Location | Pattern |
412407
|:--|:--|:--|
413408
| Screen flow | `src/flows/*.flow.ts` | Use explicit `variables`, node graph (`nodes` + `edges`), and decision branches |
414409
| Approval flow | `src/flows/*.flow.ts` | A flow with `approval` node(s); set `approvers` / `behavior` / `lockRecord` / `approvalStatusField` in node `config`, branch on `approve` / `reject` edges |
415410
| Flow registry | `src/flows/index.ts` | Export `allFlows: Flow[]` and register centrally in `defineStack({ flows })` |
416411

417-
Default approach for metadata apps: model business lifecycle in Flow/Approval
418-
metadata first; reserve custom code for edge-case integrations.
419-
420412
---
421413

422414
## Verify your work
@@ -432,7 +424,7 @@ os validate # CEL/predicate validation (record.<field> existence) + schema
432424
```
433425

434426
It runs the ADR-0032 gate over every condition, edge guard, validation and
435-
sharing rule, exiting non-zero. In a scaffolded project: `npm run validate`.
427+
sharing rule. In a scaffolded project: `npm run validate`.
436428

437429
---
438430

@@ -441,8 +433,7 @@ sharing rule, exiting non-zero. In a scaffolded project: `npm run validate`.
441433
The send-back shape has a graded eval at
442434
[evals/approvals/test-revise-loop.md](./evals/approvals/test-revise-loop.md).
443435

444-
See [references/_index.md](./references/_index.md) for the full list of Zod
445-
schemas (with one-line descriptions) — pointers into
446-
`node_modules/@objectstack/spec/src/`. Always `Read` the source for exact field
447-
shapes; do not rely on memory of property names.
436+
See [references/_index.md](./references/_index.md) for the Zod schemas (one-line
437+
descriptions, pointers into `node_modules/@objectstack/spec/src/`). Always `Read`
438+
the source for exact field shapes; do not rely on memory of property names.
448439

‎skills/objectstack-automation/references/examples-flows.md‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,8 @@ export const EscalateOverdueCasesFlow = defineFlow({
2626
id: 'start',
2727
type: 'start',
2828
label: 'Daily at 09:00',
29-
// The cadence lives HERE, on the start node's config — FlowSchema has NO
30-
// top-level `schedule` key (one there is a named parse error, not a silent
31-
// strip). A bare cron string also works: schedule: '0 9 * * *'. Do NOT use
32-
// the cron`…` tagged template — its envelope is not a recognized shape.
29+
// The cadence lives HERE — FlowSchema has NO top-level `schedule` key (a
30+
// named parse error). A bare cron string also works; a cron`…` tag does not.
3331
config: { schedule: { type: 'cron', expression: '0 9 * * *' } },
3432
},
3533
{
@@ -38,7 +36,6 @@ export const EscalateOverdueCasesFlow = defineFlow({
3836
label: 'Escalate Overdue Cases',
3937
config: {
4038
objectName: 'support_case',
41-
// which rows to update — `filter` is a `where` map, not filter triples
4239
filter: {
4340
status: { $in: ['new', 'open'] },
4441
due_date: { $lt: '{TODAY()}' }, // template token → today's date at run time
@@ -49,13 +46,15 @@ export const EscalateOverdueCasesFlow = defineFlow({
4946
},
5047
{
5148
id: 'notify_manager',
52-
type: 'http',
49+
type: 'connector_action',
5350
label: 'Notify Manager',
54-
config: {
55-
url: 'https://hooks.slack.com/services/...',
56-
method: 'POST',
57-
body: { text: 'Escalated overdue support cases.' },
58-
timeoutMs: 10000, // unset = NO timeout at all — always set one
51+
// NOT an `http` node with a Slack webhook url: the definition (`url` and
52+
// `headers` included) is served to every member who can read flows. The
53+
// `slack` connector (@objectstack/connector-slack) holds the bot token.
54+
connectorConfig: {
55+
connectorId: 'slack',
56+
actionId: 'chat.postMessage',
57+
input: { channel: '#support-leads', text: 'Escalated overdue support cases.' },
5958
},
6059
},
6160
{ id: 'end', type: 'end', label: 'End' },

0 commit comments

Comments
 (0)