Repository navigation
Commit eac538c
docs(skills): objectstack-automation calls the api flow secret required and routes explicit-only starts to autolaunched (#20796)
Fixes #20569
Clause-②: no
`skills/objectstack-automation/SKILL.md` still described the `api` flow
kind and its inbound-hook `secret` as they were before PR #20551: the
Flow Types row said a `type: 'api'` flow is "invoked explicitly via the
API / `engine.execute()`, **or** bound as an inbound webhook", and the
`secret` row called the HMAC secret "strongly recommended — without it
unsigned posts are accepted and a warning is logged". Both are false on
`main`. Tier H (`skills/**`): a draft PR for the maintainer's hand — no
seat readies, queues or arms it.
## What changed — one file, `skills/objectstack-automation/SKILL.md` (+5
/ −6)
| Line on `main` `7a09eee1` | Was | Now |
|---|---|---|
| `:51` Flow Types `api` row | explicit invocation **or** inbound
webhook, route inline | "Inbound **webhook** — every `api` flow is bound
to its hook endpoint and needs a start-node `secret` (see *Inbound
webhook triggers* below); a flow only ever started explicitly is
`autolaunched`" |
| `:342` | "An `api` flow **can be** bound to an inbound HTTP endpoint"
| "**is** bound" — every `api` flow is |
| `:344-345` | "(the start `config` is a free-form record, so these keys
are read at runtime, not Zod-validated)" | "(the start `config` is a
free-form record with no Zod shape)" — `secret` is now judged before
runtime, so only the still-true half stays |
| `:350` `secret` row | "Strongly recommended — without it unsigned
posts are accepted and a warning is logged" | "**Required** — without a
non-blank one the flow is refused at registration (`os validate` too)
and never armed at boot; the signature goes in
`x-objectstack-signature`" |
| `:352` Signature bullet | "sender sends `x-objectstack-signature:
sha256=…`" | the value shape only (`sha256=` plus the hex) — the header
name now lives in the `secret` row |
Untouched on purpose: the `http` row (`:87`) and the examples-flows
Slack node (PR #20778's same-day churn on this file). Kept abstract
under the security disclosure rule: no request recipe was added; the
bullet lost text.
## Measured at the code (`origin/main` `7a09eee1`)
Every claim in the new text was read from the runtime, none recalled:
- **The header.** `packages/triggers/trigger-api/src/plugin.ts:89` reads
`c.req.header('x-objectstack-signature')`; `api-trigger.ts:78-81`
(`verifySignature`) compares it, constant-time, with `sha256=` plus the
hex HMAC-SHA256 of the raw body under the start-node secret.
- **`api` is the inbound-webhook kind; there is no explicit-only `api`
form.** `packages/spec/src/automation/flow-trigger-kind.ts:83`: `if
(f.type === 'api' || triggerType === 'api') return 'api'`, and
`AutomationEngine.deriveTriggerBinding`
(`packages/services/service-automation/src/engine.ts:3526`) binds from
that resolver, so every `type: 'api'` flow is handed to `trigger-api`. A
`type: 'autolaunched'` flow with no start-node binding resolves to no
kind (`:84`) — the explicit-only form. The enum is `['autolaunched',
'record_change', 'schedule', 'screen', 'api']`
(`packages/spec/src/automation/flow.zod.ts:1060`).
- **Refused at registration, never armed at boot.** `engine.ts:4311`
calls `validateApiTriggerSecret` (`:10036-10052`), which throws on an
`api` binding whose start node has no non-blank `config.secret`; every
`registerFlow` call site try/catches per flow, so at boot the flow is
skipped loudly. `trigger-api`'s own `start()` refuses the same binding
before anything is stored or subscribed (`api-trigger.ts:133-143`: "not
armed").
- **Every authoring door.** The `/automation` write doors answer the
throw with `VALIDATION_FAILED_STATUS`
(`packages/runtime/src/domains/automation.ts:2174-2178`). `os validate`
runs `runAuthoringRules('validate', …)`
(`packages/cli/src/commands/validate.ts:461`) over `AUTHORING_RULES`,
which carries `validateFlowApiTriggerSecret`
(`packages/lint/src/authoring-rules.ts:1170-1195`: `tier: 'gating'`,
`commands: ALL`) and answers `flow-api-trigger-secret-missing` at
`error` (`validate-flow-trigger-readiness.ts:875-895`). `/meta` runs the
same table and throws its 422 `INVALID_METADATA`
(`packages/metadata-protocol/src/protocol.ts:4933-4941`).
- **Exposure.**
`packages/services/service-automation/src/flow-credential-projection.ts:129`
withholds the start node's `secret` from every served definition; the
skill already says so at `:87`, so the row does not repeat it.
## `skills/**` readings (token = ceil(utf8 bytes / 4), the ratchet's own
convention)
| Reading | Before (`7a09eee1`) | After (`32847a29`) | Delta |
|---|---|---|---|
| `skills/objectstack-automation/SKILL.md` | 439 lines · 23106 bytes ·
5777 tokens | 438 lines · 23125 bytes · 5782 tokens | −1 line · +19
bytes · +5 tokens (ceiling 5785, headroom 3) |
| Whole package — the 54 hand-authored files the ratchet covers | 12786
lines · 143329 tokens | 12785 lines · 143334 tokens | −1 line · +5
tokens |
| Whole package — every `SKILL.md` | 4395 lines | 4394 lines | −1 line |
Line budget (PM-set: net +2 at most across the package): net **−1**. No
ceiling raised and none lowered (the file grew by 5 tokens). No re-wrap:
the one removed line is the `:345` clause replaced by its true half. The
new text is paid by three in-file deletions, each of which keeps its
home in this same file — the hook route is now stated once (`:343`, in
the section the `api` row points to); "sender sends
`x-objectstack-signature: `" folded into the `secret` row; "so these
keys are read at runtime, not Zod-validated" cut to "with no Zod shape".
Nothing left the published package.
## Verification
Gates derived from the change with `node scripts/pm/dispatch-gates.mjs
--repo objectstack-ai/objectstack --commands` at head `32847a29` (no
paths: the change set read from the merge base) and reconciled with
`--ran`: **24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN**, every recorded
exit code 0. The first run of `pnpm --filter @objectstack/lint run
check:doc-formula-expressions` answered exit 3 (PREREQUISITE NOT MET:
`@objectstack/formula` and `@objectstack/lint` unbuilt); it was re-run
after `pnpm exec turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` under the verify lock (`VERDICT command-exit
0`) and answered exit 0 — the exit-3 run is a non-measurement, not a
red. Also green outside the derivation: `pnpm --filter @objectstack/spec
run check:skill-refs`. `check:skills-token-ratchet` prints
"skills/objectstack-automation/SKILL.md is 5782 tokens (ceiling 5785;
headroom 3)". `check:skill-examples` is not owed: no edited block
carries an `os:check` marker. No control bytes in the file. No package
test or typecheck owed: the diff touches no package.
## Acceptance notes
Census of `skills/**` and `content/docs/**` for sentences describing the
`api` trigger or its secret (case-insensitive grep over `unsigned post`,
`strongly recommended`, `invoked explicitly`, `explicit-only`,
`hooks/:flowName`, `x-objectstack-signature`, `inbound webhook`, `type:
'api'`, `api … secret`, `autolaunched`; `content/docs/references/` and
`content/docs/releases/` excluded as generated / release-owned):
- `skills/**` outside this file: no sentence about the `api` flow
trigger or its secret. The `type: 'api'` hits in
`skills/objectstack-ui/rules/actions.md:22,38` and
`skills/objectstack-ai/SKILL.md:158` are the UI action kind, not the
flow kind. `skills/objectstack-automation/references/*` and `evals/*`
carry no `api`-flow or `secret` sentence.
- `content/docs/**`: no sentence calls the secret optional or `type:
'api'` explicit-only, so nothing there is false in the card's sense. Two
observations, noted and not filed:
`content/docs/automation/flows.mdx:92` (`api` — "Exposed as an API
endpoint" / "HTTP request") is true but names neither the hook nor the
secret; `content/docs/automation/webhooks.mdx:733-736` ("Inbound
webhooks … reintroduce it only alongside a real inbound runtime") is the
outbound protocol's non-goals list written before `trigger-api`
(ADR-0041 Tier 1) and reads as if no inbound runtime existed — stale,
not false; carrier: none. `content/docs/releases/v17/17-5.mdx` already
states the requirement correctly.
## 维护者速读(草稿)
**改了什么** — `skills/objectstack-automation/SKILL.md` 里两处过时说法:Flow Types
表的 `api` 行不再说 `type: 'api'` 可以「只显式调用」,改为「入站 webhook,每个 `api` 流都绑到它的 hook
端点、都要 start 节点的 `secret`;只显式启动的流是 `autolaunched`」;`secret`
行由「强烈建议」改为「必填」,写明缺失时的真实后果(注册时拒绝、启动时不装载),并点名签名头
`x-objectstack-signature`。另外三处小改是为 token 上限付账:hook
路由只在下方章节写一次、签名要点只留值的形状、「运行时才读取、不经 Zod 校验」只保留仍成立的后半句。
**为什么改** — PR #20551 之后,运行时所有写入口(`/automation` 写门、`os
validate`、`/meta`、引擎注册、`trigger-api` 装载)都拒绝没有非空 `config.secret` 的 `api`
流;技能包却仍在教 AI 写一个运行时必拒收的流,并暗示 `type: 'api'`
有「只显式调用」的形态。每条新句子都从代码读出,正文附行号。
**风险与代价(含回滚)** — 纯文档改动,不发布任何包(`skills/**` 不在任何包的 `files[]` 内,打
`skip-changeset`)。token 上限 5785 内(现 5782,余量 3),行数净 −1,上限未动。风险仅限措辞;回滚为
`git revert` 单个 commit,无连带。
**席位意见** — (留空)
**你要做的** — 以维护者身份审阅并合并这个 draft PR(Tier H:席位不得 ready / queue /
auto-merge)。若想保留「read at runtime, not Zod-validated」原句,需另删等量内容守住 token
上限,请在评审中指出。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 157baa7 commit eac538c
1 file changed
Lines changed: 5 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
339 | 339 | | |
340 | 340 | | |
341 | 341 | | |
342 | | - | |
| 342 | + | |
343 | 343 | | |
344 | | - | |
345 | | - | |
| 344 | + | |
346 | 345 | | |
347 | 346 | | |
348 | 347 | | |
349 | 348 | | |
350 | | - | |
| 349 | + | |
351 | 350 | | |
352 | | - | |
| 351 | + | |
353 | 352 | | |
354 | 353 | | |
355 | 354 | | |
| |||
0 commit comments