Repository navigation
Conversation
7cd039d to
b69cdc1
Compare
ebe76ec to
5660716
Compare
5660716 to
697020d
Compare
|
|
ebaec87 to
2891cbb
Compare
2768536 to
0d69f5d
Compare
0d69f5d to
b463e46
Compare
| return response_204 | ||
|
|
||
| if response.status_code == 401: | ||
| response_401 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: A logout 401 that doesn't match the Error schema now raises a raw exception from sign_out and leaves the local session set
The regenerated auth_logout._parse_response now runs Error.from_dict(response.json()) on a 401. AuthIdentityTransport.auth_logout (src/volcano_sdk/_transport_auth_identity.py:365) still calls auth_logout.sync_detailed and passes the result to parsed_response.
If a 401 body is not JSON, or has no error key (for example a 401 from a gateway or proxy), the parser raises JSONDecodeError or KeyError. Before this change, a 401 had no parser: parsed was None and parsed_response decoded the body leniently.
_sign_out_captured (src/volcano_sdk/_auth_requests.py:229-240) only catches VolcanoError. The raw exception therefore skips clear_session_if_current, and client.auth.sign_out() fails without clearing the local session. Previously it raised AuthenticationError after clearing the session.
The documented contract says the Volcano server returns Error JSON on a 401, so the trigger needs a 401 that breaks that contract. The fix is to switch the logout adapter to generated_request(...) plus unparsed_response, the pattern auth_forgot_password, auth_reset_password, and auth_signup_anonymous already use.
Found by claude-opus-5-5
| return response_204 | ||
|
|
||
| if response.status_code == 401: | ||
| response_401 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
🟡 P2: Preserve local sign-out cleanup when a 401 body cannot be parsed
If an auth proxy returns HTTP 401 with an empty or HTML body, this new parser raises JSONDecodeError. AuthIdentityTransport.auth_logout() calls it directly, while AuthRequests._sign_out_captured() catches only VolcanoError, so client.auth.sign_out() skips clear_session_if_current() and retains local credentials, violating the cleanup guarantee in docs/authentication.md. Previously this response reached normal error handling and session cleanup. Preserve raw error handling in the handwritten logout adapter or translate parsing failures into TransportError.
Found by gpt-6.1-sol
2f166c8 to
6b6ea84
Compare
| response_204 = cast(Any, None) | ||
| return response_204 | ||
|
|
||
| if response.status_code == 401: |
There was a problem hiding this comment.
⚪ P3: A malformed 401 from logout now escapes sign_out as KeyError/JSONDecodeError instead of AuthenticationError
The regenerated auth_logout._parse_response now runs Error.from_dict(response.json()) on a 401. AuthIdentityTransport.auth_logout (src/volcano_sdk/_transport_auth_identity.py:372) calls auth_logout.sync_detailed without a guard. Before this change, a 401 parsed to None and parsed_response fell back to decoding the raw body, so response_payload raised AuthenticationError. Now a 401 with a non-JSON body, or a JSON body without an error key (for example from a gateway or proxy), raises JSONDecodeError or KeyError from the transport. invoke only wraps httpx.HTTPError, and _sign_out_captured only catches VolcanoError. So sign_out() raises a raw exception and skips clear_session_if_current, leaving the local session in place. The repo already guards this case for other auth operations (see test_generated_transport_preserves_a_malformed_session_auth_error). The fix is to route logout through request_kwargs + generated_request + unparsed_response, as auth_forgot_password does, or to return unparsed_response on a 401 before parsing. A transport test with httpx.Response(401, content=b"not-json") should cover it.
Found by claude-opus-5-5
| return response_204 | ||
|
|
||
| if response.status_code == 401: | ||
| response_401 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
🟡 P2: Logout now parses 401 bodies as Error, so a malformed 401 escapes sign_out() as a raw exception and leaves the session in place
Before this change, auth_logout._parse_response returned None for a 401. parsed_response then decoded the body leniently, and response_payload(response, 204) raised AuthenticationError. The regenerated code calls Error.from_dict(response.json()) on every 401. A 401 whose body isn't JSON, or has no error key (for example a gateway's {"message": "Unauthorized"}), now raises json.JSONDecodeError or KeyError from inside auth_logout.sync_detailed.
Nothing on the way up catches it:
invokeonly wrapshttpx.HTTPError._sign_out_captured(_auth_requests.py:229-234) only catchesVolcanoError.
So client.auth.sign_out() raises a raw exception, and clear_session_if_current never runs, which leaves the local session signed in.
The repo already treats this as a contract. auth_get_user and similar operations return unparsed_response before generated parsing on 401. Tests such as test_generated_transport_preserves_a_malformed_session_auth_error assert that a not-json 401 surfaces as AuthenticationError. No test covers a 401 on logout, so CI won't catch this.
Fix: route AuthIdentityTransport.auth_logout (_transport_auth_identity.py:365-376) through the generated request_kwargs plus generated_request/unparsed_response, as auth_forgot_password already does, or short-circuit 401 before parsing. Then add a malformed-401 logout test.
Found by claude-opus-5-5
| response_204 = cast(Any, None) | ||
| return response_204 | ||
|
|
||
| if response.status_code == 401: |
There was a problem hiding this comment.
🟡 P2: Logout now parses 401 bodies, so a 401 with a malformed body makes sign_out() fail without clearing the local session
Before this PR, auth_logout._parse_response returned None for any non-204 status, and response_payload(response, 204) raised AuthenticationError. The regenerated module now runs Error.from_dict(response.json()) on 401. The adapter (_transport_auth_identity.py auth_logout) still calls sync_detailed, so this runs on every logout.
If the 401 body is not JSON (a gateway or HTML error page, an empty body), response.json() raises JSONDecodeError. If the JSON has no error key, Error.from_dict raises KeyError. Nothing on the path catches either: invoke only catches httpx.HTTPError, and _sign_out_captured only catches VolcanoError. So clear_session_if_current never runs. The stale session stays in memory and the caller gets a raw exception. That breaks the docs/authentication.md promise that a revocation failure is raised after the local session is cleared.
The repo already guards against this elsewhere: the refresh path catches KeyError/TypeError/ValueError, several auth adapters return unparsed_response on 401, and test_generated_transport_preserves_a_malformed_*_auth_error tests pin it.
Fix: switch the logout adapter to generated_request(client, request_kwargs(body=...)) plus unparsed_response, as auth_forgot_password and auth_signup_anonymous do. Add a test for a non-JSON 401 on logout.
Found by claude-opus-5-5
| pass | ||
| return cast(FrontendCustomDomainRedirect | None, data) | ||
|
|
||
| redirect = _parse_redirect(d.pop("redirect")) |
There was a problem hiding this comment.
🟠 P1: Update managed-TLS fixtures for the required redirect field
from_dict() now requires redirect, as does ProjectFrontendCustomDomain.from_dict(), but domain_payload() in src/volcano_sdk/_tests/test_managed_tls_contract.py still omits it. Consequently, the ownership-challenge, pending-domain, failed-domain, and legacy-routing tests raise KeyError('redirect') before reaching their assertions, failing the standard CI test task. Update the fixtures with redirect: None and explicitly resolve compatibility for the older-server cases.
Found by gpt-6.1-sol
| pass | ||
| return cast(FrontendCustomDomainRedirect | None, data) | ||
|
|
||
| redirect = _parse_redirect(d.pop("redirect")) |
There was a problem hiding this comment.
🟠 P1: Newly required redirect breaks the existing managed-TLS contract tests
FrontendCustomDomainResponse.from_dict (and ProjectFrontendCustomDomain.from_dict at project_frontend_custom_domain.py:215) now calls d.pop("redirect") with no default. The PR leaves src/volcano_sdk/_tests/test_managed_tls_contract.py unchanged. That file is in pytest testpaths, and its domain_payload() fixture has no redirect key. As a result, test_create_operation_decodes_the_ownership_challenge, test_pending_domain_decodes_the_certificate_validation_record, test_failed_domain_decodes_its_failure_reason and test_deprecated_routing_record_from_older_servers_round_trips should all raise KeyError: 'redirect' instead of decoding, through both create_frontend_custom_domain/get_frontend_custom_domain and the project feed decoder. Fix: add "redirect": None to domain_payload(), and consider a case with a non-null redirect. Do not hand-edit the generated model.
Found by claude-opus-5-5
| pass | ||
| return cast(FrontendCustomDomainRedirect | None, data) | ||
|
|
||
| redirect = _parse_redirect(d.pop("redirect")) |
There was a problem hiding this comment.
🟡 P2: Update managed TLS fixtures for the required redirect field
The unchanged domain_payload() in src/volcano_sdk/_tests/test_managed_tls_contract.py omits redirect. Consequently, test_create_operation_decodes_the_ownership_challenge raises KeyError('redirect'); project-feed decoding fails similarly, and polling returns a raw dictionary that fails its type assertion. These tests run through poe checks, blocking the mandatory CI gate. Update the shared fixture to include redirect: None for the new contract.
Found by gpt-6.1-sol
| return response_404 | ||
|
|
||
| if response.status_code == 416: | ||
| response_416 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: Download now parses 416 bodies strictly, so a non-JSON 416 escapes as a raw decode error
download_storage_object._parse_response now runs Error.from_dict(response.json()) for 416. Bucket.download(path, byte_range=...) reaches it through parsed_response in _transport_storage.py:169. A range past end of file that comes back with a non-JSON or error-less body now raises JSONDecodeError/KeyError instead of a VolcanoError with status 416.
The contract documents a JSON Error body (StorageRangeNotSatisfiable), and 429 already behaves this way, so impact depends on a non-conforming response. Using generated_request + unparsed_response for downloads, or catching decode errors around the generated parse, would keep error mapping robust.
Found by claude-opus-5-5
| return response_404 | ||
|
|
||
| if response.status_code == 416: | ||
| response_416 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: Preserve SDK error mapping for malformed range-error responses
If a byte-range download receives an empty or non-JSON 416, this parser now raises a raw decoding exception. StorageTransport.download_storage_object() calls it directly, so Bucket.download() no longer raises a VolcanoError carrying status 416 as it did before. This requires a response outside the documented JSON error contract; using raw response handling in the handwritten download adapter would preserve the previous robustness.
Found by gpt-6.1-sol
| @@ -6428,44 +6751,45 @@ paths: | |||
| application/json: | |||
| schema: | |||
| $ref: '#/components/schemas/Error' | |||
| /durable-callbacks: | |||
| /durable-approvals: | |||
There was a problem hiding this comment.
🟡 P2: Sync drops the /durable-callbacks contract that ctx.wait_for_callback and the published docs still use
The sync deletes POST /durable-callbacks and its succeed, fail and heartbeat operations, along with every DurableCallback* schema and the generated modules for them. The handwritten SDK, unchanged, still depends on these:
register_callbackinsrc/volcano_sdk/_durable_callback_token.py:169posts to/durable-callbacksforctx.wait_for_callback, the feature added in the base commit (feat(durable): wait for callbacks with ctx.wait_for_callback #400).- The published
docs/functions.md:241-259tells users to call/durable-callbacks/succeed,/failand/heartbeat.
The other platform paths the SDK calls (/durable-approvals, /durable-messages/*) are still in the contract, so only the callback feature is affected. There are two possibilities, and both need handling before merge:
- Hosting at f0ab44447199 does not yet have the endpoints, for example because feat(durable): wait for callbacks with ctx.wait_for_callback #400 was vendored from an unmerged Hosting change. Then this sync rolls the vendored contract back.
- Hosting removed the endpoints. Then
wait_for_callbackand its docs are shipping against a server that no longer serves them.
CONTRIBUTING.md requires SDK-facing wire-contract changes to update the affected facade, tests and public examples together. Hosting compatibility should be confirmed first; then either re-sync from a Hosting commit that has the endpoints, or remove or gate wait_for_callback and its docs.
Found by claude-opus-5-5
| @@ -6428,44 +6751,45 @@ paths: | |||
| application/json: | |||
| schema: | |||
| $ref: '#/components/schemas/Error' | |||
| /durable-callbacks: | |||
| /durable-approvals: | |||
There was a problem hiding this comment.
🟡 P2: Restore the callback contract still used by the SDK
The sync deletes all four /durable-callbacks operations and their generated models, while register_callback() in src/volcano_sdk/_durable_callback_token.py still posts to /durable-callbacks for ctx.wait_for_callback(). Published docs/functions.md also instructs users to call the completion and heartbeat routes. The vendored contract consequently no longer describes this shipped feature. Re-sync a compatible Hosting bundle containing callbacks, or coordinate an intentional removal with the facade and documentation.
Found by gpt-6.1-sol
| response_404 = cast(Any, None) | ||
| return response_404 | ||
|
|
||
| if response.status_code == 416: |
There was a problem hiding this comment.
⚪ P3: A 416 download response is now parsed strictly, so a non-JSON 416 escapes as a raw decode error
The regenerated _parse_response now runs Error.from_dict(response.json()) for 416. StorageTransport.download_storage_object (_transport_storage.py:169) calls sync_detailed and then parsed_response. Suppose a byte-range download past end of file returns a 416 with an empty or non-JSON body, or JSON with no error key. The caller then gets JSONDecodeError or KeyError instead of a VolcanoError with status 416.
The contract documents a JSON Error body (StorageRangeNotSatisfiable), and 429 already behaves this way, so this needs a non-conforming response. Two fixes would keep error mapping robust: route downloads through generated_request plus unparsed_response, or catch decode errors around the generated parse.
Found by claude-opus-5-5
| response_404 = cast(Any, None) | ||
| return response_404 | ||
|
|
||
| if response.status_code == 416: |
There was a problem hiding this comment.
⚪ P3: Malformed 416 responses bypass SDK error mapping
If Bucket.download(..., byte_range=...) receives an empty or non-JSON 416, or JSON without error, this parser raises JSONDecodeError or KeyError. StorageTransport.download_storage_object() calls it directly, so callers receive a raw exception instead of a VolcanoError carrying status 416. This requires a response outside the documented JSON contract. Using generated_request plus unparsed_response in the handwritten adapter would preserve the previous error mapping.
Found by gpt-6.1-sol
| provider = d.pop("provider", UNSET) | ||
| provider = d.pop("provider") | ||
|
|
||
| provider_user_id = d.pop("provider_user_id") |
There was a problem hiding this comment.
🟠 P1: Newly required provider_user_id breaks the unchanged OAuth fixture that every StateTransport test builds
Provider items now require provider, provider_user_id, linked_at and updated_at, and from_dict calls d.pop("provider_user_id") with no default. The unchanged tests break in four places:
OAuthTransport.__init__(src/volcano_sdk/_tests/test_state.py:233) builds a fixture (lines 202-213) with noprovider_user_id, so it raisesKeyError.StateTransportinherits this throughsuper().__init__()(line 441), so every test that builds one errors during setup. The file constructs it 149 times.test_state.py:1952and:1967callfrom_dictwithout the field.test_generated_transport.py:598now getsVolcanoError.test_auth_parser_boundaries.py:252builds the item with onlyprovider=. That raisesTypeError, and basedpyright rejects the call.
Outside the tests, list_linked_oauth_providers() now raises against a Hosting deployment that doesn't send the field.
Fix: update the fixtures and the boundary test. Do not hand-edit generated code. Consider exposing provider_user_id on LinkedOAuthProvider.
Found by claude-opus-5-5
| provider = d.pop("provider", UNSET) | ||
| provider = d.pop("provider") | ||
|
|
||
| provider_user_id = d.pop("provider_user_id") |
There was a problem hiding this comment.
🟠 P1: Required provider_user_id breaks existing OAuth fixtures
_linked_oauth_providers() in src/volcano_sdk/_tests/test_state.py omits provider_user_id and is decoded during every StateTransport construction, causing widespread KeyError failures before test assertions. test_generated_transport_lists_linked_oauth_providers and the future-provider fixture omit it too. Update these fixtures, and adapt the incomplete-item test, whose strict from_dict() call now raises before its pytest.raises block.
Found by gpt-6.1-sol
| provider: str | Unset = UNSET | ||
| linked_at: datetime.datetime | Unset = UNSET | ||
| updated_at: datetime.datetime | Unset = UNSET | ||
| provider: str |
There was a problem hiding this comment.
🟠 P1: Non-optional provider type invalidates the handwritten type guard
provider is now statically str, but linked_oauth_provider_from_model() in src/volcano_sdk/_auth_values.py:391 still calls isinstance(provider, str). With basedpyright's configured typeCheckingMode = "all", this produces reportUnnecessaryIsInstance and fails poe types, independently of the fixture failures. Preserve runtime validation through an object-typed helper, as linked_oauth_datetime() already does.
Found by gpt-6.1-sol
| provider: str | Unset = UNSET | ||
| linked_at: datetime.datetime | Unset = UNSET | ||
| updated_at: datetime.datetime | Unset = UNSET | ||
| provider: str |
There was a problem hiding this comment.
🟠 P1: provider is now typed str, so the isinstance guard in _auth_values.py fails type checks
linked_oauth_provider_from_model() in src/volcano_sdk/_auth_values.py:391 still runs if not isinstance(provider, str) or not provider.strip(). Since item.provider is now statically str, the left operand is always false:
- basedpyright, which runs with
typeCheckingMode = "all"(pyproject.toml:141) and checks everything undersrcexcept_generated, should reportreportUnnecessaryIsInstance. - mypy, which enables
redundant-expr, should report that the left operand oforis always false.
Either failure blocks poe types on its own, apart from the test failures. Fix: keep the runtime validation by sending the value through an object-typed helper, as linked_oauth_datetime() already does.
Found by claude-opus-5-5
| provider: str | Unset = UNSET | ||
| linked_at: datetime.datetime | Unset = UNSET | ||
| updated_at: datetime.datetime | Unset = UNSET | ||
| provider: str |
There was a problem hiding this comment.
🟡 P2: Adapt handwritten validation to the narrowed provider type
linked_oauth_provider_from_model() in src/volcano_sdk/_auth_values.py:391 still checks isinstance(provider, str), although provider is now statically str. With the configured basedpyright typeCheckingMode = "all", this triggers reportUnnecessaryIsInstance and blocks poe types. Preserve runtime validation through an object-typed helper, as the adjacent datetime validation already does.
Found by gpt-6.1-sol
| return response_401 | ||
|
|
||
| if response.status_code == 403: | ||
| response_403 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: A non-JSON 403 from update_user now surfaces as a misleading "malformed user profile" error
auth_update_user._parse_response now runs Error.from_dict(response.json()) on 403. The adapter (src/volcano_sdk/_transport_auth_identity.py:330-342) short-circuits only 401 to unparsed_response, then calls build_auth_update_user_response inside a try that turns KeyError/ValueError into AuthenticationError(MALFORMED_USER_PROFILE).
Before this change, every 403 parsed to None and parsed_response reported the server's status and message. Now a 403 whose body isn't JSON, or has no error key (for example a proxy or WAF page), raises an error saying the user profile was malformed. A conforming {"error": ...} 403 behaves as before. Fix: return unparsed_response for every non-200 status before generated parsing.
Found by claude-opus-5-5
|
|
||
| return response_401 | ||
|
|
||
| if response.status_code == 403: |
There was a problem hiding this comment.
⚪ P3: A non-JSON 403 from update_user now surfaces as a misleading "malformed user profile" error
auth_update_user._parse_response now runs Error.from_dict(response.json()) on 403. The adapter (src/volcano_sdk/_transport_auth_identity.py:325-342) short-circuits only 401 to unparsed_response. It then calls build_auth_update_user_response inside a try that turns KeyError/ValueError into AuthenticationError(MALFORMED_USER_PROFILE). A 403 whose body isn't JSON or has no error key, such as a proxy or WAF page, now reports a malformed user profile instead of the server's status. A conforming {"error": ...} 403 behaves as before. Returning unparsed_response for every non-200 status before generated parsing would keep the old mapping.
Found by claude-opus-5-5
| return response_401 | ||
|
|
||
| if response.status_code == 403: | ||
| response_403 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
⚪ P3: Preserve status information for malformed update-user errors
A non-JSON or error-less 403, such as a proxy rejection, now raises during generated parsing. AuthIdentityTransport.auth_update_user() catches that failure and turns it into AuthenticationError("Expected a complete user profile"), losing the original status and reporting a misleading profile error. This requires a response outside the documented error schema. Returning unparsed_response for non-200 statuses before generated parsing would preserve the previous error mapping.
Found by gpt-6.1-sol
|
|
||
| return response_401 | ||
|
|
||
| if response.status_code == 403: |
There was a problem hiding this comment.
⚪ P3: Preserve status information for malformed update-user errors
A non-JSON or error-less 403 now raises during generated parsing. AuthIdentityTransport.auth_update_user() catches that failure and reports AuthenticationError("Expected a complete user profile"), losing the original 403 and misidentifying the failure. This requires a response outside the documented error schema, such as a proxy rejection. Returning unparsed_response for non-200 statuses before generated parsing would preserve the previous mapping.
Found by gpt-6.1-sol
| return response_401 | ||
|
|
||
| if response.status_code == 403: | ||
| response_403 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
🟡 P2: Preserve status information for malformed update-user errors
A non-JSON or error-less 403, such as a proxy rejection, now raises during generated parsing. AuthIdentityTransport.auth_update_user() catches that exception and reports AuthenticationError("Expected a complete user profile"), losing the original 403 and misidentifying the failure. Return unparsed_response for non-200 statuses before parsing the successful profile.
Found by gpt-6.1-sol
| return response_404 | ||
|
|
||
| if response.status_code == 416: | ||
| response_416 = Error.from_dict(response.json()) |
There was a problem hiding this comment.
🟡 P2: Preserve SDK error mapping for malformed range errors
If Bucket.download(..., byte_range=...) receives an empty, non-JSON, or error-less 416, this new parser raises JSONDecodeError or KeyError. StorageTransport.download_storage_object() calls it directly, so callers now receive a raw exception instead of a VolcanoError carrying status 416. Preserve raw error handling in the handwritten adapter before generated parsing.
Found by gpt-6.1-sol
| response_404 = cast(Any, None) | ||
| return response_404 | ||
|
|
||
| if response.status_code == 416: |
There was a problem hiding this comment.
🟡 P2: A malformed 416 range response now escapes Bucket.download as a raw decode error
The regenerated _parse_response now runs Error.from_dict(response.json()) on 416. StorageTransport.download_storage_object (src/volcano_sdk/_transport_storage.py:169-175) calls sync_detailed and then parsed_response. A byte-range download past the end of the file can come back as a 416 with an empty body, a non-JSON body, or JSON without an error key (for example from a proxy). The caller then gets JSONDecodeError or KeyError instead of a VolcanoError carrying status 416. Fix: route downloads through generated_request plus unparsed_response, the pattern other adapters use, or catch decode errors around the generated parse.
Found by claude-opus-5-5
|
|
||
| return response_401 | ||
|
|
||
| if response.status_code == 403: |
There was a problem hiding this comment.
🟡 P2: A malformed 403 from update_user is now reported as a "malformed user profile" error
auth_update_user._parse_response now parses 403 bodies as Error. The adapter (src/volcano_sdk/_transport_auth_identity.py:325-342) only short-circuits 401 to unparsed_response. It then calls build_auth_update_user_response inside a try that turns KeyError/ValueError into AuthenticationError(MALFORMED_USER_PROFILE). A 403 whose body is not JSON or has no error key, such as a WAF or proxy rejection, therefore loses its status and is reported as a malformed profile. Fix: return unparsed_response for every non-200 status before generated parsing.
Found by claude-opus-5-5
Summary
API change report
Public API
New Endpoints: 44
GET /durable-functions/{functionId}/executions/{executionId}
POST /durable-functions/{functionId}/executions/{executionId}/stop
GET /lavaflow/resources
GET /lavaflow/runs
GET /lavaflow/runs/{run_id}
GET /lavaflow/settings
PUT /lavaflow/settings
GET /lavaflow/stats
GET /projects/{id}/capabilities
GET /projects/{id}/databases/{databaseName}/migration-runs
POST /projects/{id}/databases/{databaseName}/migration-runs
GET /projects/{id}/databases/{databaseName}/migration-runs/{runId}
GET /projects/{id}/databases/{databaseName}/migrations
POST /projects/{id}/databases/{databaseName}/migrations/baseline
GET /projects/{id}/frontends/{frontendId}/candidates
POST /projects/{id}/frontends/{frontendId}/candidates
DELETE /projects/{id}/frontends/{frontendId}/candidates/{candidateId}
GET /projects/{id}/frontends/{frontendId}/candidates/{candidateId}
POST /projects/{id}/frontends/{frontendId}/candidates/{candidateId}/deployments
POST /projects/{id}/frontends/{frontendId}/candidates/{candidateId}/promote
POST /projects/{id}/frontends/{frontendId}/deployments
GET /projects/{id}/frontends/{frontendId}/domains
POST /projects/{id}/frontends/{frontendId}/domains
DELETE /projects/{id}/frontends/{frontendId}/domains/{domain}
GET /projects/{id}/frontends/{frontendId}/domains/{domain}
PATCH /projects/{id}/frontends/{frontendId}/domains/{domain}
GET /projects/{id}/frontends/{frontendId}/frontend-routes
POST /projects/{id}/frontends/{frontendId}/frontend-routes
DELETE /projects/{id}/frontends/{frontendId}/frontend-routes/{routeId}
PUT /projects/{id}/frontends/{frontendId}/frontend-routes/{routeId}
GET /projects/{id}/frontends/{frontendId}/serving
POST /projects/{id}/frontends/{frontendId}/serving/rollback
GET /projects/{id}/git-connection/owner-push
PUT /projects/{id}/git-connection/root-directory
GET /projects/{id}/git-deployment-runs
GET /projects/{id}/git-deployment-runs/{runId}
GET /projects/{id}/notifications
PUT /projects/{id}/notifications
POST /projects/{id}/notifications/test
POST /projects/{id}/notifications/webhook-secret
DELETE /user/sessions
GET /user/sessions
DELETE /user/sessions/{sessionId}
DELETE /user/tokens/current
Deleted Endpoints: 6
GET /projects/{id}/autofix
PUT /projects/{id}/autofix
GET /projects/{id}/autofix-runs
GET /projects/{id}/autofix-runs/{runId}
DELETE /projects/{id}/autofix/api-key
POST /projects/{id}/issues/{issueId}/autofix-runs
Modified Endpoints: 270
POST /auth/device/token
access_denied- the approving account's email domain is not inallowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. Re-checked here because approval and redemptionare separate requests.
' to '
access_denied- the approving account's canonical email, or the emailof any of its identities, is not in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. Re-checked here because approval and redemptionare separate requests.
'
POST /auth/device/verify
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/forgot-password
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/logout
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/oauth/exchange
in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. Both are re-checked here because the code outlivesthe callback that issued it.
' to 'Email confirmation is now required, or the account's canonical email or
the provider identity's email is not in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. Both are re-checked here because the code outlivesthe callback that issued it.
'
GET /auth/oauth/providers
user's own account ID at each provider. Match on
provider_user_id,which never changes, rather than on the login or email. For
GitHub, it is the numeric user ID that GitHub reports elsewhere, such
as a webhook's
sender.id.'
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/oauth/{provider}/call-api
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/oauth/{provider}/callback
flow's stored redirect_url is no longer registered in
allowed_redirect_urls (re-checked at callback time)
' to 'Missing/invalid code or state, the state parameter expired, the
provider was disabled after the flow started, or the flow's stored
redirect_url is no longer registered in allowed_redirect_urls (both
re-checked at callback time)
'
allowed_email_domains. Creatingan account is refused under
signupandsignup_and_signin; signing inan already-linked account is refused under
signup_and_signin.' to 'The provider's email domain is not in
allowed_email_domains. Creatingan account is refused under
signupandsignup_and_signin; signing inan already-linked account is refused under
signup_and_signin, as it iswhen the account's canonical email is outside the list. A new account
at a reserved domain (
anonymous.volcano.internal,volcano.local) isrefused whatever the project allows.
'
POST /auth/oauth/{provider}/link
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/oauth/{provider}/refresh-token
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/oauth/{provider}/token
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'DELETE /auth/oauth/{provider}/unlink
Cannot unlink if it's the only authentication method.
' to 'Remove OAuth provider from user's account. Refused when the account would
have no other way to sign in: another linked OAuth provider that's enabled
for the project, or a password while email/password sign-in is on for the
project (
enable_email_password).'
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/password-policy
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/platform/exchange
account's email domain is not in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. The domain isre-checked here because the minted platform token outlives the
session it is exchanged from.
' to 'Exchange not allowed for this session/client/project, or the
account's canonical email, or the email of any of its identities, is
not in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. The domain is re-checked here because the minted platform token outlives thesession it is exchanged from.
'
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/refresh
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin, so the sessioncannot be extended
' to 'The account's canonical email, or the email of the identity the
session signed in with, is not in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin, so the sessioncannot be extended
'
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/resend-confirmation
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/reset-password
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/signin
not in
allowed_email_domainswhileallowed_email_domains_modeissignup_and_signin. The domain is taken from the account's canonicalemail (its primary identity), which is not necessarily the address in
the request.
' to 'Forbidden - Email/password is turned off for the project
(
enable_email_password: false), the anon key lacks signinpermission, or the email domain is not in
allowed_email_domainswhile
allowed_email_domains_modeissignup_and_signin. Both theaccount's canonical email (its primary identity) and the email signed
in with must be on the list.
'
POST /auth/signup
email domain is not in
allowed_email_domains. The internalanonymous.volcano.internaldomain is reserved for anonymousaccounts and is refused whatever the project allows.
' to 'Forbidden - Signups disabled, anon key lacks signup permission, or the
email domain is not in
allowed_email_domains. The internalanonymous.volcano.internalandvolcano.localdomains are reservedfor the anonymous and Builder debug accounts Volcano creates, and are
refused whatever the project allows.
'
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'POST /auth/signup-anonymous
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'GET /auth/user
plan_required(403) means the project owner's plan does not include the requested feature or option; upgrading the plan unlocks it.person_required(403) means a project access token tried to change LavaFlow settings, which only the project's owner changes, with a platform token. A 403 without a code leaves the reason unspecified: it can be an access refusal, a reached limit, or a plan check that could not read the owner's plan. Match on the code, not the message text.'PUT /auth/user
Report truncated; see the source commit for the complete contract diff.
Validation