issue_mcp_token gains a user_type field (account|end_user), derived from
invoke_from ({DEBUGGER,EXPLORE}->account else end_user) so the enterprise side
routes webapp end-users to the published-webapp token store.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
controllers/common/errors.py is consumed by other controllers; keep it
untouched and declare the openapi-surface error as an OpenApiError
subclass. Wire output is identical.
Review follow-ups:
- finalize() now falls back to a minimal status-derived body instead of
letting a ValidationError escape the framework error handler when an
already-rewritten e.data carries malformed canonical details
- document that a pre-built e.response bypasses the body formatter
- note the promote-to-libs seam for transport-generic codes in the module
docstring
- CLI: skip the loc prefix when a server error detail has an empty loc
@accepts(query/body) now emits a 422 response with ErrorBody; @returns emits a
default error response with ErrorBody. ErrorBody (and auto-promoted ErrorDetail)
are registered in openapi_ns so they appear in definitions and are reachable from
both error response entries.
Adds TestErrorMatrix (23 parametrized rows) covering every exception class
raised or mapped in files.py and app_run.py, asserting the exact wire code
each path emits and that every emitted code is an OpenApiErrorCode member.
Also adds error_code = "filename_not_exists" to FilenameNotExistsError, which
had no explicit code and was falling through to the status-map (bad_request).
Replaces the _quota_error/.response hack in workspaces.py with two
throwable OpenApiError subclasses (MemberLimitExceeded,
MemberLicenseExceeded) so all 403 quota responses flow through
OpenApiErrorFormatter rather than bypassing it via the early-return
in external_api.py. Wire codes rename to member_limit_exceeded and
member_license_exceeded.
Install OpenApiErrorFormatter on the openapi blueprint's ExternalApi so
all non-2xx responses from /openapi/v1 carry the canonical ErrorBody shape
(code, message, status, optional details/hint). RFC 8628 device-flow
endpoints are unaffected — their flat {error: ...} shape is passed through
unchanged.
Also: set catch_all_404s=True when a formatter is present so unknown
routes return canonical JSON 404s (not Flask's default HTML 404).
Override _help_on_404 to suppress route suggestions, which would corrupt
the JSON contract and enumerate routes to unauthenticated callers.
Both behaviours are scoped by formatter presence — other blueprints that
construct ExternalApi without error_body_formatter are byte-identical.
Wire-level tests added to TestWireContract (3 tests, 18 total):
- 422 from @accepts validation carries code/status/details
- unknown-route 404 is canonical JSON without route suggestions
- device token POST returns RFC 8628 flat shape untouched by formatter
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>