docs(skills): objectstack-automation calls the api flow secret required and routes explicit-only starts to autolaunched - #20796
Conversation
…ed and routes explicit-only starts to autolaunched The `api` Flow Types row said a `type: 'api'` flow could be invoked explicitly OR bound as an inbound webhook; the engine binds every `api`-kind flow to the inbound trigger, so the explicit-only form is `autolaunched`. The `secret` row called the HMAC secret "strongly recommended"; the runtime refuses an `api` flow with no non-blank `config.secret` at registration (`/automation` doors, `os validate`, `/meta`) and `trigger-api` never arms it. The row now says so and names the header the signature goes in, read from `trigger-api`'s handler. Paid in-file: the hook route stays stated once (the section the row points to), the signature bullet keeps only the value shape, and the "read at runtime, not Zod-validated" clause — now false for `secret` — keeps only its true half. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg
Contract reviewServed-tier: Inputs: card #20569 (body + its three comments, the dev report ① Derived judgmentsAccept-set and public-surface changes implied by the diff: none. One published skill file edited; no spec key, export, runtime behaviour, route or generated artifact moves. Every statement the changed lines make, judged at the code on
Deleted clauses, each judged for a surviving home:
Contradictions: none found. In-skill: Security disclosure constraint: right. The head states the route (once), the header name, the value shape and "GitHub/Stripe style"; Token ratchet, measured at both trees: 23106 → 23125 bytes, 5777 → 5782 tokens (ceil(bytes/4)) against the ceiling 5785 at ② Semver level
③ Boundary flagsDev report
Other flags: Tier H ( Check-runs on the head, read 2026-09-30T07:11:41Z — 34 runs: 20
Implemented-by: VERDICT: PASS |
维护者速读(终稿)— PR #20796 · automation 技能:
|
Fixes #20569
Clause-②: no
skills/objectstack-automation/SKILL.mdstill described theapiflow kind and its inbound-hooksecretas they were before PR #20551: the Flow Types row said atype: 'api'flow is "invoked explicitly via the API /engine.execute(), or bound as an inbound webhook", and thesecretrow called the HMAC secret "strongly recommended — without it unsigned posts are accepted and a warning is logged". Both are false onmain. 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)main7a09eee1:51Flow Typesapirowapiflow is bound to its hook endpoint and needs a start-nodesecret(see Inbound webhook triggers below); a flow only ever started explicitly isautolaunched":342apiflow can be bound to an inbound HTTP endpoint"apiflow is:344-345configis a free-form record, so these keys are read at runtime, not Zod-validated)"configis a free-form record with no Zod shape)" —secretis now judged before runtime, so only the still-true half stays:350secretrowos validatetoo) and never armed at boot; the signature goes inx-objectstack-signature":352Signature bulletx-objectstack-signature: sha256=…"sha256=plus the hex) — the header name now lives in thesecretrowUntouched on purpose: the
httprow (: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/main7a09eee1)Every claim in the new text was read from the runtime, none recalled:
packages/triggers/trigger-api/src/plugin.ts:89readsc.req.header('x-objectstack-signature');api-trigger.ts:78-81(verifySignature) compares it, constant-time, withsha256=plus the hex HMAC-SHA256 of the raw body under the start-node secret.apiis the inbound-webhook kind; there is no explicit-onlyapiform.packages/spec/src/automation/flow-trigger-kind.ts:83:if (f.type === 'api' || triggerType === 'api') return 'api', andAutomationEngine.deriveTriggerBinding(packages/services/service-automation/src/engine.ts:3526) binds from that resolver, so everytype: 'api'flow is handed totrigger-api. Atype: '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).engine.ts:4311callsvalidateApiTriggerSecret(:10036-10052), which throws on anapibinding whose start node has no non-blankconfig.secret; everyregisterFlowcall site try/catches per flow, so at boot the flow is skipped loudly.trigger-api's ownstart()refuses the same binding before anything is stored or subscribed (api-trigger.ts:133-143: "not armed")./automationwrite doors answer the throw withVALIDATION_FAILED_STATUS(packages/runtime/src/domains/automation.ts:2174-2178).os validaterunsrunAuthoringRules('validate', …)(packages/cli/src/commands/validate.ts:461) overAUTHORING_RULES, which carriesvalidateFlowApiTriggerSecret(packages/lint/src/authoring-rules.ts:1170-1195:tier: 'gating',commands: ALL) and answersflow-api-trigger-secret-missingaterror(validate-flow-trigger-readiness.ts:875-895)./metaruns the same table and throws its 422INVALID_METADATA(packages/metadata-protocol/src/protocol.ts:4933-4941).packages/services/service-automation/src/flow-credential-projection.ts:129withholds the start node'ssecretfrom 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)7a09eee1)32847a29)skills/objectstack-automation/SKILL.mdSKILL.mdLine 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
:345clause 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 theapirow points to); "sender sendsx-objectstack-signature:" folded into thesecretrow; "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 --commandsat head32847a29(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 ofpnpm --filter @objectstack/lint run check:doc-formula-expressionsanswered exit 3 (PREREQUISITE NOT MET:@objectstack/formulaand@objectstack/lintunbuilt); it was re-run afterpnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lintunder 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-ratchetprints "skills/objectstack-automation/SKILL.md is 5782 tokens (ceiling 5785; headroom 3)".check:skill-examplesis not owed: no edited block carries anos:checkmarker. No control bytes in the file. No package test or typecheck owed: the diff touches no package.Acceptance notes
Census of
skills/**andcontent/docs/**for sentences describing theapitrigger or its secret (case-insensitive grep overunsigned post,strongly recommended,invoked explicitly,explicit-only,hooks/:flowName,x-objectstack-signature,inbound webhook,type: 'api',api … secret,autolaunched;content/docs/references/andcontent/docs/releases/excluded as generated / release-owned):skills/**outside this file: no sentence about theapiflow trigger or its secret. Thetype: 'api'hits inskills/objectstack-ui/rules/actions.md:22,38andskills/objectstack-ai/SKILL.md:158are the UI action kind, not the flow kind.skills/objectstack-automation/references/*andevals/*carry noapi-flow orsecretsentence.content/docs/**: no sentence calls the secret optional ortype: '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 beforetrigger-api(ADR-0041 Tier 1) and reads as if no inbound runtime existed — stale, not false; carrier: none.content/docs/releases/v17/17-5.mdxalready 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