Skip to content

feat(durable): add durable approvals commands - #282

Merged
subnetmarco merged 15 commits into
mainfrom
feat/durable-approvals
Oct 11, 2026
Merged

subnetmarco merged 15 commits into
mainfrom
feat/durable-approvals

Conversation

@subnetmarco

@subnetmarco subnetmarco commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Tracking

Summary

  • add volcano durable approvals list | get | stats | approve | deny, for approvals durable workflows wait on
  • list defaults to pending and filters by --function, --execution, --status, and --since; stats counts outcomes, approval rate, and time to decision over --since (default 30d). Both send only the window's start, so the API's clock ends it, and the API enforces the longest window
  • approve and deny take --comment, confirm unless --yes, and print the decision. Repeating the decision already made exits 0; a conflicting decision or an expired approval exits non-zero with what happened
  • a project access token is refused before the confirmation prompt with "approvals are decided by a person. Run volcano login with VOLCANO_TOKEN unset, or decide in the dashboard"
  • the API error now keeps its code, so a 409 approval_expired reads as "expired at …"
  • works the same in local mode, which serves the same routes and resumes the workflow through the local engine
  • docs in docs/durable-functions.md

Verification

  • go build ./..., go vet ./..., go test ./...
  • go tool golangci-lint run --new-from-rev=origin/main ./...: 0 issues
  • make openapi-generated-check
  • E2E, not run yet because they need Hosting's approvals deployed: TestAPIE2ECloudDurableApprovals (tests/e2e/api, against staging) and the local-mode round trip (tests/e2e/localmode). The project-token refusal is also covered from Hosting's tests/e2e/cli.

Merge order

  1. SDK releases: JS SDK 2.0 (feat(durable): add waitForApproval and the approvals client volcano-sdk-js#315) and Ruby SDK 0.34 (feat(durable): list, read, and decide durable approvals volcano-sdk-ruby#341) are published. Python SDK 0.14 (feat(durable): add wait_for_approval and the approvals client volcano-sdk-python#389) is merged and waits on its release PR (chore(main): release 0.14.0 volcano-sdk-python#378). 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 #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:17
`volcano [cloud] durable approvals` lists, reads, counts, approves and denies
the approvals a workflow opens with `ctx.waitForApproval`. Approve and deny
show the title and confirm unless `--yes`; every command takes `--json`.

A 403 for a project access token says a person has to decide. A 409 reads
the approval again and says who decided it and when, or when it expired.
Repeating the decision an approval already has succeeds and changes nothing,
as the API does.

The vendored contract gains the approvals paths and schemas from the hosting
bundle, and the client is regenerated from it. The cloud and local-mode E2E
round trips pin `@volcano.dev/sdk` ^1.16.0, the first release with
`waitForApproval`.

Co-authored-by: Cursor <cursoragent@cursor.com>
The cloud round trip now mints a project access token and checks it can read a pending approval but not approve it.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Map the API's real project-token refusals ("cannot decide durable
  approvals" and "project access token is read-only") to the human-only
  message on any credential.
- Send stats' window with both ends from one clock reading, so
  --since 366d fits the API's limit, and refuse a longer window before
  the request.
- Report a pending approval past its deadline as expired instead of
  prompting for it or surfacing the API's 409.
- Strip control characters from workflow-supplied text in human output;
  --json stays as the API sent it. stripControl moves from internal/docs
  to theme.StripControl so both share it.
- Show list's --since without a default in the docs table, and check the
  readable list output in the local-mode E2E as the cloud E2E does.

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

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
approve and deny compared a pending approval's expires_at with the local
clock and refused before asking the API, so a machine whose clock runs
fast turned away a decision the API would have taken. The decision is now
always sent, and the API's 409 approval_expired is answered with the same
"expired at" message, using the code the API error now carries.

Co-authored-by: Cursor <cursoragent@cursor.com>
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 ee48168. The CLI matches Hosting's contract: the spec is identical to Hosting's bundle, and regenerating client.gen.go gives the same bytes. Adding code to the typed error doesn't change Error() or any existing command's output. A prompt with no TTY fails instead of hanging, and list pages like the other list commands. Build, vet, tests, and lint are clean.

localmode-e2e won't turn green when Hosting merges. Today's 404 hides a second failure. Both E2E fixtures pin @volcano.dev/sdk ^1.16.0, which isn't on npm (latest 1.15.1), so durable deploy --all can't install dependencies. check / localmode-e2e is also required by release-please.yml, so overriding it once leaves later PRs and release PRs red. I'd release the SDKs first, then this PR, then Hosting (details on Kong/volcano-hosting#1678).

The other comments are P3s.

Comment thread tests/e2e/localmode/durable_approvals_test.go Outdated
Comment thread internal/durable/durable.go Outdated
Comment thread internal/durable/durable.go
Comment thread internal/cmd/durable/approvals/approvals.go Outdated
Comment thread internal/cmd/durable/approvals/stats.go Outdated
subnetmarco and others added 2 commits October 8, 2026 08:40
Co-authored-by: Cursor <cursoragent@cursor.com>
list --since sends only the start, so the API's clock ends the window; stats leaves its longest window to the API. The refusal also says to unset VOLCANO_TOKEN.

Co-authored-by: Cursor <cursoragent@cursor.com>
subnetmarco and others added 2 commits October 8, 2026 09:45
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	docs/README.md
#	internal/apiclient/client.gen.go
#	openapi/openapi.yaml
Co-authored-by: Cursor <cursoragent@cursor.com>
@subnetmarco

Copy link
Copy Markdown
Member Author

On the review summary: the merge order in the description is now the one you suggested (SDK releases, then this PR, then Hosting, then web and skills). localmode-e2e will stay red on later CLI PRs and release PRs from this merge until the first nightly local-mode image built after Hosting merges, so Hosting should follow this PR closely rather than wait.

Co-authored-by: Cursor <cursoragent@cursor.com>
@subnetmarco
subnetmarco marked this pull request as ready for review October 10, 2026 06:51
@subnetmarco
subnetmarco requested a review from a team as a code owner October 10, 2026 06:51
Copilot AI balanced review requested due to automatic review settings October 10, 2026 06:51

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.

Co-authored-by: Cursor <cursoragent@cursor.com>
@subnetmarco
subnetmarco merged commit 6c7a517 into main Oct 11, 2026
10 of 11 checks passed
@subnetmarco
subnetmarco deleted the feat/durable-approvals branch October 11, 2026 01:20
@kong-volcano-app kong-volcano-app Bot mentioned this pull request Oct 11, 2026
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