Repository navigation
Commit eed637c
docs(automation): every sender-facing text for an inbound api hook teaches the timestamped x-objectstack-signature form only (#22846)
Fixes #22825
Clause-②: no
Ruling B on #22805 (maintainer 「同意」, comment 6108492902), step 1: every
text that teaches a sender of an inbound `api` hook teaches the
timestamped `x-objectstack-signature` form only. The form is the one
`signHttpBodyAt` in `packages/core/src/security/http-signature.ts`
writes — `t=UNIX_SECONDS,v1=HEX`, where HEX is the HMAC-SHA256 of
`UNIX_SECONDS.RAW_BODY` under the flow's secret, and the door refuses a
`t` more than `HTTP_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
flow `http` node, body-only `signHttpBody`) are not touched. The door's
refusal of the body-only form is #22826, which is `Blocked-by:` this
card and unblocks on merge.
## The five positions and the changeset
| position | before | after |
|:--|:--|:--|
| `skills/objectstack-automation/SKILL.md`, Inbound webhook section,
Signature line (Tier H) | the body-only form, "(GitHub/Stripe style)" |
`t=UNIX_SECONDS,v1=HEX`; `v1` = HMAC-SHA256 of `UNIX_SECONDS.RAW_BODY`
under `secret`; refused when `t` is over 300 s from the server clock |
| `packages/lint/src/rule-explanations.ts`,
`flow-api-trigger-secret-missing` explanation (`os explain`) | "carries
'sha256=' and the hex HMAC-SHA256 of the raw body" | the timestamped
form, its material, the 300-second window, and why (a captured post
stops verifying); `ADR-0041` and `HMAC` kept, which the explanation pin
reads |
| `packages/lint/src/validate-flow-trigger-readiness.ts`, the rule's
`hint` | same sentence | the same form; the verdict `message`, rule id,
severity, path and judgement are unchanged |
| `examples/app-showcase/src/automation/flows/index.ts`, the
`InboundTaskWebhookFlow` doc comment | header line with the body-only
form | header line with the timestamped form, `signHttpBodyAt(rawBody,
secret, Math.floor(Date.now() / 1000))` named as the writer, the 300 s
refusal named |
| `docs/qa/platform-checklist/areas/automation.json`, item
`automation.trigger-type-matrix`, the `api` step | signs the raw body
only | signs with `signHttpBodyAt(rawBody, 'showcase-webhook-secret',
Math.floor(Date.now() / 1000))`; `revision` 2 → 3 with a history entry |
| `.changeset/22825-inbound-hook-timestamped-guidance.md` | — |
`@objectstack/lint` patch + `@objectstack/skills` patch
(`@objectstack/example-showcase` is private; `docs/qa` is not a package)
|
Not changed on purpose: `automation.json` line 429 (another item's step,
"a valid x-objectstack-signature HMAC", names no form);
`content/docs/releases/**` and `packages/*/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/evals` is empty).
## Done-when readings
Grep set `FIVE` = the five files above. Trees named by commit.
- **Before** (`origin/main` `d8c7d3864`): `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`).
- **After** (this branch): the same grep → 0 hits.
- **Positive control** (this branch): `git grep -c "v1=" -- FIVE` →
`automation.json` 2, `flows/index.ts` 1, `rule-explanations.ts` 1,
`validate-flow-trigger-readiness.ts` 1, `SKILL.md` 1; `git grep -c
"signHttpBodyAt" -- packages/core/src/security/http-signature.ts` → 6.
- **What the wider grep still hits** (`git grep -c "sha256=" -- skills
packages/lint examples docs/qa content/docs packages/triggers
packages/core`, this branch): `webhooks.mdx` 5 (outbound contract, kept
by the ruling), deployment docs 10 (the `#sha256=` artifact pin),
`releases/v17/17-6.mdx` 1 (history), `packages/core/CHANGELOG.md` 1
(release-owned), `http-signature.ts` 5 + its test 8 and `api-trigger.ts`
2 + its test 4 (the verifier and its pins — #22826's). None is a
sender-facing inbound-hook text.
## `skills/**` readings (token = ceil(utf8 bytes / 4), the ratchet's
convention)
| surface | before (`d8c7d3864`) | after |
|:--|:--|:--|
| `skills/objectstack-automation/SKILL.md` | 5783 tokens / 23132 bytes /
438 lines (ceiling 5785, headroom 2) | 5784 tokens / 23135 bytes / 438
lines (ceiling 5785, headroom 1) — `check-skills-token-ratchet` green |
| package `skills/objectstack-automation/**/*.md` | 56541 bytes / 1036
lines | 56544 bytes / 1036 lines |
| whole catalog, every `skills/**/SKILL.md` | 210520 bytes / 4409 lines
| 210523 bytes / 4409 lines |
The Signature line grew by 103 bytes and is paid by two same-file
deletions of text restated beside it, not by re-wrapping: the `api` row
of the Flow Types table loses "every `api` flow is bound to its hook
endpoint and" (the Inbound webhook section opens with that sentence),
and the Queue-backed bullet loses "Requires the `queue` service (see
prerequisite)" (the prerequisites table 25 lines above carries the
`queue` row and its 503). The ceiling is not moved. No eval token names
the retired spelling, so `evals/**` is untouched.
## Serial state
PR #22828 (lint one-line verdicts slice 10, the other writer of
`rule-explanations.ts`) landed as `d8c7d3864`, which is this branch's
base. `origin/main` was merged at `1eff3224d` before opening; the
incoming commits touch `packages/cli`, `packages/verify`, `packages/qa`
and `content/docs` only, 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 of `origin/main` that followed touched no file under
`packages/lint`, `packages/spec`, `packages/formula` or
`packages/sdui-parser`, and the derived gate union (74 families, same
set as before the merge) was run again on the final head `caf9fa094`,
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 and
`check:platform-checklist`, whose red set on this head is byte-identical
to the base's.
- `pnpm --filter @objectstack/lint` test (`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 --noEmit` clean;
`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/objectstack` derived 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 `--ran` verdict on `435fd3b3e`: `74 derived
famil(ies) accounted for — 68 run, 6 NOT-MEASURED (6 DERIVED from a
recorded exit 3)`, 0 UNRUN; the final pass on `caf9fa094` is 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.
- Dispatch-named gates outside the derived list: `pnpm --filter
@objectstack/spec run check:skill-refs` → `9 generated files in sync`;
`check:skill-examples` → `exit 0` once `@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.
- NOT MEASURED locally, declared to CI: `pnpm check:i18n-coverage`
(prerequisite: the built CLI; this diff touches no label, translation or
metadata form) and `pnpm check:dual-build-cjs-loads` (prerequisite: all
81 package dists; this diff changes string literals in
`@objectstack/lint` only). The other four `exit 3` refusals 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`) and
`check:doc-formula-expressions` after `@objectstack/lint` was built,
`check:lean-entry-closure` (`2 published condition(s) measured from a
real load`) after `@objectstack/objectql` was built, and
`check-plugin-teardown-shape --self-test` (`48 cases pass`) after `git
fetch --deepen=4000` brought its fixture commit `621a4876…` into reach.
- `pnpm check:platform-checklist` exits 1 on this branch AND on the
untouched base `d8c7d3864` with a byte-identical set of 5 unique
findings (absent symbol anchors in `areas/access-security.json` and
`areas/attachments-storage.json`); none names `automation.json`, which
parses (`json.load`) and carries the bumped `revision` 3 with its
history entry. `checklist-status.yml` keeps that gate out of per-PR CI;
it is a watchdog finding on `main`, 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.json` line 429 (item
`automation.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 #22826
lands, "valid" means the timestamped form by construction, and the
showcase comment this step points at now teaches it.
- The `check:platform-checklist` reds above are pre-existing on `main`
(symbol anchors pointing at `envWideRawViewRows`,
`anonymousFormIntakeOrgScopeRefusal`, `anonymousFormIntakeReopenRefusal`
in `packages/metadata-protocol/src/protocol.ts` and `canEdit` in
`packages/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.
- The ratchet's token convention under-prices nothing here: the edit is
ASCII apart from one em dash and the arrows already in the file.
## 维护者速读(草稿)
**改了什么。** 五处「教发送方怎么给入站 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](https://claude.ai/code/session_011u73oxZ5X95qrARPTeoU6v)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent ea9fd99 commit eed637c
6 files changed
Lines changed: 43 additions & 16 deletions
File tree
- .changeset
- docs/qa/platform-checklist/areas
- examples/app-showcase/src/automation/flows
- packages/lint/src
- skills/objectstack-automation
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
530 | 530 | | |
531 | 531 | | |
532 | 532 | | |
533 | | - | |
| 533 | + | |
534 | 534 | | |
535 | 535 | | |
536 | 536 | | |
| |||
554 | 554 | | |
555 | 555 | | |
556 | 556 | | |
557 | | - | |
| 557 | + | |
558 | 558 | | |
559 | 559 | | |
560 | 560 | | |
| |||
630 | 630 | | |
631 | 631 | | |
632 | 632 | | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
633 | 639 | | |
634 | 640 | | |
635 | 641 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1578 | 1578 | | |
1579 | 1579 | | |
1580 | 1580 | | |
1581 | | - | |
| 1581 | + | |
1582 | 1582 | | |
1583 | 1583 | | |
1584 | | - | |
1585 | | - | |
1586 | | - | |
1587 | | - | |
| 1584 | + | |
| 1585 | + | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
| 1589 | + | |
1588 | 1590 | | |
1589 | 1591 | | |
1590 | 1592 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1460 | 1460 | | |
1461 | 1461 | | |
1462 | 1462 | | |
1463 | | - | |
1464 | | - | |
1465 | | - | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
1466 | 1468 | | |
1467 | 1469 | | |
1468 | 1470 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
934 | 934 | | |
935 | 935 | | |
936 | 936 | | |
937 | | - | |
938 | | - | |
939 | | - | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
940 | 941 | | |
941 | 942 | | |
942 | 943 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
348 | 348 | | |
349 | 349 | | |
350 | 350 | | |
351 | | - | |
| 351 | + | |
352 | 352 | | |
353 | | - | |
| 353 | + | |
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
| |||
0 commit comments