refactor(openapi): resource-oriented paths for /openapi/v1 + difyctl version gate#38367
Merged
Conversation
Contributor
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-07-07 10:41:19.805917551 +0000
+++ /tmp/pyrefly_pr.txt 2026-07-07 10:41:11.060866487 +0000
@@ -2273,23 +2273,21 @@
ERROR `in` is not supported between `Literal['GET']` and `None` [not-iterable]
--> tests/unit_tests/controllers/openapi/test_workspaces.py:50:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:179:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:178:12
ERROR `in` is not supported between `Literal['POST']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:180:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:179:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:185:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:184:12
ERROR `in` is not supported between `Literal['GET']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:186:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:185:12
ERROR `in` is not supported between `Literal['POST']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:187:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:186:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:192:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:191:12
ERROR `in` is not supported between `Literal['DELETE']` and `None` [not-iterable]
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:192:12
+ERROR `in` is not supported between `Literal['PATCH']` and `None` [not-iterable]
--> tests/unit_tests/controllers/openapi/test_workspaces_members.py:193:12
-ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:198:12
-ERROR `in` is not supported between `Literal['PUT']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:199:12
ERROR Cannot index into `Iterable[bytes]` [bad-index]
--> tests/unit_tests/controllers/service_api/app/test_audio.py:186:16
ERROR Cannot index into `Response` [bad-index]
|
Contributor
Pyrefly Type Coverage
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #38367 +/- ##
==========================================
+ Coverage 85.33% 85.39% +0.05%
==========================================
Files 5005 5163 +158
Lines 265421 269857 +4436
Branches 50474 51554 +1080
==========================================
+ Hits 226504 230434 +3930
- Misses 34469 34974 +505
- Partials 4448 4449 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
d883dc1 to
cd5a5cb
Compare
GareArc
reviewed
Jul 3, 2026
d2340d5 to
95a28dd
Compare
fdd65fb to
1ceee98
Compare
GareArc
reviewed
Jul 7, 2026
GareArc
reviewed
Jul 7, 2026
f18035d to
94345d1
Compare
GareArc
reviewed
Jul 7, 2026
…hods
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.
Teach the oRPC codegen to split AIP-136 `resource:action` segments so custom methods nest as clean sibling nodes (apps.byAppId.run) instead of collapsing the param into a fake static key (apps.appIdRun). Regenerate the committed oRPC/Zod contract and the split Markdown docs from the new spec. Update the difyctl call-sites whose wire path or generated method name moved (describe->get, export->dsl, check-dependencies->dependencies, files/upload-> files, human_input->human-input-forms:submit, role->PATCH) and the Hono mock server + URL assertions in the CLI test suite.
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.
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.
Symmetric client-side gate for the difyctl -> dify direction: refuse a Dify server older than difyctl requires (compat.minDify 1.16.0) with a hard error (exit 6). Cached per host for 1h, positive-only so a just-upgraded server clears a previous block immediately; fails open on probe error. Checked fresh at login before persisting the session, and on every authed command. Split evaluateCompat's "unsupported" verdict into too_old (hard fail via enforceDifyVersion) and too_new (kept as the existing soft nudge). Map server 426 responses to the version-compat exit code. Bump difyctl to 0.2.0.
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).
…pyproject Address review feedback on the difyctl version gate: - Compare only the numeric core (major.minor.patch), dropping pre-release/ channel suffixes from ordering, on both the server gate and the difyctl compat check. Prevents a future floor bump (e.g. 0.3.0) from spuriously rejecting a 0.3.0-rc.1 build, and simplifies compat.ts. - Move MIN_DIFYCTL_VERSION out of _version_gate.py into pyproject.toml [tool.dify] min_difyctl_version, read via dify_config, so the difyctl floor sits next to the server version for release-time paired bumps.
The [tool.dify] comment and _version_gate.py docstring pointed at dify-todo/inprogress/difyctl/openapi/version.md, a path that only exists in a private umbrella workspace — a dangling reference in the public repo (per GareArc review). The 'bump in lockstep' guidance is self-contained without it.
94345d1 to
847ae4a
Compare
wylswz
approved these changes
Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Aligns the user-scoped
/openapi/v1surface (consumed by thedifyctlCLI) with our API Design Guide: verb path suffixes are replaced by standard resourceGETs and AIP-136:actioncustom methods.GET /apps/{id}/describeGET /apps/{id}GET /permitted-external-apps/{id}/describeGET /permitted-external-apps/{id}POST /apps/{id}/runPOST /apps/{id}:runPOST /apps/{id}/tasks/{tid}/stopPOST /apps/{id}/tasks/{tid}:stopPOST /apps/{id}/files/uploadPOST /apps/{id}/filesGET /apps/{id}/exportGET /apps/{id}/dslGET /apps/{id}/check-dependenciesGET /apps/{id}/dependencies:checkPOST /workspaces/{ws}/apps/imports/{iid}/confirmPOST /workspaces/{ws}/apps/imports/{iid}:confirmPOST /workspaces/{ws}/switchPOST /workspaces/{ws}:switchGET|POST /apps/{id}/form/human_input/{tok}/apps/{id}/human-input-forms/{tok}(+:submitfor POST)PUT /workspaces/{ws}/members/{mid}/rolePATCH /workspaces/{ws}/members/{mid}difyctlis the only consumer and ships in lockstep with the server, so this is a hard cutover — no/v2, no dual-mount. RFC 8628 device-flow routes and thelimitpagination param are intentionally left unchanged.The change is self-contained in this repo: server routes, the regenerated committed oRPC/Zod contract (
packages/contracts) plus a codegen tweak that nests:actionmethods cleanly (apps.byAppId.runrather thanapps.appIdRun), the regenerated Markdown docs, and thedifyctl(cli/) call-sites + mock server.🔀 Version compatibility gate
Because the path cutover is breaking, an outdated
difyctlthat still calls the old paths would otherwise get a bare404/405. This PR adds a symmetric version gate so each side refuses a too-old counterpart with a clear "upgrade" message — each side stores the oldest counterpart it supports and hard-fails only on the lower bound (so there are no false positives; "too new" stays a soft nudge):/openapi/v1)MIN_DIFYCTL_VERSION=0.2.0-alpha< 0.2.0-alphabefore_app_requesthook so it fires even for removed paths (which no longer match a route and would otherwise 404). Allowlists_version/_health; fails open for non-difyctl / unparseable User-Agents.difyctl.compat.minDify=1.16.0< 1.16.0loginbefore persisting the session.A server 426 also maps to the CLI's version-compat exit code (6).
difyctlis bumped to0.2.0-alpha— the first build that speaks the new paths — and the two floors move together at the cutover (dify1.16.0↔ difyctl0.2.0-alpha).Verification: backend openapi unit tests (449, incl. the new version-gate suite), swagger snapshot tests (37), and the CLI suite (1252) all pass, together with the contract and CLI type-checks, eslint, and the release-manifest validator. Integration/container tests are CI-only.
Screenshots
N/A — backend/CLI API refactor, no UI change.
Checklist
/openapi/v1is not part of the public docs; the in-repo generated Markdown is regenerated in this PR.api/openapi/markdown/openapi-openapi.md).tsgofor the contract and CLI).