Repository navigation
fix(service-automation): the http node's url and headers describes route an outbound credential to a connector's credentialRef, and the showcase says its webhook url is served (#20590) - #20672
Conversation
…ute an outbound credential to a connector's credentialRef, and the showcase says its webhook url is served (#20590) The http action descriptor's configSchema is the designer palette's authoring form. Its url and headers fields now say that the flow definition is served to every member who can read flows, and send an outbound credential to a connector_action on a declarative connector whose auth.credentialRef names the secret. The showcase's parallel+http fixture keeps its placeholder webhook url, and the comment beside it now says the url is served, so a real secret-bearing url never goes there. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f01f1d1ec9951d4ab49469b601ba7b770c41e7ed && git checkout f01f1d1ec9951d4ab49469b601ba7b770c41e7ed
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9a4b2bb38f9fb82c8e970d4f8d66d72379faa345 695bb749a0fe12d9899e19109f2e4515349c296f && git checkout -B drift-repro 9a4b2bb38f9fb82c8e970d4f8d66d72379faa345 && git merge --no-ff 695bb749a0fe12d9899e19109f2e4515349c296f
node scripts/docs-audit/affected-docs.mjs --json 9a4b2bb38f9fb82c8e970d4f8d66d72379faa345 |
…key to api-key credentialRef and a path-secret webhook to a token-authenticated connector (#20590) No credentialRef variant carries a secret in the url path, so the url describe no longer sends a path-secret webhook to auth.credentialRef. A query-string key goes to a declarative rest connector with api-key auth (paramName + auth.credentialRef); a webhook whose path is the secret is replaced by a token-authenticated connector such as slack. The code comment no longer claims the spec describes already carry the steer, and the changeset names the three routes. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
… 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>
…osition draws an author-time advisory that routes it to a connector (objectstack-ai#20654) (objectstack-ai#20698) Fixes objectstack-ai#20654 Clause-②: no This PR is the `packages/spec` + `packages/lint` face of objectstack-ai#20590's direction A (triage ruling `5891721503`, carried by the card and restated in `5891910265`). The services face (the `http` descriptor text and the showcase) is objectstack-ai#20590's own claim, and the docs and skill faces are objectstack-ai#20655 and objectstack-ai#20657. objectstack-ai#20590 is not closed by this PR. ## What changes A flow definition is served, as authored, to every member who can read flows. The read path withholds the credential slots the spec declares, but an open map or a url cannot be withheld: an ordinary value there is indistinguishable from a credential. So this PR steers and warns. Nothing is withheld and nothing is refused. - **One predicate, one home.** `@objectstack/lint` gains `isCredentialShapedLiteral(name, value)` in `packages/lint/src/credential-literal.ts`. It is the measured scanner's R1/R2 rule, unchanged: a credential-named key (R1) or an auth-scheme value (R2), on a non-blank string that carries no `{…}` template. The name lists are module-private. A test pins that no other shipped source file in the package holds them. - **One advisory.** `lintFlowCredentialLiterals` (rule id `flow-credential-literal`, `packages/lint/src/lint-flow-credential-literals.ts`) reports one `warning` per credential-shaped literal in three positions: an `http` node's `config.headers` entry, a query parameter of an `http` node's `config.url`, and a node's `connectorConfig.input` at any depth. Nodes nested in `try_catch` / `loop` / `parallel` regions are included. The finding names the key and the position, and it never carries the value. - **Routed by shape.** A header credential goes to a connector whose `auth` is `bearer` or a header `api-key`. A query-string key goes to `api-key` with `paramName`. A connector input goes to the connector's own `auth.credentialRef`. The route is in the message, which is what the `os validate` and `os lint` text faces print; the hint carries the full declaration. No text promises a variant that carries a secret in a url path. This follows the wording guard the services seat relayed in `5894445934`. - **Registered once.** There is one new `AUTHORING_RULES` entry, `tier: 'advisory'`, on all three commands, with `surfaces: ['cli', 'runtime-publish']` and `runtimeTypes: ['flow']`. The wiring guard's "every advisory rule really is advisory" check reads the rule's source for an `error` severity. The finding type is `severity: 'warning'`. - **Describes.** `HttpConfigSchema.headers` and a flow node's `connectorConfig.input` say the definition is served to every flow reader, and route an outbound credential as above. The wording changes; no shape changes. `content/docs/references/automation/{flow,io-node-config}.mdx` are regenerated with `gen:docs`. - **Changeset.** `@objectstack/lint` minor (new exports and a new advisory), `@objectstack/spec` patch (describe text only). Both packages' `dist` carry the change: measured by grep of `files[]` after the build, with a positive control. ## Mechanism hypotheses, measured 1. **Lit control on the base (`f4ce10c89d`): confirmed.** A flow with a credential-shaped literal in an `http` header, a url query key and a nested connector input got these answers: - The save door (`evaluateRuntimeAuthoringGate`, `state: 'active'`) returned `error: null, advisories: []`. The dark control (the same flow with `{var}` templates) returned the same. - The live-channel control, a `try_catch` with no `catch`, drew exactly one advisory, `flow-try-catch-without-catch`. - `os validate --json` exited 0 with `valid: true`. Its only warning was `No apps or plugins defined — this stack may not do much`, lit and dark alike. - `os lint --json` exited 0 with `passed: true` and `issues: []`, lit and dark alike. 2. **No door-side change: confirmed.** The registry entry alone reaches all three doors. `packages/cli/src/commands/*` and `runtime-authoring-gate.ts` are untouched. 3. **Published surface.** `packages/lint` has no api-surface or export-origins shards. The `.` entry gains `isCredentialShapedLiteral`, `lintFlowCredentialLiterals`, `FLOW_CREDENTIAL_LITERAL` and the type `FlowCredentialLiteralFinding`. `./runtime` exports nothing new. 4. **Derived artifacts moved.** - `check:docs-transcript-drift`: the four CLI transcripts print `Running author-time rules (48)`, up from 47. There is no generator; each line is set to the value the gate derives from `authoringRulesFor(cmd)`. - `check:docs`: the two reference pages above, via `gen:docs`. - No rule catalog exists. - `check:generated` reports all 15 artifacts up to date. 5. **Ablation: confirmed, in the predicted direction.** The ablation ran at `f7fe981913` through `scripts/ablation-replace.mjs` (WRAP mode). It made the predicate always return false, then rebuilt `@objectstack/lint`. - The dist preflight found the marker in 4 built files. - The lint pins went red on 33 tests: all 27 positive predicate cases and 6 rule pins (the pin set, severity, wording, array walk, reach through `runAuthoringRules` for each command, reach through `runRuntimeAuthoringRules`). The negatives, the draws-nothing controls, the registry entry, the export and the one-home pin stayed green, 21 in all. - The save-door pin went red on LIT and stayed green on DARK and draft. The `os validate` and `os lint` pins both went red. - Restore: the blob matched HEAD, `git diff HEAD` was empty, the marker was absent from all 14 dist files and the tree was clean. Then lint 54/54, save door 3/3 and CLI doors 2/2 all passed. ## Verification record (HEAD `ae5d8af6f8`, which includes current `main`) - The door readings after the change, on the same lit and dark fixtures: - `os validate` exits 0 with `valid: true`. Lit prints four `flow-credential-literal` warnings: the header, the url query key, the header inside the `try` region and the nested connector input. Dark prints none. - `os lint` exits 0 with `passed: true`. Lit reports the same four warnings; dark reports none. - The save door returns `error: null`. Lit carries one advisory per literal on the advisory channel; dark and draft carry none. - No finding carries a sentinel value. - The pins: - `packages/lint`: the pin set is the measured scanner's positive-control fixture (literal arm, doc-block arm, probe flow), carried over as the evaluated stack. It draws exactly 12 advisories, and the template, ordinary-value and `signingSecret` controls draw none. - The save door: `packages/metadata-protocol/src/runtime-authoring-gate.flow-credential-literal.test.ts`. - `os validate` / `os lint`: `packages/cli/test/flow-credential-literal-doors.e2e.test.ts`. It spawns the CLI, so it sits in the nightly tier by name; the per-PR half is the per-command `runAuthoringRules` pin in lint. - Tests: - `@objectstack/lint`: 117 files, 5433 tests passed, typecheck exit 0. - `@objectstack/metadata-protocol`: 190 files passed and 3 skipped (2786 tests passed, 19 skipped), typecheck exit 0. - `@objectstack/cli`: unit project 234 files, 3355 tests passed, typecheck exit 0. The door pin passes 2/2 under `OS_TEST_TIERS=nightly`. The per-PR integration project is left to CI. - Example apps: `os lint` draws 0 `flow-credential-literal` findings on `app-showcase`, `app-crm` and `app-todo`. - Gates: - `dispatch-gates --commands` derives 111 gates at `ae5d8af6f8`. All 111 ran with their exit codes captured before any pipe, and all exited 0. - `--ran` reconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN. - `check:type-check-debt` ran under the verify lock, because it runs its own closure build. - ESLint (a proven narrowing): the config's population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`. `--format json` over the 10 changed files in it reports 0 errors and 0 warnings. The config enables no type-aware linting, so this diff cannot move the verdict on any untouched file. ## Acceptance notes - **Boundaries of the rule.** It is the measured R1/R2 rule, unchanged, and it is deliberately not widened here. - It does not judge userinfo in a url, a secret carried in a url path (the incoming-webhook shape), or an `http` node's `body`. - A name like `X-Authorization` is missed by R1 unless its value opens with an auth scheme. - A setting under a credential-sounding name (a `session…` or `auth…` key holding a non-secret string) draws a false positive. That costs a line of reading, which is the ruling's trade. - **`--strict`** on `os validate` / `os lint` promotes this warning to a failure, exactly as it promotes every warning. No door treats it as an error otherwise. - **Staying in step.** The `http` descriptor's `configSchema` text in `service-automation` is objectstack-ai#20590's face (PR objectstack-ai#20672), and it is being tightened to the same shape routing. The landed flows-guide callout is filed separately for its url-path over-reach, and this PR does not copy it. - **The template test** reuses the `http` executor's interpolation token (a single-brace `{…}` span with no brace inside, as `template.ts` interpolates it). The module cites it; no test pins it against that file. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…#20682) (objectstack-ai#20704) Fixes objectstack-ai#20682 Clause-②: no Docs-only, `content/docs/automation/flows.mdx`, two spots. 1. The `http` node callout no longer sends every credential to a declarative connector's `auth.credentialRef`. It routes by shape: a header credential to `bearer`/`basic`/`api-key`; a query key to `api-key` with `paramName`; a secret in the url path has no `credentialRef` variant, so it goes to a token-authenticated connector such as `slack`, whose bot token is supplied by host code (the Slack connector is plugin-registered, not a declarative `connectors:` provider; only `rest`, `openapi`, `mcp` register providers). The first sentence and the `signingSecret` / start-node `secret` sentence are unchanged. 2. The retired-shapes row for `actionType: 'slack'` keeps the `connector_action` + Slack connector and drops the incoming-webhook `http` alternative. Wording is in step with objectstack-ai#20672's `http` descriptor and objectstack-ai#20698's describes. ## Acceptance notes - `pnpm --filter @objectstack/spec run check:skill-examples` (client dist not built) and `check:gitlink-declared` were not run: NOT MEASURED. The other derived gates ran green (33 of 40 derived; the rest are checker self-tests), `doc-security-posture` and `docs-spec-enumerations` included. - No changeset: docs-only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20590
Clause-②: no
What this changes
This is the
domain:servicesface of triage's direction A on this card (ruling5891721503, "Direction: A, taken whole"), scoped by the claim5891908897. It carries two guidance edits and nothing else. Nothing new is withheld on read.httpaction descriptor'sconfigSchema(packages/services/service-automation/src/builtin/http-nodes.ts). Theurlfield read "Target URL" and theheadersfield read "Request headers". Both now say that the value is stored in the flow definition, that a flow definition is served to every member who can read flows, and that an outbound credential goes to aconnector_actionon a declarative connector whoseauth.credentialRefnames the secret. (Seat's correction after patch round 1: that holds forheaders. Forurl, a query-string key goes to a declarativerestconnector withapi-keyauth (paramName,auth.credentialRef), and a webhook whose path is the secret goes to a token-authenticated connector such asslack, since nocredentialRefvariant carries a path-borne secret.) Of this node's config, onlysigningSecretis withheld on read (FLOW_NODE_CREDENTIAL_KEYS), sourlandheadersare served as authored. A code comment beside the two fields records that.examples/app-showcase/src/automation/flows/index.ts, theslack_postnode inshowcase_fan_out_notify). The branch comment used to teach "post through an incoming webhook". It now sends a real post through a connector, asTaskCompletedSlackFlowalready does. A comment directly above the placeholder url says that the url is served with the flow definition to every member who can read flows, and that an incoming-webhook url's path is its secret, so a real one never goes there.A changeset (
patch,@objectstack/service-automation) covers the descriptor text, which ships in that package'sdist. The showcase isprivate, so it takes no changeset.Mechanism assumptions, measured at
992c9ac87A1: where the descriptor text reaches an author.
AutomationEngine.getActionDescriptors()backs the runtime automation domain'sGET /automation/actions, which is the designer palette's feed. The client SDK calls it asautomation.listActions. TheconfigSchemait serves is what the Studio property form is built from (io-node-form-zod-ledger.test.tsheader).HttpDispatcher, over a realAutomationEnginewithinstallBuiltinNodes, answeredGET /automation/actionswith200and 17 descriptors. Thehttpdescriptor'surlandheadersdescriptions are the new text.description. That lives in the sibling UI repository, which is outside this run's scope.connector_action: its descriptor publishes noconfigSchema. That is deliberate: the executor reads only theFlowNodeSchema.connectorConfigsibling block. The same probe answeredconfigSchema: nullfor it, andio-node-form-zod-ledger.test.tsandconfig-schemas.test.tsboth pin it. So there is no services-sideinputdescribe to steer. TheconnectorConfig.inputdescribe lives inpackages/spec, which is spec+lint: flowhttpheaders / connector input / url describes route credentials tocredentialRef, and one exported predicate draws an author-time advisory on a credential-shaped literal (the spec half of #20590) #20654's face.httpheaders / connector input / url describes route credentials tocredentialRef, and one exported predicate draws an author-time advisory on a credential-shaped literal (the spec half of #20590) #20654 will carry, in substance: a flow definition is served to every member who can read flows, and an outbound credential goes to a declarative connector'sauth.credentialRef. It also agrees with the flows guide sentence docs(automation): say an http node's url and headers are served; route credentials to connector credentialRef #20663 landed. The exact words are this PR's own.A2: the showcase site. The showcase could not simply take the connector route.
showcase_fan_out_notifyis the platform checklist'sparallel+httpfixture (docs/qa/platform-checklist/areas/automation.json,automation.flow-node-type-matrix). That item locates thehttpvariant's step in this flow's runs. Turningslack_postinto aconnector_actionwould move the tested surface.credentialReffails the boot when the reference does not resolve (resolveInstanceAuth, ADR-0097 §3). That is why every showcase instance declaresauth: { type: 'none' }. Adding one would also touchsrc/system/connectors/index.ts, which is outside this card's file surface.992c9ac87to byte-identical JavaScript (sha256 prefix6197a04e663ec0d8, 31,595 bytes). The positive control is a one-character change in the same url, which does change the output.Tests, at
992c9ac87@objectstack/service-automation: 154 test files and 1,913 tests passed (vitest run --maxWorkers=2).typecheckpassed, includingcheck:test-typecheck.tsconfig.test.jsoncompileshttp-nodes.test.ts(counted with--listFiles).http-nodes.test.ts: theurlandheadersdescriptions each nameauth.credentialRefandconnector_action. It asserts the named route, not the prose.scripts/ablation-replace.mjsin wrap mode plus a shell trap. The mutation put both descriptions back to their pre-fix text. On disk:auth.credentialRefcount 2 to 0, and the old one-line fields 0 to 1 each. Result: exactly the two new cases failed, "2 failed, 8 passed", with the messages "expected 'Target URL' to contain 'auth.credentialRef'" and "expected 'Request headers' ...". Restore proof: blob67f3d9d05a64equals the HEAD blob, andgit diff HEADis empty. The test imports the source file directly, so no build sits in the path.dist/index.jsanddist/index.cjseach carry the new text twice, and the oldheadersdescription zero times. The positive control, the unchangedmethoddescription, appears once in each.files[]isdist,README.md,CHANGELOG.md.@objectstack/example-showcase:typecheckpassed, and the full suite passed: 29 files, 385 tests..tsfiles: 3 files, 0 errors, 0 warnings (eslint--no-inline-config --format json). The narrowing is sound for three reasons.--print-configresolves a rule set for each file, so none is ignored. NoparserOptions.projectorprojectServiceis set, so linting is not type-aware. A diff therefore cannot move any untouched file's verdict. The fullpnpm lintis CI's.Gates
node scripts/pm/dispatch-gates.mjs --commandsat992c9ac87derived 63 commands. All 63 were run with each exit code captured before any pipe, and all exit 0.--ranreconciles "63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN", with every code recorded.check:dual-build-cjs-loadsandcheck:type-check-debtfirst answeredPREREQUISITE NOT MET(exit 3). They were re-run after a turbo build of every package, and both then passed.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casingandpnpm check:filter-alias-parity.origin/mainhas moved five commits past this branch's base. None of those commits touches a path in this diff.Acceptance notes
content/docs/automation/flows.mdxstill lists "anhttpnode posting to an incoming webhook" as the replacement for the retired SlackactionType, in its retired-shapes table. The docs face's PR recorded the same line. It is outside this card's file surface.httpheaders / connector input / url describes route credentials tocredentialRef, and one exported predicate draws an author-time advisory on a credential-shaped literal (the spec half of #20590) #20654 (spec describes, and the lint predicate and advisory) and skills(objectstack-automation): the http example routes an outbound credential tocredentialRefand stops teaching a secret-bearing webhook url (the skills half of #20590) #20657 (the automation skill) remain open, and each carries its own face. The flows guide face landed separately.Patch round 1 (the seat's append)
5894416988):ConnectorInstanceAuthSchemahas no variant for a secret in the url path, so theurldescribe no longer sends a path-secret webhook toauth.credentialRef. It routes by shape:restconnector withapi-keyauth (paramName,auth.credentialRef);slackbot token).connector-auth.zod.ts,plugin.tsresolveInstanceAuth,rest-connector.tsapplyAuth,slack-connector.ts).headersdescribe is unchanged. The code comment no longer claims the spec describes already match (spec+lint: flowhttpheaders / connector input / url describes route credentials tocredentialRef, and one exported predicate draws an author-time advisory on a credential-shaped literal (the spec half of #20590) #20654 is open).695bb749:service-automation1913 passed,typecheckexit 0. The ablation of the url clause turned exactly the url pin red, with the headers pin green, and the restore was proven.distcarries the new clause. 63 / 63 derived gates and the 4 ⛔ rosters exit 0.flows.mdxcallout and its retired-shapes row are docs(automation): flows.mdx routes a secret-bearing webhook url toauth.credentialRef, which no connector auth variant can carry, and still offers anhttpnode posting to an incoming webhook as the Slack replacement #20682. spec+lint: flowhttpheaders / connector input / url describes route credentials tocredentialRef, and one exported predicate draws an author-time advisory on a credential-shaped literal (the spec half of #20590) #20654 and skills(objectstack-automation): the http example routes an outbound credential tocredentialRefand stops teaching a secret-bearing webhook url (the skills half of #20590) #20657 carry wording guards.Generated by Claude Code