feat: multi-IdP claim-shape support (roles_claim_path + token validation mode)

Scope

Per-IdP roles_claim_path field in idp.toml + canopy_auth claims-extraction generalization so non-Keycloak IdPs (authentik, zitadel, kanidm, generic OIDC with custom realms) emit role claims at IdP-specific paths.

Background

Stage 4 (#493 (closed)) narrowed provider_type enum to keycloak | oidc-generic because WorkerRole::from_keycloak_roles (services/canopy-web/src/session.rs:28-40) hardcodes Keycloak's realm_access.roles claim shape. This issue unblocks the full set of OIDC backends (authentik, zitadel, kanidm).

Note: per-IdP audience already ships in Stage 4 ([[idp]].audience field is required). This FU is specifically the roles claim path + token validation mode plumbing.

Acceptance

  • IdpEntry.roles_claim_path: Option<String> (default realm_access.roles)
  • WorkerRole::from_claims(&claims, &roles_claim_path) generalized lookup
  • ProviderType enum widened to keycloak | authentik | zitadel | kanidm | oidc-generic
  • Per-IdP integration tests against each backend type
  • CHANGELOG entry

References

  • Tracking: epic &51 #460
  • Stage 4 plan: docs/modules/ROOT/pages/plans/worker-portal-redesign-stage4-idp-loader-and-sign-in.adoc
  • CRAIG idp-integration.adoc:90 (CRAIG_IDENTITY_ROLES_CLAIM_PATH)