Skip to content

feat(durable): add waitForApproval and the approvals client - #315

Merged
subnetmarco merged 13 commits into
mainfrom
feat/durable-approvals
Oct 8, 2026
Merged

subnetmarco merged 13 commits into
mainfrom
feat/durable-approvals

Conversation

@subnetmarco

@subnetmarco subnetmarco commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Tracking

Tracks: https://github.com/Kong/volcano-hosting/pull/1678

Summary

  • ctx.waitForApproval(name, { title, description?, details?, timeout? }) in durable functions. It registers the approval with Volcano inside the callback submitter, so a replay never registers twice, and resolves to { approved, status, comment, decidedBy, decidedAt }. A callback timeout resolves to status: 'expired'.
  • Registration retries network errors, 404, 429, 5xx and 409 approval_not_ready within one 30 s deadline, with each attempt bounded by min(10 s, remaining). The request is validated against the 64 KiB body limit before the callback opens.
  • name must be printable ASCII of at most 237 characters, since it is the runtime's operation name, and NUL is refused anywhere in the text or details. A registration refusal keeps its status and code in the message, because the runtime rethrows it with the message alone.
  • volcano.durable.approvals: list, get, stats, approve, deny. A project access token is refused deciding with a permission error.
  • The decision parser trusts only status; comment, decidedBy, and decidedAt read as empty when malformed, so a recorded decision never fails a replay. decidedAt must be an RFC 3339 date-time, matching Python.
  • @aws/durable-execution-sdk-js 2.3.1 (the release Volcano installs) and its testing package are dev dependencies, so approvals are tested on the real runtime. The runtime stays an optional peer; published dependencies are unchanged.
  • OpenAPI synced verbatim from Hosting's bundle.

Verification

  • pnpm check:openapi, pnpm lint, pnpm test:types, pnpm build, pnpm test:package, pnpm check:unused: pass
  • pnpm test: 126 suites, 2528 tests pass
  • pnpm test:tooling fails one case ("native reporter preserves cancelled case") on Node 24.10 only; it passes on Node 22 and is unrelated to this change.

Cross-language impact

Classify this change: public facade / wire contract / shared behavior.

Merge order

  1. SDK releases: a maintainer merges and releases the JS SDK 1.16 (feat(durable): add waitForApproval and the approvals client #315), Python SDK 0.14 (feat(durable): add wait_for_approval and the approvals client volcano-sdk-python#389), and the Ruby SDK (feat(durable): list, read, and decide durable approvals volcano-sdk-ruby#341). Hosting's E2E fixtures install those versions from the registries. Until Hosting deploys, the new approval methods get 404.
  2. CLI (feat(durable): add durable approvals commands volcano-cli#282), with a one-time maintainer override of localmode-e2e. That suite needs a nightly local-mode image with approvals, which exists only once Hosting merges. Hosting's merge queue runs its CLI E2E against CLI main, so the CLI goes first.
  3. Hosting (https://github.com/Kong/volcano-hosting/pull/1678).
  4. Dashboard (https://github.com/Kong/volcano-web/pull/836, https://github.com/Kong/volcano-web/pull/837, then https://github.com/Kong/volcano-web/pull/832) and agent skills (docs(durable): teach agents to request and decide durable approvals volcano-skills#57).

subnetmarco and others added 9 commits October 6, 2026 15:53
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Retry a 404 while Volcano records a just-started execution, and treat a
409 approval_closed as registered so the callback resolves as expired.
Bound all registration attempts by one 30-second deadline.

Refuse details that are not JSON, and requests over 64 KiB with room for
the longest callback id, before the callback opens. Count name, title,
description, and comment lengths in code points, as Volcano does.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	openapi/openapi.yaml
#	src/durable.ts
#	src/generated/openapi.d.ts
The retry check read the clock, then each attempt read it again to size
its timeout. A millisecond between the two could leave a negative
timeout, which AbortSignal.timeout throws on, so a 503 was reported as
Volcano being unreachable. Compute the remaining time once after the
backoff and give it to the attempt; with none left, surface the failure
already seen.

Co-authored-by: Cursor <cursoragent@cursor.com>
Match the decision rule the Python SDK follows: status decides, and must
be approved or denied; approved derives from it; comment defaults to
''; decidedBy and decidedAt are null when missing or malformed. Any
string passed as decidedAt before. It now has to be an RFC 3339
date-time on a real calendar day, the form Volcano writes.

Co-authored-by: Cursor <cursoragent@cursor.com>
The approval tests stood a double in for the runtime whose
waitForCallback always ran the submitter, so nothing showed that a
replay skips registration, that a callback timeout reaches the SDK as
CallbackTimeoutError, or that the execution ARN the runtime is given
reaches the registration body. Run those three through the runtime's
local test runner.

Pin @aws/durable-execution-sdk-js 2.3.1, the release Volcano installs
into durable functions, and @aws/durable-execution-sdk-js-testing 1.3.0
as dev dependencies. Both declare Node 22; the tests pass on Node
20.20.2, which CI also runs. The package still declares the runtime
only as an optional peer, and the packaging test now checks that the
example app, which consumes the SDK, does not resolve it.

The runtime's declarations do not compile under exactOptionalPropertyTypes,
so the runner is loaded untyped behind the repo's opaque stub.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	openapi/openapi.yaml
#	pnpm-workspace.yaml
#	src/generated-runtime/generated/client.js
#	src/generated/client.ts
#	src/generated/openapi.d.ts
Co-authored-by: Cursor <cursoragent@cursor.com>

@subnetmarco subnetmarco left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 314d66f. I found no P1 or P2 issues. Checked against the real runtime (2.3.1), not just the stand-in:

  • Registration happens once across replays, including after a decision and after an expiry.
  • When every attempt fails, the execution ends FAILED after about 28 s instead of hanging.
  • Only a real CallbackTimeoutError becomes expired, and that stays true on replay.

The 64 KiB check measures UTF-8 bytes of the exact body sent, and the retry list and deadline match Python. Only the durable entry touches process.env or the runtime, and the OpenAPI is byte-identical to Hosting's bundle. pnpm test passed 2540 tests, and the type checks, build, and lint passed too.

Release order: Hosting's and the CLI's E2E fixtures pin ^1.16.0, so this needs to be released before those PRs merge, not after (details on Kong/volcano-hosting#1678).

Comment thread docs/durable-functions.md
Comment thread src/durable-approval.ts
Comment thread src/durable-approval.ts Outdated
subnetmarco and others added 3 commits October 8, 2026 08:26
Co-authored-by: Cursor <cursoragent@cursor.com>
Names are the runtime's operation name, so they hold to printable ASCII and 237 characters. NUL is refused before the callback opens. A refusal keeps its status and code in the message, since the runtime rethrows it with the message alone.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	package.json
#	src/generated-runtime/generated/model/index.js
#	src/generated/client.ts
#	src/generated/model/index.ts
#	test/fixtures/published-api.d.ts
@subnetmarco
subnetmarco marked this pull request as ready for review October 8, 2026 19:05
Copilot AI balanced review requested due to automatic review settings October 8, 2026 19:05
@subnetmarco
subnetmarco requested a review from a team as a code owner October 8, 2026 19:05
@subnetmarco
subnetmarco added this pull request to the merge queue Oct 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Merged via the queue into main with commit 14732f9 Oct 8, 2026
45 checks passed
@subnetmarco
subnetmarco deleted the feat/durable-approvals branch October 8, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants