ADR-013: UDAP registration metadata as an immutable value object
Status: Accepted
Context
UDAP Dynamic Client Registration signs caller-controlled client metadata into a software statement. Once signed, that statement is authoritative: the registration server uses it instead of an unsigned request-body field when the same metadata appears in both places.
This creates a trust boundary before cryptographic construction. Safire must reject malformed or ambiguous registration metadata before signing, while still preserving valid extension fields allowed by RFC 7591. Discovery entities are not an appropriate model for this boundary: they represent remote input and use warn-and-return-false compliance checks, whereas registration metadata is local input that must either produce one canonical payload or raise.
The conformance target is HL7 UDAP Security STU2 / v2.0.0, which builds on the UDAP Dynamic Client Registration profile.
Decision
Use a dedicated strict value object
UdapRegistrationMetadata validates and normalizes the caller’s metadata at construction. Invalid input raises Safire::Errors::ValidationError with the failing attribute and a value-free reason. The class does not inherit from Entity and does not use the discovery layer’s warn-and-return-false contract.
Top-level string and symbol keys are normalized to strings. Supplying both forms of one key is rejected rather than allowing insertion order to decide which value is signed. The value object and its canonical internal hash are frozen, and to_h returns a defensive copy.
Enforce exact STU2 grant shapes
Registration accepts exactly one primary grant:
authorization_code, optionally withrefresh_tokenclient_credentials
Unknown and duplicate grant values are rejected. redirect_uris, logo_uri, and generated response_types: ["code"] apply only to authorization-code registration. Redirect and logo URIs require absolute HTTPS by default.
For local development without TLS, callers may explicitly pass allow_insecure_localhost: true. This permits HTTP only on localhost and 127.0.0.1; remote HTTP remains invalid. Safire warns only when the exception is actually used. The library does not infer a framework environment because an implicit environment default could silently weaken production validation. Metadata created through the exception is intentionally documented as non-conformant.
Cancellation is a separate operation: :cancel mode. Callers omit grant_types; Safire injects an empty array and removes authorization-only metadata from the canonical result.
Reserve protocol-owned fields
Safire generates token_endpoint_auth_method: "private_key_jwt" and, when applicable, response_types. Callers cannot supply those fields, registered JWT claims, or request-envelope fields such as software_statement, certifications, and udap.
Unknown RFC 7591 extension fields are retained only when their values are exact JSON data: strings, finite JSON numbers, booleans, null, arrays, or objects with string keys and recursively valid values. Ruby-specific values and recursive collections are rejected.
Use the standard-library mail boundary
contacts must be a non-empty array of absolute URI strings with at least one valid mailto: address. Safire parses mail contacts as URI::MailTo, obtains addresses from URI::MailTo#to, and applies URI::MailTo::EMAIL_REGEXP. It does not maintain a project-specific email regular expression.
Consequences
- Signing code can consume one canonical, already validated metadata hash.
- Caller mutations cannot change metadata between validation and signing.
- Protocol-owned claims cannot be shadowed through caller input.
- Plain HTTP remains impossible for remote hosts; local HTTP requires a literal boolean opt-in and produces a warning.
- Extension metadata remains forward-compatible without permitting arbitrary Ruby objects into a JWT payload.
- Metadata validation remains independent of software-statement signing and the network registration lifecycle.
Protocols::Udap#register_clientandProtocols::Udap#cancel_registrationconsume this value object when performing end-to-end UDAP registration operations.