Tighten static-stage checks against the atproto OAuth spec
Four spec-conformance gaps surfaced during a review pass of the
`test oauth client` implementation against
<https://atproto.com/specs/oauth>, all of which caused either false
positives against spec-conformant clients or false negatives against
non-conformant ones:
1. `response_types` required exact equality with `["code"]`. The spec
(<https://atproto.com/specs/oauth#client-metadata>) only requires
`"code"` to be included; additional values are not forbidden.
Relax the check to membership.
2. `grant_types` rejected any value outside
{`authorization_code`, `refresh_token`}. Spec only requires
`"authorization_code"` be present (and `"refresh_token"` if the
client refreshes). Drop the allow-list.
3. `metadata_document_fetchable` accepted any 2xx status. Spec
requires HTTP 200 exactly ("must be 200 (not another 2xx or a
redirect)"). Non-200 2xx now emits a `SpecViolation` with a
pointed diagnostic; transport failures and non-2xx statuses
remain `NetworkError`.
4. The metadata response's `Content-Type` was never validated. The
atproto OAuth profile requires the JSON content type. Add a new
discovery check `metadata_content_type_is_json` that accepts
`application/json` with optional parameters (e.g.
`charset=utf-8`). A 200 response whose body parses as JSON but is
labelled `text/html` now fails `metadata_content_type_is_json` and
passes `metadata_is_json` — the two checks are deliberately
independent so the specific reason surfaces.
`FakeHttpClient::add_response` now defaults its seeded `Content-Type`
to `application/json` so the dozens of tests that already serve JSON
fixtures don't each need to spell it out; tests that need a specific
(or absent) content type continue to call
`add_response_with_content_type` directly.
All 16 real-world atproto OAuth clients still pass; 325 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>