Repository navigation
docs(automation): every sender-facing text for an inbound api hook teaches the timestamped x-objectstack-signature form only - #22846
Conversation
…aches the timestamped x-objectstack-signature form only The skill's Inbound webhook section, the flow-api-trigger-secret-missing hint and its os explain entry, the showcase example comment and the platform checklist's intake recipe teach t=UNIX_SECONDS,v1=HEX — the HMAC-SHA256 of UNIX_SECONDS.RAW_BODY under the flow's secret, refused more than 300 seconds from the server clock — and no longer the body-only form. The outbound contract and the inbound verifier are unchanged. The skill's token ceiling is paid by two same-file deletions of text restated beside it. Claude-Session: https://claude.ai/code/session_011u73oxZ5X95qrARPTeoU6v Co-authored-by: Claude <noreply@anthropic.com>
…bound-hook-timestamped-guidance
📓 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 — 4 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 6c8096cb6667eb61f71549f5e6d4a30e37a52172 && git checkout 6c8096cb6667eb61f71549f5e6d4a30e37a52172
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 31b5a5f7f51b7a7fd1131299a5b1dbe95910bc4f caf9fa094761dea1da403431c9b05aabd89ed0c3 && git checkout -B drift-repro 31b5a5f7f51b7a7fd1131299a5b1dbe95910bc4f && git merge --no-ff caf9fa094761dea1da403431c9b05aabd89ed0c3
node scripts/docs-audit/affected-docs.mjs --json 31b5a5f7f51b7a7fd1131299a5b1dbe95910bc4f |
维护者速读(终稿)· PR #22846 · #22825席位 2( 改了什么:按你裁的 B(#22805「同意」)第 1 步,把五处教「入站 webhook 发送方怎么签名」的文本全部改成只教带时间戳的新形式 为什么改:旧形式只签正文,截获的请求可以无限重放;新形式已经在门口可用,但所有教发送方的文本还在教旧形式。先把写法改对,再由 #22826 关掉旧形式的入口(它 风险与代价(含回滚):纯文本。技能文件 token 余量从 2 降到 1,靠删掉两句与上下文重复的话付账(api 行的「每个 api 流程绑定到它的 hook 端点」由本节首句说过;Queue-backed 行的「需要 queue 服务(见前提)」由前提一节说过)。回滚 = revert 本 PR。 席位意见:ACCEPT。五处与 你要做的:同意即给一个 APPROVED review;落地由席位走队列。 Generated by Claude Code |
Contract reviewServed-tier: Reviewed by the skills seat 2 in seat ( ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #22825
Clause-②: no
Ruling B on #22805 (maintainer 「同意」, comment 6108492902), step 1: every text that teaches a sender of an inbound
apihook teaches the timestampedx-objectstack-signatureform only. The form is the onesignHttpBodyAtinpackages/core/src/security/http-signature.tswrites —t=UNIX_SECONDS,v1=HEX, where HEX is the HMAC-SHA256 ofUNIX_SECONDS.RAW_BODYunder the flow's secret, and the door refuses atmore thanHTTP_SIGNATURE_TOLERANCE_SECONDS(300) from its clock. The inbound verifier,packages/core/**,packages/triggers/**,packages/services/**and the outbound contract (content/docs/automation/webhooks.mdx§6.2, the messaging outbox, the flowhttpnode, body-onlysignHttpBody) are not touched. The door's refusal of the body-only form is #22826, which isBlocked-by:this card and unblocks on merge.The five positions and the changeset
skills/objectstack-automation/SKILL.md, Inbound webhook section, Signature line (Tier H)t=UNIX_SECONDS,v1=HEX;v1= HMAC-SHA256 ofUNIX_SECONDS.RAW_BODYundersecret; refused whentis over 300 s from the server clockpackages/lint/src/rule-explanations.ts,flow-api-trigger-secret-missingexplanation (os explain)ADR-0041andHMACkept, which the explanation pin readspackages/lint/src/validate-flow-trigger-readiness.ts, the rule'shintmessage, rule id, severity, path and judgement are unchangedexamples/app-showcase/src/automation/flows/index.ts, theInboundTaskWebhookFlowdoc commentsignHttpBodyAt(rawBody, secret, Math.floor(Date.now() / 1000))named as the writer, the 300 s refusal nameddocs/qa/platform-checklist/areas/automation.json, itemautomation.trigger-type-matrix, theapistepsignHttpBodyAt(rawBody, 'showcase-webhook-secret', Math.floor(Date.now() / 1000));revision2 → 3 with a history entry.changeset/22825-inbound-hook-timestamped-guidance.md@objectstack/lintpatch +@objectstack/skillspatch (@objectstack/example-showcaseis private;docs/qais not a package)Not changed on purpose:
automation.jsonline 429 (another item's step, "a valid x-objectstack-signature HMAC", names no form);content/docs/releases/**andpackages/*/CHANGELOG.md(release-owned); the deployment docs'#sha256=artifact pin (a different thing);skills/objectstack-automation/evals/**(no eval names the signature:git grep -i "sha256|signature|x-objectstack" -- skills/objectstack-automation/evalsis empty).Done-when readings
Grep set
FIVE= the five files above. Trees named by commit.origin/maind8c7d3864):git grep -n "sha256=" -- FIVE→ 5 hits, one per file (SKILL.md:351,rule-explanations.ts:1463,validate-flow-trigger-readiness.ts:937,flows/index.ts:1581,automation.json:557).git grep -c "v1=" -- FIVE→automation.json2,flows/index.ts1,rule-explanations.ts1,validate-flow-trigger-readiness.ts1,SKILL.md1;git grep -c "signHttpBodyAt" -- packages/core/src/security/http-signature.ts→ 6.git grep -c "sha256=" -- skills packages/lint examples docs/qa content/docs packages/triggers packages/core, this branch):webhooks.mdx5 (outbound contract, kept by the ruling), deployment docs 10 (the#sha256=artifact pin),releases/v17/17-6.mdx1 (history),packages/core/CHANGELOG.md1 (release-owned),http-signature.ts5 + its test 8 andapi-trigger.ts2 + its test 4 (the verifier and its pins — trigger-api: the inbound hook door refuses the body-onlysha256=signature, so only the timestamped, replay-protected form verifies (ruling B on #22805, step 3) #22826's). None is a sender-facing inbound-hook text.skills/**readings (token = ceil(utf8 bytes / 4), the ratchet's convention)d8c7d3864)skills/objectstack-automation/SKILL.mdcheck-skills-token-ratchetgreenskills/objectstack-automation/**/*.mdskills/**/SKILL.mdThe Signature line grew by 103 bytes and is paid by two same-file deletions of text restated beside it, not by re-wrapping: the
apirow of the Flow Types table loses "everyapiflow is bound to its hook endpoint and" (the Inbound webhook section opens with that sentence), and the Queue-backed bullet loses "Requires thequeueservice (see prerequisite)" (the prerequisites table 25 lines above carries thequeuerow and its 503). The ceiling is not moved. No eval token names the retired spelling, soevals/**is untouched.Serial state
PR #22828 (lint one-line verdicts slice 10, the other writer of
rule-explanations.ts) landed asd8c7d3864, which is this branch's base.origin/mainwas merged at1eff3224dbefore opening; the incoming commits touchpackages/cli,packages/verify,packages/qaandcontent/docsonly, none of the six files.Verification
Measured on this worktree; the lock figures are shared-box seconds. The lint suite and typecheck ran on
435fd3b3e(the content commit); the merge oforigin/mainthat followed touched no file underpackages/lint,packages/spec,packages/formulaorpackages/sdui-parser, and the derived gate union (74 families, same set as before the merge) was run again on the final headcaf9fa094, every exit code recorded as the tool prints it, and reconciled:dispatch-gates --ran: 74 derived famil(ies) accounted for — 72 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).; the only non-zero exits are the two NOT MEASURED prerequisites below andcheck:platform-checklist, whose red set on this head is byte-identical to the base's.pnpm --filter @objectstack/linttest (vitest run --maxWorkers=2, whole package):Test Files 137 passed (137),Tests 6450 passed | 5 skipped (6455). The two directly affected files first:rule-explanations.test.ts+validate-flow-trigger-readiness.test.ts,107 passed (107)— this is where the explanation pin (ADR-0041,HMAC,registerFlow, ...) lives.pnpm --filter @objectstack/lint typecheck:tsc --noEmitclean;check:test-typecheck: OK — 2 file(s) / 6 error(s) / 2 pinned signature(s) held(the ledger as committed).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 74 families from the six-path change set; each was run in the foreground with its exit code recorded as the tool prints it; first-pass--ranverdict on435fd3b3e:74 derived famil(ies) accounted for — 68 run, 6 NOT-MEASURED (6 DERIVED from a recorded exit 3), 0 UNRUN; the final pass oncaf9fa094is quoted above (72 run, 2 NOT-MEASURED, 0 UNRUN). Green among them:check-skills-token-ratchet(+--self-test),check:skill-docs,check:skill-compatibility,check:skill-frame-sync,check:skill-identifier-liveness,check:doc-authoring,check:nul-bytes,check:pm-governed-merges,check-empty-changeset,check-changeset-no-major,check-adr-0087-registration,check:issue-citations,check:cross-package-test-inputs,check:test-source-alias, and the rest of the list.pnpm --filter @objectstack/spec run check:skill-refs→9 generated files in sync;check:skill-examples→exit 0once@objectstack/client-react(its client-SDK root) was built; its first run was a prerequisite refusal (exit 3, no built output to read), not a finding.pnpm check:i18n-coverage(prerequisite: the built CLI; this diff touches no label, translation or metadata form) andpnpm check:dual-build-cjs-loads(prerequisite: all 81 package dists; this diff changes string literals in@objectstack/lintonly). The other fourexit 3refusals of the first pass were re-run green once their prerequisite was met:check:docs-transcript-drift(4 declared transcript value(s) across 412 page(s) … equal what the registry derives) andcheck:doc-formula-expressionsafter@objectstack/lintwas built,check:lean-entry-closure(2 published condition(s) measured from a real load) after@objectstack/objectqlwas built, andcheck-plugin-teardown-shape --self-test(48 cases pass) aftergit fetch --deepen=4000brought its fixture commit621a4876…into reach.pnpm check:platform-checklistexits 1 on this branch AND on the untouched based8c7d3864with a byte-identical set of 5 unique findings (absent symbol anchors inareas/access-security.jsonandareas/attachments-storage.json); none namesautomation.json, which parses (json.load) and carries the bumpedrevision3 with its history entry.checklist-status.ymlkeeps that gate out of per-PR CI; it is a watchdog finding onmain, not this PR's.@objectstack/example-showcase: the diff is inside one/** … */block (comment only), so its typecheck was not run.check:commit-card-trailers(pre-push) is green on every pushed commit: the trailer pair is model-free and no commit carries a card relation; this body is the one carrier.Acceptance notes
docs/qa/platform-checklist/areas/automation.jsonline 429 (itemautomation.flow-node-type-matrix's webhook chain step) says "a valid x-objectstack-signature HMAC (secret 'showcase-webhook-secret')" and names no form. It is not one of the five and is left as is; once trigger-api: the inbound hook door refuses the body-onlysha256=signature, so only the timestamped, replay-protected form verifies (ruling B on #22805, step 3) #22826 lands, "valid" means the timestamped form by construction, and the showcase comment this step points at now teaches it.check:platform-checklistreds above are pre-existing onmain(symbol anchors pointing atenvWideRawViewRows,anonymousFormIntakeOrgScopeRefusal,anonymousFormIntakeReopenRefusalinpackages/metadata-protocol/src/protocol.tsandcanEditinpackages/services/service-storage/src/attachment-access-hooks.ts, which those files no longer declare). Carrier: the checklist watchdog / whoever renamed those symbols; noted here, not filed.维护者速读(草稿)
改了什么。 五处「教发送方怎么给入站 webhook 签名」的文本,从只签请求体的旧写法改为带时间戳的
t=UNIX_SECONDS,v1=HEX写法(v1是对UNIX_SECONDS.RAW_BODY用流程secret做的 HMAC-SHA256,服务端只接受与自身时钟相差 300 秒以内的t)。五处是:对外发布的objectstack-automation技能的 Inbound webhook 一节(Tier H,需要您的 APPROVED)、lint 规则flow-api-trigger-secret-missing的提示与os explain解释、showcase 示例的注释、平台测试清单里api触发的请求配方;另加一个 changeset(@objectstack/lint与@objectstack/skills各 patch)。校验器、出站签名、webhooks.mdx§6.2 一字未动。为什么改。 #22805 裁决 B(您批「同意」)第 1 步:先让所有教材只教带时间戳的形式,再让入站门拒收旧形式(#22826,被本卡阻塞)。旧形式不含时间,抓到一次请求可以永远重放;新形式是门已经在接受、
@objectstack/core的signHttpBodyAt已经在写的形式,教材落后于代码。风险与代价(含回滚)。 纯文本改动,无行为变化:lint 的判定、规则 id、严重级别不变,整包 137 个测试文件 6450 用例全绿,typecheck 全绿。对外技能文件的 token 棘轮只有 2 的余量,新签名行多出 102 字节,用同文件两处与相邻表格重复的半句话抵掉(未折行、未抬上限),落地后余量 1。代价是下一位编辑该文件的人仍要先删再加。回滚即 revert 本 PR 的单个 squash 提交,无数据、无迁移。
席位意见。 (留空,席位定稿成评论。)
你要做的。 本 PR 含
skills/**,属 Tier H:只需您在 PR 上给一个 APPROVED review(任一提交上、不撤回即可),席位随后负责排队落地;不需要您合并。若您认为技能里那一行的措辞应改(例如要不要保留「Stripe style」的类比),在 review 里写一句即可,席位代改。Generated by Claude Code