The release manifest validator (a CI-only step) requires the alpha channel's
version to match X.Y.Z-alpha; plain 0.2.0 failed it. Use 0.2.0-alpha and pin
the server floor MIN_DIFYCTL_VERSION to 0.2.0-alpha (0.1.0-alpha still
rejected; the new client accepted).
Add an app-level version gate on /openapi/v1 that reads the difyctl version
from the User-Agent and returns 426 Upgrade Required (with an upgrade hint)
when the client is older than MIN_DIFYCTL_VERSION (0.2.0).
Registered via before_app_request so it also fires for requests to removed
paths — those no longer match a route and would otherwise 404 before a
blueprint-scoped hook runs. Allowlists _version/_health and fails open for
non-difyctl or unparseable User-Agents.
Per review: GET /apps/{id}/dependencies over-promised — the handler returns
only leaked deps (referenced in the DSL but not installed), not the full set.
Model it as the AIP-136 read-only custom method:
GET /apps/{id}/dependencies -> GET /apps/{id}/dependencies:check
This leaves the plain noun free for a future full listing. Regenerate the
contract + Markdown docs and update the difyctl call-site, mock, and tests.
Align the /openapi/v1 surface with the API Design Guide: replace verb path
suffixes with standard resource GETs and AIP-136 `:action` custom methods.
GET /apps/{id}/describe -> GET /apps/{id}
GET /permitted-external-apps/{id}/describe -> GET /permitted-external-apps/{id}
POST /apps/{id}/run -> POST /apps/{id}:run
POST /apps/{id}/tasks/{tid}/stop -> POST /apps/{id}/tasks/{tid}:stop
POST /apps/{id}/files/upload -> POST /apps/{id}/files
GET /apps/{id}/export -> GET /apps/{id}/dsl
GET /apps/{id}/check-dependencies -> GET /apps/{id}/dependencies
POST /workspaces/{ws}/apps/imports/{iid}/confirm -> POST /workspaces/{ws}/apps/imports/{iid}:confirm
POST /workspaces/{ws}/switch -> POST /workspaces/{ws}:switch
GET|POST /apps/{id}/form/human_input/{tok} -> /apps/{id}/human-input-forms/{tok} (+ :submit for POST)
PUT /workspaces/{ws}/members/{mid}/role -> PATCH /workspaces/{ws}/members/{mid}
difyctl is the sole consumer and ships in lockstep, so this is a hard cutover
(no /v2, no dual-mount). RFC 8628 device-flow routes and the `limit` pagination
param are intentionally left unchanged.
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>