Repository navigation
runtime + core: an exact POST /automation/hooks/:flowName/:hookId dispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773
Description
Activity
- addedenhancementNew feature or requestNew feature or requestpriority:p2Medium: important, M3Medium: important, M3area:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving it
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsPointer from segment 1 (#22772): forward to slot
trigger-api, memberhandleInboundHook(request, { flowName, hookId }), and one self-test to adddomain:specseat 2 (#18549) ·session_016njDy8ozy9B9Ns5Y8kAWEK· 2026-10-11T07:46Z. ⛔ Not a claim, ⛔ not a dispatch, ⛔ no label change.What segment 1 declared (PR #22779 at
ad45100733, accepted6106807013, in the merge queue):ITriggerApiServiceinpackages/spec/src/contracts/trigger-api-service.ts, for the slot keytrigger-api. It has one optional member:handleInboundHook?(request: Request, hook: { readonly flowName: string; readonly hookId: string }), which returns a Promise of Response.- The docblock names this domain as the member's one caller. It forwards the request with its body unread, and returns the member's Response as it is. It hands the two path parameters as the router matched them, decoded.
- An empty slot or an absent member answers a typed 404 or 501, ⛔ never
ROUTE_NOT_FOUND.
One self-test the review asked for, beyond condition 1's list (contract review on PR #22779):
- Condition 1 already pins the four segments,
POSTonly, and every other/automationpath still gated. - The allow-list's segment normalisation (empty segments dropped, no percent-decoding) and this domain's four-segment route must also agree on every spelling they are shown.
- A spelling that one reader admits and the other reads differently stays gated.
- The two parameters handed to the member are the decoded segments. The dispatcher's
cleanPathis undecoded, so this domain decodes them, as the i18n, meta and share-links domains do.
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsUnlock:
pm:blocked→pm:queueTriage seat (seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T08:56Z. ⛔ Not a claim, ⛔ not a dispatch.- The only blocker is closed: spec: declare the forward target for trigger-api's inbound hook (
POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772 closedcompletedwhen PR feat(spec): declare ITriggerApiService.handleInboundHook, the optional transport-neutral inbound-hook member #22779 merged.ITriggerApiService.handleInboundHook?is onmainunder slottrigger-api, and the pointer6106817103names it. - The ruling's own line has been met: no seat claimed this before spec: declare the forward target for trigger-api's inbound hook (
POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772 was onmain. - Scope, done-when and ruling conditions 1 and 2 are unchanged. ⛔ Anything wider returns to [Decision] 托管环境上 trigger-api 的入站 webhook(HMAC 签名)要不要在
/automation匿名门槛上开一个精确到路由的口子,与自托管一致? #22757. - Not waiting:
- replay protection trigger-api: the inbound-hook HMAC signs the body alone, so its signed material carries no timestamp or tolerance window (the replay-protection hardening card condition 3 of ruling 6105447950 names) #22769 (PR feat(core,trigger-api): timestamped x-objectstack-signature form with a 300 s tolerance window; body-only still accepted, deprecated #22803 is open; ruling condition 3 keeps it separate);
- [finding] trigger-api: the inbound hook matches the flow and hook id before it verifies the signature, so an unsigned post's 401 versus 404 tells which flow names are armed #22806 (filed today: an existence signal in the verifier's ordering), also a separate hardening card.
- It pairs with trigger-api: implement the declared inbound-hook member through the same verifier, read
http.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774. Whichever lands second runs the end-to-end pin.
- The only blocker is closed: spec: declare the forward target for trigger-api's inbound hook (
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsSerial note from the
domain:cliseat 1 (#6024) ·marchtian·session_01B5CHJNXuuqzChM4w6hkTN4· 2026-10-11T11:19Z. ⛔ Not a claim, ⛔ not a dispatch, ⛔ no label change.This card is held hard-serial behind #22755 (this lane, claim
6108426635, dispatched this round), in the same files:- runtime: an exact
/webhooks/redeliverdispatcher domain that forwards to the declared redeliver member, with a typed refusal for an absent slot (webhooks segment 2 of #22564's stage 2) #22755 adds an exact/webhooks/redeliverdispatcher domain. Like the/approvals/actprecedent (PR fix(runtime,hono): an exact /approvals/act dispatcher domain for kernels with no raw app, and a catch-all that leaves the raw request readable #22693), it registers inpackages/runtime/src/http-dispatcher.ts, adds apackages/runtime/src/route-ledger.tsrow, and may move the dogfood authz census pair (authz-conformance.matrix.ts,authz-probe-blind-spot.census.ts). - This card's
/automation/hooks/:flowName/:hookIddomain edits the same registration, ledger and census files, pluspackages/core'sauth-gate.ts. - This seat holds a file hard-serial until the MERGE, ⛔ not the arm.
Known for whoever claims this card next:
- It starts from the
mainthat carries runtime: an exact/webhooks/redeliverdispatcher domain that forwards to the declared redeliver member, with a typed refusal for an absent slot (webhooks segment 2 of #22564's stage 2) #22755's PR, and reads that domain as the nearest sibling: the per-request slot resolution, the typed absence and the end-to-end pin. - The trigger-api cards in flight elsewhere are [finding] trigger-api: the inbound hook matches the flow and hook id before it verifies the signature, so an unsigned post's 401 versus 404 tells which flow names are armed #22806 (
domain:services, PR fix(trigger-api,spec): one answer for every inbound hook post that does not verify #22822, the verifier's ordering) and trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 (domain:services, queued, the member). Whichever of runtime + core: an exactPOST /automation/hooks/:flowName/:hookIddispatcher domain and its parameterised ADR-0069 allow-list row, anonymous through to the trigger's HMAC verifier only (trigger-api segment 2 of ruling A on #22757) #22773 / trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 lands second runs the end-to-end pin (the triage unlock6107344979).
Dispatchable once #22755's PR merges.
Generated by Claude Code
- runtime: an exact
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-11T14:25Z
Session:session_01VoSxBQujKLZKPwK2u5ehQ6
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22773-automation-hooks-domain
Worktree:objectstack-issue-22773
Domain:domain:cli
Seat:domain:cli#2(seat post #22648)
File surface (read onorigin/main12b9daf7; stop on a breach and explain it in the report):packages/runtime/src/domains/: a new domain module forPOST /automation/hooks/:flowName/:hookIdand its tests. It forwards the raw request, body unread, to thetrigger-apislot'shandleInboundHook(request, { flowName, hookId })with the two segments decoded, and answers an empty slot or an absent member with a typed 404 or 501.packages/runtime/src/http-dispatcher.ts(registration) andpackages/runtime/src/route-ledger.ts(the row).packages/runtime/src/domains/automation.tsonly if the existing/automationdomain must yield this exact route.- Cross-domain exception path (
domain:enginefiles, declared here as the card and ruling A require):packages/core/src/security/auth-gate.ts, the allow-list's first parameterised row, and its self-test;packages/core/src/security/anonymous-deny.tsonly if the anonymous floor must read the new row's shape. - The dogfood authz census pair
packages/qa/dogfood/test/authz-conformance.matrix.ts/authz-probe-blind-spot.census.tsand their count tests,route-ledger-live-mount-parity.dogfood.test.ts,scripts/check-route-envelope.mjsand apackages/qa/http-conformance/end-to-end pin, each only if the new route moves it (the PR fix(runtime): an exact POST /webhooks/redeliver dispatcher domain for kernels with no raw app #22842 shape). - One
.changeset/22773-*.md. - ⛔ No
packages/specchange, and nopackages/triggers/trigger-apichange (that is trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774's).
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Not mechanical: a new dispatcher domain, the allow-list's first parameterised matcher, and the hosted-shape ordering measurement. CeilingCONTRACT_REVIEW_TIER, reason: the allow-list row removes the anonymous floor's refusal for one route (Clause-②: yes), so one review at that tier is owed before the queue.
Clause-②: yes - Widening: one exact, parameterised route passes the ADR-0069 gate without a session, as ruling A on [Decision] 托管环境上 trigger-api 的入站 webhook(HMAC 签名)要不要在
/automation匿名门槛上开一个精确到路由的口子,与自托管一致? #22757 directs (record6105447950, maintainer 「同意」). The precedent is the['approvals', 'act']row, declaredyeson core: put the exact approvals act path on the ADR-0069 auth-gate allow-list, token-only as ADR-0043 sets it and as the self-hosted mount already serves it (segment 3 of ruling A on #22438) #22577.
Responsibility:n/a — not a defect card
Thread-read: 6108474120
Serial constraints cleared: runtime: an exact/webhooks/redeliverdispatcher domain that forwards to the declared redeliver member, with a typed refusal for an absent slot (webhooks segment 2 of #22564's stage 2) #22755, the hard-serial predecessor in serial note6108474120, is closed and its PR fix(runtime): an exact POST /webhooks/redeliver dispatcher domain for kernels with no raw app #22842 is onmainas8558668a6; this card starts from thatmain. No open PR toucheshttp-dispatcher.ts,route-ledger.ts, the/automationdomain,auth-gate.ts,anonymous-deny.tsor the census pair (all 12 open PRs' file lists read in this act). None of the 18 openpm:dispatchedclaims declares those files. trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 (domain:services, in flight) editspackages/triggers/trigger-apionly, so the two are disjoint; whichever lands second runs the end-to-end pin (triage unlock6107344979).
Generated by Claude Code
Blocked-by: #22772
Filing gate ③: a segment the maintainer's ruling directs. Filed by the triage seat (seat post #6015,
session_01AavokzJ5DndAwitDXvKy4U) under ruling A on #22757 (director record6105447950, maintainer 「同意」), which directs triage to split the trigger-api member into the #22438 segment shapes (#22575–#22578). ⛔ Not a claim.Scope
packages/runtime: an exact dispatcher domain forPOST /automation/hooks/:flowName/:hookId. It forwards the raw request to the member spec: declare the forward target for trigger-api's inbound hook (POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772 declares, and an absent slot or member answers a typed 404 or 501.packages/coreauth-gate.ts: the allow-list's first parameterised row, under ruling condition 1:automation,hooks, one flow name, one hook id),POSTonly;/automation/hooks,/automation/hooks/x,/automation/hooks/x/y/zand every other/automationpath as still gated./automation匿名门槛上开一个精确到路由的口子,与自托管一致? #22757.POSTor unsigned request meets the floor's refusal before any route shape leaks (the [17.0-rc2验收] 安全:REST /actions 与 /automation 派发路由缺少匿名拒绝门 —— 未认证调用者可触发 system 提权的 RLS/FLS 绕过写入 #5519 ordering).domain:cli, withpackages/coredeclared under the cross-domain exception path. The claim declares both. ⛔ No seat claims this before spec: declare the forward target for trigger-api's inbound hook (POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772 is onmain(the ruling's own line).Done when
http.server, a correctly signed request reaches the verifier and is accepted. A wrongly signed one is refused by the verifier./automationpath stays at the floor.http.serveris unchanged, ⛔ with no double mount.domain:cli· p2 ·security.