Repository navigation
Description
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K
Filing gate: ③ the server slice that PR #29995 (contract half of #29983, merged) lists for the domain:server lane. ⛔ Not a claim. Suggested-grade: p1 (the parent #29983 is p1, and its need is met only by this slice). Route: domain:server.
Business reason. hotclm's e-signature step wants DocuSign Connect to post envelope status to an api flow. Since #29995, @objectstack/spec lets a flow declare the sender's signature (config.signature: header, HMAC-SHA256, hex or base64, body or timestamp.body, key through config.secretRef), and ITriggerApiService states the verifier's obligations. No trigger serves it yet: a hook that declares signature still checks only x-objectstack-signature, so every vendor post gets 401 INVALID_SIGNATURE (fail closed). This card makes the trigger verify the declaration.
The contract is in packages/spec/src/automation/api-trigger-signature.zod.ts (apiTriggerVerifierOf answers platform, declared or unusable) and the "declared scheme REPLACES that check" text in packages/spec/src/contracts/trigger-api-service.ts. The contract reviews are on PR #29995 (records 6619711618, 6620065679, 6620688559).
Wanted (checklist from PR #29995)
-
packages/triggers/trigger-api: readapiTriggerVerifierOf(binding.config)atstart().unusablenever arms a hook that admits a post and never falls back tox-objectstack-signature; whether the trigger declines to arm it or refuses each post, every post to it gets the one401 INVALID_SIGNATURE. -
serveInboundHook/handleRequest: pass the declared header's value (today onlyx-objectstack-signatureandx-idempotency-keyreachhandleRequest), on both faces (the self-hosted mount andhandleInboundHook), read withHeaders.getso a repeated field arrives as its one comma-joined value, never the first field alone. - The verifier per the contract text: header parse per arm (timestamp element once, decimal as
x-objectstack-signaturewritest; digest elements one or more; unknown elements ignored); HMAC-SHA256 over the exact body bytes (request.text()re-encodes valid UTF-8 byte for byte; a non-UTF-8 body mismatches and fails closed); decode hex/base64 to 32 bytes;timingSafeEqual; the window from core'sHTTP_SIGNATURE_TOLERANCE_SECONDS. - Every failure through
refuseUnverifiedwith its own logged cause (header absent, malformed, prefix missing, no digest, mismatch, outside window, declaration unusable) and the one401 INVALID_SIGNATUREanswer. - Pins: a DocuSign vector (real base64 HMAC) admitted; a Stripe-style vector admitted, with a second
v1element (key roll) and an outside-windowtrefused; a validx-objectstack-signatureon a declared hook refused; a post without the declared header refused; a body-only header sent twice refused; every failure's status and body identical; anunusabledeclaration answers that same401and admits nothing. - Runtime
HttpDispatcher: confirm the forwardedRequestkeeps the vendor header (one pin). -
validate-flow-trigger-readiness(required): a hook withsecretRefandsignaturedoes not grade ready while no trigger serves the key; once the trigger serves it, the grade names the verifier the hook has (apiTriggerVerifierOf), and anunusabledeclaration never grades ready. - Skills and docs from the
Skills-docs:line above, and thevariant-docs.jsonexemption replaced by a binding to the new flows.mdx section.
Binding
- An auth boundary: one verifier per hook, constant-time compare, the window from core's
HTTP_SIGNATURE_TOLERANCE_SECONDSwhere a timestamp is signed, the key only throughsecretRef, and one401 INVALID_SIGNATUREanswer for every failure. A weakness found in today's verifier goes tosecurity#1, not into a comment. - Its PR closes An api-triggered flow can verify only an ObjectStack signature: a vendor webhook signed its own way (DocuSign Connect HMAC) cannot be admitted #29983 too (
Fixes #29983beside this card's), because An api-triggered flow can verify only an ObjectStack signature: a vendor webhook signed its own way (DocuSign Connect HMAC) cannot be admitted #29983's need is met here.
Dedupe: searched api trigger signature, x-docusign-signature, apiTriggerVerifierOf. No open card. New.
Activity
objectstack-fleet commentedon Nov 4, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p1 kept ·
domain:serverkept ·pm:queuekept · 2026-11-04T04:52Z⛔ Not a claim.
config.signature,apiTriggerVerifierOf), but no trigger serves it yet, so every vendor post to a hook that declares a signature gets401. hotclm's DocuSign step is blocked until this slice lands, so p1 is kept, as the parent.Generated by Claude Code
objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5
Claim: PM loop round 10
Session:
session_01WGufzeeFznzXxc61VmVH12(dev: os-dev card thread; seatsession_01WGufzibcN9oHa8kDbTTnL5)Account:
os-support-aiBranch:
claude/project-thread-2vidml(harness-pinned thread branch; platform exception toclaude/issue-<n>-<slug>)Worktree: the dev thread's own container checkout
Domain:
domain:serverSeat:
domain:server#6File surface:
packages/triggers/trigger-api/src/(start(),serveInboundHook/handleRequeston both faces, the declared-signature verifier,refuseUnverified), the runtimeHttpDispatcherheader pass-through pin,packages/lint/src/validate-flow-trigger-readiness.ts, the skills/docs that #29995'sSkills-docs:line owes,variant-docs.json, pins, one changeset. ⛔protocol.tsandengine.tsare frozen for today's codemod window. The spec contract is not changed here. On a breach, stop and explain it in the report.Container & model:
L,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: yes (widening)
Metadata-protocol: none
Responsibility:
the platform's own api trigger (a hook that declares config.signature answers 401 to every vendor post) | none: PR #29995 landed the contract half only | every flow author receiving a vendor webhook (hotclm's DocuSign Connect step first)Thread-read: 6622896133
Premise: holds (contract merged in #29995; no trigger reads
apiTriggerVerifierOf; triage 6622896133; the dev re-measures on main)Serial constraints cleared: PR #29995 (contract half of #29983) merged
Stamp: 2026-11-04T09:38Z
/mnt/project-files/server-orders/30009.md(project files). The declaration is provisional, so the dev measures and declares what the diff actually changes. This is an inbound auth boundary: before landing, the seat runs aCONTRACT_REVIEW_TIERsecurity-face review, and the maintainer approves before the PR enters the queue. The PR also closes An api-triggered flow can verify only an ObjectStack signature: a vendor webhook signed its own way (DocuSign Connect HMAC) cannot be admitted #29983.objectstack-fleet commentedon Nov 4, 2026
domain:server#6 · os-support-ai · session_01WGufzeeFznzXxc61VmVH12
os-dev-report
Stamp: 2026-11-04T10:40Z
{ "issue": 30009, "status": "done", "branch": "claude/project-thread-2vidml", "pr": "https://github.com/objectstack-ai/objectstack/pull/30048", "session": "session_01WGufzeeFznzXxc61VmVH12", "premise_still_valid": true, "summary": "trigger-api now serves the settled contract (#29995): start() reads each api hook's one verifier with apiTriggerVerifierOf; a declared config.signature (body or timestamp.body, hex or base64) REPLACES x-objectstack-signature, read from the declared header on both faces via Headers.get, HMAC-SHA256 under the config.secretRef key, digests strictly decoded to 32 bytes and compared with timingSafeEqual (no early exit, any v1 of a key roll), timestamped schemes held to core's HTTP_SIGNATURE_TOLERANCE_SECONDS after the HMAC verifies. Every failure is the one 401 INVALID_SIGNATURE with its own logged cause (no key/header/body in logs); unusable arms a hook that refuses before any secret read and never falls back; a declared binding without the engine's secretRef reader throws at start(). Lint readiness grades by the same reader (declared = ready; unusable = new error flow-api-trigger-signature-unusable with os explain entry); flows.mdx, variant-docs.json and the automation/api skills teach the key. No spec change; protocol.ts and engine.ts untouched.", "tests": "head 763d73589. trigger-api vitest → 3 files, 62 passed; inbound-hook-member.test.ts → 18 passed (6 [#30009], 34 rows through both faces, byte-equal answers; DocuSign base64 and Stripe hex literals from openssl; key roll; 300 s in / 301 s old and ahead out; valid x-objectstack-signature refused on a declared hook; missing header; body-only header twice; unusable never reads the key; 503 on unreadable key; control). lint vitest → 193 files, 7981 passed. runtime vitest → 540 files, 8615 passed, 19 skipped (header pass-through pin through real dispatch()). typecheck trigger-api/lint/runtime exit 0; eslint on changed files exit 0. Ablations via ablation-replace.mjs: timingSafeEqual → length compare: 2 of 18 red ('DocuSign: the vector over another body: expected 202 to be 401'); window '> HTTP_SIGNATURE_TOLERANCE_SECONDS' → '< 0': 2 of 18 red ('Stripe-style … 301 s old: expected 202 to be 401'); each restored, git diff HEAD empty.", "gates": "dispatch-gates --commands at 763d73589: 102 derived; --ran → 101 run all exit 0, 1 NOT-MEASURED (pnpm check:dual-build-cjs-loads exit 3, whole-repo build prerequisite; left to CI), 0 UNRUN. Reruns: check-plugin-teardown-shape --self-test (exit 3 shallow clone → fetched pinned commit → 0), check:skill-examples (exit 3 no client-react dist → built → 0, 283 examples), check:test-source-alias (exit 1, real: added anchored @objectstack/spec/automation alias in trigger-api vitest.config.ts → 0). check-changeset-no-major and check-adr-0087-registration with PR event file → 0. Main probe before pr_create: merge-tree with origin/main 9ebca9478 clean. CI check-runs: pending at report time.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "8 — github issue_read x5 (get 30009, get_comments 30009 x2, get 29983, get_comments 29983), pull_request_read x2 (get 29995, get_comments 29995), claude-code-remote subscribe_pr_activity x1 (#30048); ToolSearch loads not counted; 0 GitHub MCP writes", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41815606952, read-back identical 15539 bytes, assignees none) and this os-dev-report comment via post-stamped.mjs; plus git push (not REST)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [] }Generated by Claude Code