feat(canopy-auth): provider-agnostic OIDC (discovery + neutral naming)

Pre-existing tech debt: canopy-auth + canopy-web hardcode Keycloak-specific OIDC paths (/protocol/openid-connect/{auth,token,logout,certs}) and use keycloak_* config keys throughout. This bakes Keycloak as the only supported IdP — Okta, Auth0, Azure AD users would have to fork the codebase.

The standard fix is OIDC .well-known/openid-configuration discovery: every compliant OIDC provider exposes the metadata document at the issuer URL, with the actual endpoint URLs inside. Use that, drop the hardcoded paths.

Acceptance criteria

  • New crates/canopy-auth/src/discovery.rs module with OidcDiscovery::fetch(issuer_url) — caches per-URL with Cache-Control: max-age honoured.
  • JwksProvider::from_discovery(discovery) async constructor; legacy JwksProvider::new(issuer) deprecated (drops the /protocol/openid-connect/certs Keycloak fallback) and removed in the follow-up MR (#411 (closed) / B1).
  • ServiceSettings.keycloak_issueroidc_issuer rename (with #[serde(alias)] for one MR cycle of back-compat).
  • services/canopy-web/src/auth.rs 5 hardcoded path strings replaced with discovery.{authorization,token,end_session}_endpoint references (browser-visible discovery cached separately from server-side discovery).
  • docker-compose.yml env vars renamed (CANOPY_<SERVICE>__KEYCLOAK_*CANOPY_<SERVICE>__OIDC_*, ~34 lines).
  • New docs/modules/ROOT/pages/idp-integration.adoc documenting Keycloak default + Okta/Auth0 configuration.
  • Test fixtures cover Keycloak / Okta / Auth0 discovery doc shapes.
  • Existing cargo xtask e2e baseline still passes (regression-free).

Out of scope

  • Removing the back-compat #[serde(alias)] and the deprecated JwksProvider::new — those drop in the dependent MR (#411 (closed) BFF token refresh) once both have shipped together.
  • Azure AD smoke test — documented as untested. File a follow-up if a tenant becomes available.
  • Service-account / client-credentials flow — separate concern, file when needed.

Why priority::high

Blocks #411 (closed) (BFF token refresh — the actual production bug). Without this refactor, the #411 (closed) fix would extend Keycloak coupling instead of using RFC 6749 refresh_token against a discovered token endpoint.

Plan

docs/modules/ROOT/pages/plans/oidc-pluggability-refactor.adoc (filed alongside this issue per ADR-013).

Surfaced by

User direction during Tier A planning, 2026-05-05: "we're already touching the code in that area, do the strategic refactor now."