safire 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/Gemfile.lock +5 -5
- data/README.md +14 -1
- data/ROADMAP.md +26 -5
- data/docs/adr/ADR-006-lazy-discovery.md +11 -0
- data/docs/adr/ADR-007-https-only-redirects-and-localhost-exception.md +4 -4
- data/docs/adr/ADR-008-warn-return-false-for-compliance-validation.md +17 -4
- data/docs/adr/ADR-009-oauth-error-hierarchy.md +13 -4
- data/docs/adr/ADR-011-udap-stu2-discovery-conformance.md +9 -7
- data/docs/adr/ADR-013-udap-registration-request-model.md +22 -1
- data/docs/adr/ADR-014-udap-software-statement-signing.md +8 -4
- data/docs/adr/ADR-015-client-role-and-runtime-readiness.md +207 -0
- data/docs/adr/index.md +1 -0
- data/docs/configuration/client-setup.md +2 -0
- data/docs/configuration/index.md +1 -1
- data/docs/smart-on-fhir/backend-services/index.md +19 -4
- data/docs/smart-on-fhir/backend-services/token-request.md +20 -2
- data/docs/smart-on-fhir/discovery/capability-checks.md +8 -1
- data/docs/smart-on-fhir/discovery/index.md +18 -2
- data/docs/smart-on-fhir/discovery/metadata.md +16 -2
- data/docs/troubleshooting/auth-errors.md +19 -3
- data/docs/troubleshooting/client-errors.md +58 -9
- data/docs/troubleshooting/index.md +17 -2
- data/docs/udap/dynamic-client-registration/index.md +20 -11
- data/docs/udap/dynamic-client-registration/lifecycle.md +26 -7
- data/docs/udap/dynamic-client-registration/registration-metadata.md +14 -3
- data/docs/udap/dynamic-client-registration/software-statement.md +7 -4
- data/docs/udap.md +14 -4
- data/lib/safire/client.rb +3 -2
- data/lib/safire/client_config.rb +9 -3
- data/lib/safire/protocols/json_response_parsing.rb +104 -0
- data/lib/safire/protocols/oauth_response_handling.rb +2 -5
- data/lib/safire/protocols/smart.rb +80 -17
- data/lib/safire/protocols/smart_metadata.rb +61 -15
- data/lib/safire/protocols/udap.rb +135 -55
- data/lib/safire/protocols/udap_metadata.rb +9 -3
- data/lib/safire/protocols/udap_registration_metadata.rb +31 -15
- data/lib/safire/protocols/udap_scope_coverage.rb +122 -0
- data/lib/safire/protocols/udap_signed_metadata_validator.rb +2 -2
- data/lib/safire/uri_validation.rb +9 -5
- data/lib/safire/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9019d0d248f2e4e723a7be2051feea39d3be739d6b7118b31c1d67be16aad1a
|
|
4
|
+
data.tar.gz: ed3c092bc91718d766e779b361e10275014b0d750d3f59f22fa60504b9cee5a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd779a03d653c1108aee4085d90a14f806b1cf0e337892a3a9ca08bbe41dd2f266211e5173b265b5ca597b82b60cf4a0d4509705181369deb9b7affb79eb8888
|
|
7
|
+
data.tar.gz: 69939d1190e064f56a38481d069f27d313fdfacb2efd096316add8c2db4e5baa6c7b572420f949fe952edfa844f2bb35d63ecbdf18833208fb5f95896c2d7b72
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- SMART and UDAP discovery now accept usable raw JSON-object response bodies
|
|
15
|
+
when incorrect or missing response content types leave them undecoded by the
|
|
16
|
+
HTTP adapter. Shared discovery, registration, and OAuth error handling treats
|
|
17
|
+
malformed or non-object JSON plus ambiguous, recursive, or non-JSON-compatible
|
|
18
|
+
adapter-provided Hashes as unusable instead of risking silent normalization
|
|
19
|
+
or unexpected type errors.
|
|
20
|
+
- SMART metadata diagnostics now validate array-valued field shapes. Capability
|
|
21
|
+
and signing-algorithm helpers treat malformed list advertisements as
|
|
22
|
+
unsupported instead of applying scalar substring semantics or raising an
|
|
23
|
+
unexpected type error. Authorization and token operations now also apply the
|
|
24
|
+
configured HTTPS/localhost policy to endpoints obtained through SMART
|
|
25
|
+
discovery before using them. OAuth authorization, token, registration, and
|
|
26
|
+
redirect endpoint URIs are rejected when they contain fragment components.
|
|
27
|
+
- UDAP Dynamic Client Registration now validates and snapshots caller input
|
|
28
|
+
before discovery, then gates only on trusted DCR profile, endpoint, algorithm,
|
|
29
|
+
certification, and scope values needed by the request. Missing RS256
|
|
30
|
+
advertisement and unconfirmed scope support produce value-free warnings when
|
|
31
|
+
an otherwise usable request can proceed; unrelated metadata conformance
|
|
32
|
+
defects remain available through `UdapMetadata#valid?`. In v0.4.1,
|
|
33
|
+
unadvertised wildcards still proceed after warning; registration and
|
|
34
|
+
modification require exact advertisement in v0.5.0, while wildcard scope
|
|
35
|
+
compatibility remains warning-only during cancellation.
|
|
36
|
+
- UDAP authorization-code registration now rejects only locally provable
|
|
37
|
+
`logo_uri` defects. Usable HTTPS URLs whose PNG, JPG/JPEG, or GIF format
|
|
38
|
+
cannot be inferred from the path are accepted with a value-free warning;
|
|
39
|
+
Safire does not dereference caller-supplied logo URLs during validation.
|
|
40
|
+
- UDAP registration returns metadata only for completed `200`/`201` outcomes;
|
|
41
|
+
`202 Accepted` is reported as pending rather than treated as completed.
|
|
42
|
+
Cancellation error messages now more clearly distinguish an unconfirmed
|
|
43
|
+
outcome from server rejection. Safire never retries registration lifecycle
|
|
44
|
+
requests automatically when the authorization server may already have
|
|
45
|
+
committed them.
|
|
46
|
+
|
|
47
|
+
### Deprecated
|
|
48
|
+
|
|
49
|
+
- SMART Backend Services requests without usable configured or per-call scopes
|
|
50
|
+
still use `system/*.rs` for v0.4.x compatibility, but now emit a deprecation warning.
|
|
51
|
+
Configure or pass scopes explicitly before v0.6.0, when missing scopes will
|
|
52
|
+
raise `ConfigurationError`.
|
|
53
|
+
|
|
10
54
|
## [0.4.0] - 2026-08-07
|
|
11
55
|
|
|
12
56
|
### Added
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
safire (0.4.
|
|
4
|
+
safire (0.4.1)
|
|
5
5
|
activesupport (>= 7.1, < 9)
|
|
6
6
|
addressable (~> 2.8)
|
|
7
7
|
faraday (~> 2.14)
|
|
@@ -127,8 +127,8 @@ GEM
|
|
|
127
127
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
128
128
|
rspec-support (~> 3.13.0)
|
|
129
129
|
rspec-support (3.13.6)
|
|
130
|
-
rubocop (1.
|
|
131
|
-
json (
|
|
130
|
+
rubocop (1.90.0)
|
|
131
|
+
json (>= 2.3)
|
|
132
132
|
language_server-protocol (~> 3.17.0.2)
|
|
133
133
|
lint_roller (~> 1.1.0)
|
|
134
134
|
parallel (>= 1.10)
|
|
@@ -146,7 +146,7 @@ GEM
|
|
|
146
146
|
rubocop (~> 1.81)
|
|
147
147
|
ruby-progressbar (1.13.0)
|
|
148
148
|
securerandom (0.4.1)
|
|
149
|
-
simplecov (1.
|
|
149
|
+
simplecov (1.1.1)
|
|
150
150
|
simplecov-cobertura (4.0.0)
|
|
151
151
|
rexml
|
|
152
152
|
simplecov (~> 1.0)
|
|
@@ -167,7 +167,7 @@ GEM
|
|
|
167
167
|
unicode-emoji (~> 4.1)
|
|
168
168
|
unicode-emoji (4.2.0)
|
|
169
169
|
uri (1.1.1)
|
|
170
|
-
webmock (3.26.
|
|
170
|
+
webmock (3.26.4)
|
|
171
171
|
addressable (>= 2.8.0)
|
|
172
172
|
crack (>= 0.3.2)
|
|
173
173
|
hashdiff (>= 0.4.0, < 2.0.0)
|
data/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Safire is a Ruby gem for healthcare client applications that implements [SMART A
|
|
|
19
19
|
- Confidential Symmetric Client (`client_secret` + HTTP Basic Auth)
|
|
20
20
|
- Confidential Asymmetric Client (`private_key_jwt` with RS384/ES384)
|
|
21
21
|
- POST-Based Authorization
|
|
22
|
-
- Backend Services (`client_credentials` grant, JWT assertion, no user interaction or PKCE;
|
|
22
|
+
- Backend Services (`client_credentials` grant, JWT assertion, no user interaction or PKCE; client-selected scopes)
|
|
23
23
|
|
|
24
24
|
### UDAP Security (STU2)
|
|
25
25
|
|
|
@@ -77,6 +77,13 @@ cancellation = udap_client.cancel_registration(
|
|
|
77
77
|
)
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
UDAP registration uses the trusted fields needed for that workflow rather than
|
|
81
|
+
turning the full `UdapMetadata#valid?` conformance diagnostic into an automatic
|
|
82
|
+
gate. In v0.4.1, an unadvertised requested wildcard produces a warning and is
|
|
83
|
+
still submitted; v0.5.0 will require exact wildcard advertisement for
|
|
84
|
+
registration and modification while keeping wildcard scope drift warning-only
|
|
85
|
+
during cancellation.
|
|
86
|
+
|
|
80
87
|
UDAP JWT client authentication and Tiered OAuth are planned. See [ROADMAP.md](https://github.com/vanessuniq/safire/blob/main/ROADMAP.md) for details.
|
|
81
88
|
|
|
82
89
|
---
|
|
@@ -185,6 +192,12 @@ token_data = client.request_backend_token(
|
|
|
185
192
|
client.token_response_valid?(token_data, flow: :backend_services)
|
|
186
193
|
```
|
|
187
194
|
|
|
195
|
+
Configure scopes on the client or pass `scopes:` to each Backend Services
|
|
196
|
+
request. The legacy `system/*.rs` fallback is deprecated in v0.4.x and will be
|
|
197
|
+
removed in v0.6.0. Backend Services normally uses `system/` scopes. Safire
|
|
198
|
+
preserves explicit non-system scopes because SMART permits them when context is
|
|
199
|
+
established out of band.
|
|
200
|
+
|
|
188
201
|
---
|
|
189
202
|
|
|
190
203
|
## Configuration
|
data/ROADMAP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Safire Roadmap
|
|
2
2
|
|
|
3
|
-
## Latest Published Release — v0.4.
|
|
3
|
+
## Latest Published Release — v0.4.1
|
|
4
4
|
|
|
5
5
|
Safire is in early development (pre-release). The API is functional but not yet stable — breaking changes may occur before v1.0.0. Published to [RubyGems](https://rubygems.org/gems/safire).
|
|
6
6
|
|
|
@@ -22,7 +22,7 @@ Feedback, bug reports, and pull requests are welcome via the [issue tracker](htt
|
|
|
22
22
|
- **POST-Based Authorization** — form-encoded authorization requests
|
|
23
23
|
- **JWT Assertion Builder** — signed JWT assertions with configurable `kid` and expiry
|
|
24
24
|
- **PKCE** — automatic code verifier and challenge generation
|
|
25
|
-
- **Backend Services** — `client_credentials` grant for system-to-system flows; JWT assertion (RS384/ES384); no user interaction, redirect, or PKCE required;
|
|
25
|
+
- **Backend Services** — `client_credentials` grant for system-to-system flows; JWT assertion (RS384/ES384); no user interaction, redirect, or PKCE required; client-selected scopes with a deprecated v0.4.x compatibility fallback
|
|
26
26
|
- **Dynamic Client Registration** — runtime client registration per [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591); endpoint discovered from SMART metadata or supplied explicitly; supports initial access token
|
|
27
27
|
|
|
28
28
|
### UDAP Security (STU2 / v2.0.0)
|
|
@@ -53,9 +53,30 @@ Feedback, bug reports, and pull requests are welcome via the [issue tracker](htt
|
|
|
53
53
|
|
|
54
54
|
## Planned Features
|
|
55
55
|
|
|
56
|
-
### UDAP
|
|
56
|
+
### v0.5.0 — UDAP JWT Client Authentication and B2B Authorization
|
|
57
|
+
|
|
58
|
+
- **UDAP Registration Wildcard Enforcement** — require exact
|
|
59
|
+
`scopes_supported` advertisement for requested wildcard scopes during new
|
|
60
|
+
registration and modification after the v0.4.1 warning period; cancellation
|
|
61
|
+
remains warning-only so metadata drift cannot strand an existing registration
|
|
62
|
+
- **UDAP Authentication Tokens** — certificate-backed JWT client authentication
|
|
63
|
+
for token endpoint requests, using authoritative signed discovery metadata
|
|
64
|
+
- **B2B Client Credentials** — headless system-to-system authorization with the
|
|
65
|
+
STU2 `hl7-b2b` authorization extension and discovery-constrained scopes,
|
|
66
|
+
signing algorithms, and extension support
|
|
67
|
+
|
|
68
|
+
### v0.6.0 — UDAP Authorization Code Flows
|
|
69
|
+
|
|
70
|
+
- **Explicit SMART Backend Scopes** — remove the deprecated `system/*.rs`
|
|
71
|
+
fallback; Backend Services requests without configured or per-call scopes
|
|
72
|
+
raise `ConfigurationError`
|
|
73
|
+
- **Consumer-Facing Authorization** — UDAP authorization code flow with state,
|
|
74
|
+
PKCE, Authentication Tokens, code exchange, and refresh support
|
|
75
|
+
- **Interactive B2B Authorization** — authorization code flow for B2B clients,
|
|
76
|
+
including local-user authorization and UDAP client authentication
|
|
77
|
+
|
|
78
|
+
### v0.7.0 — UDAP Tiered OAuth
|
|
57
79
|
|
|
58
|
-
- **UDAP JWT Client Auth** — B2B and consumer-facing authorization flows
|
|
59
80
|
- **Tiered OAuth** — identity chaining for multi-system access
|
|
60
81
|
|
|
61
82
|
### Quality and Compliance
|
|
@@ -99,5 +120,5 @@ Feedback, bug reports, and pull requests are welcome via the [issue tracker](htt
|
|
|
99
120
|
| ActiveSupport | ≥ 7.1, < 9 |
|
|
100
121
|
| Rails (optional) | ≥ 7.1 |
|
|
101
122
|
| SMART App Launch | 2.2.0 (STU2) |
|
|
102
|
-
| UDAP Security | 2.0.0 (STU2 discovery and Dynamic Client Registration lifecycle implemented;
|
|
123
|
+
| UDAP Security | 2.0.0 (STU2 discovery and Dynamic Client Registration lifecycle implemented; JWT client authentication and authorization flows planned) |
|
|
103
124
|
| FHIR | R4, R4B |
|
|
@@ -125,6 +125,15 @@ end
|
|
|
125
125
|
|
|
126
126
|
A 204 response means the server has no UDAP workflows for that community. `Protocols::Udap` raises `DiscoveryError` before the body is parsed, with a descriptive message that identifies the community when one was requested.
|
|
127
127
|
|
|
128
|
+
SMART and UDAP discovery both require a JSON object but do not depend on the
|
|
129
|
+
HTTP adapter having already decoded it. A shared protocol-neutral parser accepts
|
|
130
|
+
an already parsed Hash or a raw JSON-object string, then deeply normalizes keys
|
|
131
|
+
without mutating adapter-owned data. This allows Safire to interoperate when a
|
|
132
|
+
server sends valid JSON with an incorrect or missing content type; the response
|
|
133
|
+
is still non-conformant at the HTTP layer. Malformed JSON, non-object JSON, and
|
|
134
|
+
ambiguous or non-JSON-compatible adapter Hashes raise the protocol's existing
|
|
135
|
+
`DiscoveryError`.
|
|
136
|
+
|
|
128
137
|
---
|
|
129
138
|
|
|
130
139
|
## Consequences
|
|
@@ -136,6 +145,8 @@ A 204 response means the server has no UDAP workflows for that community. `Proto
|
|
|
136
145
|
- Callers control when discovery happens — supports application-level caching patterns (see [Advanced Examples]({{ site.baseurl }}/advanced/#metadata-caching))
|
|
137
146
|
- `client_type=` mutation preserves cached SMART metadata — no re-discovery
|
|
138
147
|
- UDAP community-and-trust-policy cache allows a single client instance to serve multiple communities without serving stale signed metadata
|
|
148
|
+
- Discovery parsing is independent of response content-type middleware while
|
|
149
|
+
still failing closed on unusable object data
|
|
139
150
|
|
|
140
151
|
**Trade-offs:**
|
|
141
152
|
- Discovery errors surface at first use (e.g. `authorization_url`), not at construction — callers must handle `Errors::DiscoveryError` in their flow logic rather than at the `new` call site
|
|
@@ -30,7 +30,7 @@ security boundary and can hide production misconfiguration.
|
|
|
30
30
|
For client configuration and HTTP redirects, HTTPS is enforced at **two layers**.
|
|
31
31
|
Both layers use the same explicit local-development opt-in:
|
|
32
32
|
|
|
33
|
-
**Layer 1 — `ClientConfig` URI validation:** all URI attributes (`base_url`, `redirect_uri`, `issuer`, `authorization_endpoint`, `token_endpoint`, `jwks_uri`) must use `https://`, except when `allow_insecure_localhost: true` is configured and the host is `localhost` or `127.0.0.1`.
|
|
33
|
+
**Layer 1 — `ClientConfig` URI validation:** all URI attributes (`base_url`, `redirect_uri`, `issuer`, `authorization_endpoint`, `token_endpoint`, `jwks_uri`) must use `https://`, except when `allow_insecure_localhost: true` is configured and the host is `localhost` or `127.0.0.1`. OAuth redirect, authorization, and token endpoint URIs must not contain a fragment component, as required by RFC 6749 Sections 3.1 and 3.2.
|
|
34
34
|
|
|
35
35
|
**Layer 2 — `HttpsOnlyRedirects` Faraday middleware:** intercepts every 3xx response before `faraday-follow_redirects` follows it. If the redirect target is not HTTPS, and the local-development exception is not enabled for a loopback host, a `Safire::Errors::NetworkError` is raised immediately rather than following the redirect.
|
|
36
36
|
|
|
@@ -57,9 +57,9 @@ runtime concern.
|
|
|
57
57
|
UDAP registration metadata applies the same secure default to its registration
|
|
58
58
|
URI fields. The UDAP Security STU2 registration profile requires
|
|
59
59
|
`redirect_uris` and `logo_uri` to use HTTPS.
|
|
60
|
-
`URIValidation#
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
`URIValidation#classify_uri` provides the shared HTTPS and fragment policy.
|
|
61
|
+
UDAP redirect URIs reject fragment components; the rule is not applied to the
|
|
62
|
+
presentation-oriented `logo_uri` field.
|
|
63
63
|
|
|
64
64
|
`UdapRegistrationMetadata` uses the same explicit option name for non-TLS local
|
|
65
65
|
redirect and logo URIs. It accepts only HTTP on `localhost` or `127.0.0.1`,
|
|
@@ -15,7 +15,10 @@ nav_order: 8
|
|
|
15
15
|
|
|
16
16
|
Safire performs two different kinds of checks:
|
|
17
17
|
|
|
18
|
-
1. **Configuration checks** — validating that the caller has provided
|
|
18
|
+
1. **Configuration checks** — validating that the caller has provided usable
|
|
19
|
+
configuration (required attributes present, URIs well-formed and HTTPS).
|
|
20
|
+
General checks run at construction time; flow-specific requirements run at
|
|
21
|
+
the operation boundary. They represent caller-correctable errors if they fail.
|
|
19
22
|
|
|
20
23
|
2. **Compliance checks** — validating that a remote server's response conforms to the SMART App Launch 2.2.0 specification. These run at runtime and represent server behaviour, not caller behaviour.
|
|
21
24
|
|
|
@@ -31,6 +34,12 @@ Option B lets the caller decide what to do: they can check the return value, obs
|
|
|
31
34
|
|
|
32
35
|
There is also a clear boundary: **the caller controls the config** (configuration errors should raise — the caller can fix them); **the server controls the response** (compliance violations should warn — the caller cannot fix a remote server).
|
|
33
36
|
|
|
37
|
+
This caller/server distinction is a useful default, but it is not sufficient
|
|
38
|
+
for security-sensitive runtime decisions. ADR-015 refines the boundary: remote
|
|
39
|
+
data raises when its defect makes the requested client operation unsafe,
|
|
40
|
+
impossible, or unconfirmed. Complete structural conformance remains an explicit
|
|
41
|
+
warn-and-return-false diagnostic when the operation does not depend on it.
|
|
42
|
+
|
|
34
43
|
---
|
|
35
44
|
|
|
36
45
|
## Decision
|
|
@@ -52,12 +61,16 @@ end
|
|
|
52
61
|
```
|
|
53
62
|
|
|
54
63
|
These methods:
|
|
64
|
+
|
|
55
65
|
- Never raise an exception
|
|
56
66
|
- Log one warning per violation (not a single combined message) so each issue is individually observable
|
|
57
|
-
- Return `true` only when fully compliant
|
|
67
|
+
- Return `true` only when fully compliant and `false` when any violation is found
|
|
58
68
|
- Are **user-callable** — they are not invoked automatically by `authorization_url` or `server_metadata`; callers opt in to compliance checking
|
|
59
69
|
|
|
60
|
-
Configuration validation
|
|
70
|
+
Configuration validation in `ClientConfig` and protocol workflow-readiness
|
|
71
|
+
checks raise typed Safire errors. These are caller-correctable failures or
|
|
72
|
+
conditions that make the requested operation unsafe, impossible, or
|
|
73
|
+
unconfirmed; ADR-015 owns the detailed boundary.
|
|
61
74
|
|
|
62
75
|
---
|
|
63
76
|
|
|
@@ -71,5 +84,5 @@ Configuration validation (`ClientConfig#validate!`, `Smart#validate!`) raises `C
|
|
|
71
84
|
|
|
72
85
|
**Trade-offs:**
|
|
73
86
|
- Callers who do not call `token_response_valid?` get no compliance signal at all — non-compliant responses are silently accepted; this is intentional (opt-in, not opt-out)
|
|
74
|
-
- The distinction between "warn + return false" and "raise" must be maintained consistently
|
|
87
|
+
- The distinction between "warn + return false" and "raise" must be maintained consistently. New validation methods follow ADR-015: explicit conformance diagnostics warn, while client obligations and security or operation-readiness failures raise.
|
|
75
88
|
- `token_response_valid?` accepts a `flow:` keyword argument (`:app_launch` default, `:backend_services`) that adjusts which fields are required and what the warning messages say. For example, `token_type` must be `"Bearer"` (App Launch spec) or `"bearer"` (Backend Services spec), and `expires_in` is RECOMMENDED for App Launch but REQUIRED for Backend Services. Callers opt in to the stricter backend-services validation by passing `flow: :backend_services`
|
|
@@ -98,7 +98,9 @@ The module accepts already-received response bodies. It does not send HTTP
|
|
|
98
98
|
requests, select endpoints, log response values, or apply protocol-specific
|
|
99
99
|
policy.
|
|
100
100
|
|
|
101
|
-
Registration success responses
|
|
101
|
+
Registration success responses delegate JSON-object parsing to the private,
|
|
102
|
+
protocol-neutral `JSONResponseParsing` collaborator. Raw JSON strings and
|
|
103
|
+
already parsed Hashes are normalized to new, deeply string-keyed hashes and must
|
|
102
104
|
contain a non-blank string `client_id`, as required by RFC 7591. Missing
|
|
103
105
|
`client_id` responses continue to report only the received field names. A
|
|
104
106
|
present but blank or non-string `client_id` raises `RegistrationError` with a
|
|
@@ -106,9 +108,14 @@ structural error description. This intentionally hardens SMART registration:
|
|
|
106
108
|
valid RFC 7591 responses are unchanged, while malformed identifiers that were
|
|
107
109
|
previously accepted through `present?` now fail closed.
|
|
108
110
|
|
|
109
|
-
Malformed or non-
|
|
110
|
-
status only. Parsed JSON objects preserve
|
|
111
|
-
UDAP-specific values such as
|
|
111
|
+
Malformed, non-object, ambiguous, or non-JSON-compatible OAuth error bodies
|
|
112
|
+
produce an error containing the HTTP status only. Parsed JSON objects preserve
|
|
113
|
+
their protocol error codes, including UDAP-specific values such as
|
|
114
|
+
`invalid_software_statement`. Symbol/string key collisions and the other Ruby
|
|
115
|
+
structure guards apply only to adapter-supplied or directly passed Hashes;
|
|
116
|
+
`JSON.parse` and Faraday's JSON middleware produce string-keyed Hashes. The
|
|
117
|
+
guards prevent local normalization from silently overwriting ambiguous values,
|
|
118
|
+
not collisions in JSON received on the wire.
|
|
112
119
|
|
|
113
120
|
### Amendment consequences
|
|
114
121
|
|
|
@@ -118,6 +125,8 @@ UDAP-specific values such as `invalid_software_statement`.
|
|
|
118
125
|
construction or endpoint policy
|
|
119
126
|
- response key normalization makes direct and middleware-parsed hashes behave
|
|
120
127
|
consistently
|
|
128
|
+
- strict adapter-Hash validation makes response translation total for cyclic or
|
|
129
|
+
non-JSON-compatible Ruby values
|
|
121
130
|
- malformed successful registrations fail before an invalid identifier reaches
|
|
122
131
|
application state
|
|
123
132
|
|
|
@@ -66,10 +66,11 @@ semantics.
|
|
|
66
66
|
algorithm (`alg`), required claim presence, and signature are not validated here — these are
|
|
67
67
|
deferred to the cryptographic validator
|
|
68
68
|
- endpoint URL fields (`token_endpoint`, `registration_endpoint`, conditionally
|
|
69
|
-
`authorization_endpoint`) must be absolute HTTPS URLs; plain HTTP
|
|
70
|
-
`allow_insecure_localhost: true` is configured and the host is `localhost`
|
|
71
|
-
to support development without TLS — any other scheme on those hosts (e.g.
|
|
72
|
-
is rejected; this exception does not apply in production
|
|
69
|
+
`authorization_endpoint`) must be absolute HTTPS URLs without fragment components; plain HTTP
|
|
70
|
+
is accepted only when `allow_insecure_localhost: true` is configured and the host is `localhost`
|
|
71
|
+
or `127.0.0.1` to support development without TLS — any other scheme on those hosts (e.g.
|
|
72
|
+
`ftp://localhost`) is rejected; this exception does not apply in production and does not relax
|
|
73
|
+
the fragment prohibition
|
|
73
74
|
- `authorization_endpoint` is conditionally required when `grant_types_supported` includes
|
|
74
75
|
`"authorization_code"`
|
|
75
76
|
- `"udap_authz"` is conditionally required in `udap_profiles_supported` when `grant_types_supported`
|
|
@@ -108,6 +109,7 @@ semantics.
|
|
|
108
109
|
|
|
109
110
|
**Trade-offs:**
|
|
110
111
|
|
|
111
|
-
- A
|
|
112
|
-
|
|
113
|
-
`signed_metadata`
|
|
112
|
+
- A directly constructed `UdapMetadata` object is not automatically
|
|
113
|
+
cryptographically validated. `Safire::Client#server_metadata` validates
|
|
114
|
+
`signed_metadata` before returning the entity; callers that construct an
|
|
115
|
+
entity themselves must establish that trust separately.
|
|
@@ -41,6 +41,11 @@ construction. Invalid input raises `Safire::Errors::ValidationError` with the
|
|
|
41
41
|
failing attribute and a value-free reason. The class does not inherit from
|
|
42
42
|
`Entity` and does not use the discovery layer's warn-and-return-false contract.
|
|
43
43
|
|
|
44
|
+
Registration orchestration constructs this value object once, before discovery
|
|
45
|
+
or other network activity. Scope diagnostics and software-statement signing
|
|
46
|
+
consume the same immutable instance; Safire does not independently normalize
|
|
47
|
+
the caller's raw Hash at multiple stages.
|
|
48
|
+
|
|
44
49
|
Top-level string and symbol keys are normalized to strings. Supplying both forms
|
|
45
50
|
of one key is rejected rather than allowing insertion order to decide which
|
|
46
51
|
value is signed. The value object and its canonical internal hash are frozen,
|
|
@@ -55,7 +60,20 @@ Registration accepts exactly one primary grant:
|
|
|
55
60
|
|
|
56
61
|
Unknown and duplicate grant values are rejected. `redirect_uris`, `logo_uri`,
|
|
57
62
|
and generated `response_types: ["code"]` apply only to authorization-code
|
|
58
|
-
registration. Redirect and logo URIs require absolute HTTPS by default
|
|
63
|
+
registration. Redirect and logo URIs require absolute HTTPS by default, and
|
|
64
|
+
redirect URIs must not contain fragment components.
|
|
65
|
+
|
|
66
|
+
The STU2 logo requirement has two different validation boundaries. Safire can
|
|
67
|
+
prove locally that `logo_uri` is an absolute HTTPS URI, so malformed, relative,
|
|
68
|
+
and insecure public values fail before signing. Safire cannot prove that the
|
|
69
|
+
remote representation is PNG, JPG/JPEG, or GIF from a URI path alone. Familiar
|
|
70
|
+
image suffixes remain a quiet heuristic; other usable URI paths produce a
|
|
71
|
+
value-free warning and remain the caller's responsibility.
|
|
72
|
+
|
|
73
|
+
Safire does not dereference `logo_uri` during validation. Fetching an untrusted
|
|
74
|
+
caller-supplied URI would introduce SSRF, latency, redirect, and remote
|
|
75
|
+
availability concerns into construction of an otherwise local value object,
|
|
76
|
+
without reliably proving what the authorization server will later retrieve.
|
|
59
77
|
|
|
60
78
|
For local development without TLS, callers may explicitly pass
|
|
61
79
|
`allow_insecure_localhost: true`. This permits HTTP only on `localhost` and
|
|
@@ -94,10 +112,13 @@ regular expression.
|
|
|
94
112
|
## Consequences
|
|
95
113
|
|
|
96
114
|
- Signing code can consume one canonical, already validated metadata hash.
|
|
115
|
+
- Invalid caller metadata fails before discovery, signing, or POSTing.
|
|
97
116
|
- Caller mutations cannot change metadata between validation and signing.
|
|
98
117
|
- Protocol-owned claims cannot be shadowed through caller input.
|
|
99
118
|
- Plain HTTP remains impossible for remote hosts; local HTTP requires a literal
|
|
100
119
|
boolean opt-in and produces a warning.
|
|
120
|
+
- Logo media type remains a caller obligation when it cannot be established
|
|
121
|
+
from the URI path; Safire reports that uncertainty without fetching the URI.
|
|
101
122
|
- Extension metadata remains forward-compatible without permitting arbitrary
|
|
102
123
|
Ruby objects into a JWT payload.
|
|
103
124
|
- Metadata validation remains independent of software-statement signing and the
|
|
@@ -81,10 +81,14 @@ key, and advertised by the server. Without an explicit algorithm, Safire chooses
|
|
|
81
81
|
the first key-compatible advertised algorithm, preferring `RS256` over `RS384`
|
|
82
82
|
for RSA keys because `RS256` is the STU2 baseline.
|
|
83
83
|
|
|
84
|
-
Protocol orchestration
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
Protocol orchestration requires the advertised value to be a non-empty array of
|
|
85
|
+
algorithm names. A malformed or empty value makes safe algorithm selection
|
|
86
|
+
impossible and raises `DiscoveryError`. Because STU2 requires every FHIR server
|
|
87
|
+
to support `RS256`, Safire warns when it is absent. The warning diagnoses the
|
|
88
|
+
server baseline without blocking an otherwise interoperable request: the
|
|
89
|
+
builder may still select another advertised, Safire-supported,
|
|
90
|
+
key-compatible algorithm. A valid advertisement with no usable intersection
|
|
91
|
+
raises `ConfigurationError`.
|
|
88
92
|
|
|
89
93
|
### Validate local signing identity, not server trust
|
|
90
94
|
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: default
|
|
3
|
+
title: "ADR-015: Client role and runtime readiness"
|
|
4
|
+
parent: Architecture Decision Records
|
|
5
|
+
nav_order: 15
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# ADR-015: Client role and runtime readiness
|
|
9
|
+
|
|
10
|
+
**Status:** Accepted
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Context
|
|
15
|
+
|
|
16
|
+
Safire consumes metadata and protocol responses from remote SMART App Launch
|
|
17
|
+
and UDAP servers. Some defects make a client request unsafe or impossible;
|
|
18
|
+
others are server conformance defects that an interoperable client can report
|
|
19
|
+
without blocking an otherwise valid operation.
|
|
20
|
+
|
|
21
|
+
A blanket rule such as "caller input raises, server input warns" is not
|
|
22
|
+
sufficient at a protocol trust boundary. A server controls discovery and token
|
|
23
|
+
responses, but Safire must still reject an untrusted value when it is needed to
|
|
24
|
+
select a secure endpoint, validate a signed assertion, protect credentials, or
|
|
25
|
+
establish that a lifecycle operation completed. Conversely, complete server
|
|
26
|
+
certification is outside a client library's runtime role.
|
|
27
|
+
|
|
28
|
+
Safire therefore needs a consistent way to distinguish operational readiness
|
|
29
|
+
from optional conformance diagnostics. Comprehensive server certification
|
|
30
|
+
belongs in a conformance harness such as Inferno.
|
|
31
|
+
|
|
32
|
+
## Decision
|
|
33
|
+
|
|
34
|
+
Safire classifies protocol checks by their effect on the requested client
|
|
35
|
+
operation:
|
|
36
|
+
|
|
37
|
+
1. **Runtime hard failure:** reject a condition that would violate a client
|
|
38
|
+
obligation, make the operation unsafe or impossible, expose credentials, or
|
|
39
|
+
leave a security-sensitive result unconfirmed. This includes malformed wire
|
|
40
|
+
shapes required by the operation and failed UDAP signed-metadata trust
|
|
41
|
+
validation.
|
|
42
|
+
2. **Warning or negotiation:** report a server defect or uncertain capability
|
|
43
|
+
when Safire can still send a safe, conformant request and the server remains
|
|
44
|
+
authoritative for acceptance.
|
|
45
|
+
3. **Explicit diagnostic:** methods such as `SmartMetadata#valid?`,
|
|
46
|
+
`UdapMetadata#valid?`, and `Smart#token_response_valid?` warn and return a
|
|
47
|
+
Boolean when called. Discovery does not automatically promote every failed
|
|
48
|
+
diagnostic into a workflow failure.
|
|
49
|
+
4. **Server-owned decision:** authorization grants, requested scope approval,
|
|
50
|
+
client registration policy, certifications, and issued credentials are
|
|
51
|
+
decided by the authorization server. Safire validates its request and the
|
|
52
|
+
response shape it must consume, but does not predict the policy result.
|
|
53
|
+
|
|
54
|
+
This decision refines ADR-008. Server-controlled data is warning-only when the
|
|
55
|
+
defect does not cross a security or operation-readiness boundary; its remote
|
|
56
|
+
origin alone does not determine the error behavior.
|
|
57
|
+
|
|
58
|
+
Protocol response bodies that must be JSON objects use the private,
|
|
59
|
+
protocol-neutral `JSONResponseParsing` collaborator. It accepts an already
|
|
60
|
+
parsed `Hash` or a raw JSON string, produces a new deeply string-keyed Hash, and
|
|
61
|
+
rejects malformed or non-object JSON. Adapter-supplied Hashes also fail closed
|
|
62
|
+
when they contain recursive structures, unsupported JSON key or value types,
|
|
63
|
+
non-finite numbers, invalid string encodings, excessive nesting, or keys that
|
|
64
|
+
collide after symbol-to-string normalization. The collaborator returns a parse
|
|
65
|
+
failure and leaves each consumer to choose its protocol-specific error class.
|
|
66
|
+
|
|
67
|
+
Parsing a valid raw JSON object allows interoperability when an HTTP adapter did
|
|
68
|
+
not decode the body because the server sent an incorrect or missing content
|
|
69
|
+
type. Safire's tolerance does not make that server response conformant.
|
|
70
|
+
|
|
71
|
+
Metadata capability helpers type-check list-shaped advertisements before using
|
|
72
|
+
membership semantics. A malformed list cannot assert a SMART capability or
|
|
73
|
+
raise an unexpected type error. The complete structural assessment remains the
|
|
74
|
+
caller's explicit `SmartMetadata#valid?` diagnostic rather than an automatic
|
|
75
|
+
discovery failure.
|
|
76
|
+
|
|
77
|
+
SMART discovery preserves metadata for inspection, but an operation validates
|
|
78
|
+
its discovered authorization or token endpoint against Safire's HTTPS policy
|
|
79
|
+
and rejects fragment components before use. This focused runtime stop protects
|
|
80
|
+
authorization data and client credentials while enforcing the OAuth endpoint
|
|
81
|
+
shape the operation consumes, without promoting unrelated metadata defects into
|
|
82
|
+
discovery failures.
|
|
83
|
+
|
|
84
|
+
UDAP DCR uses focused readiness rather than calling `UdapMetadata#valid?` as an
|
|
85
|
+
operational gate. Safire requires trusted discovery, an advertised `udap_dcr`
|
|
86
|
+
profile, a usable authoritative registration endpoint, usable registration
|
|
87
|
+
algorithm metadata, and type-safe certification requirements. Other structural
|
|
88
|
+
conformance checks remain available through the explicit `valid?` diagnostic.
|
|
89
|
+
|
|
90
|
+
In v0.4.1, requested UDAP registration scopes are diagnostic only. Requested
|
|
91
|
+
tokens containing a literal `*` are classified first and compared by exact
|
|
92
|
+
membership in `scopes_supported`; narrower recognized SMART FHIR scopes may be
|
|
93
|
+
covered by broader advertised scopes. Unrecognized syntax uses exact membership.
|
|
94
|
+
Missing, malformed, or insufficient advertisements produce separate aggregated
|
|
95
|
+
warnings while the server remains authoritative for negotiation. Registration
|
|
96
|
+
and modification move to exact wildcard enforcement in v0.5.0; cancellation
|
|
97
|
+
remains warning-only so an existing registration can still be removed.
|
|
98
|
+
|
|
99
|
+
For warning suppression only, compatible advertised FHIR permission fragments
|
|
100
|
+
may collectively cover a requested permission set. STU2 does not guarantee that
|
|
101
|
+
the server accepts the combined token, so this permissive inference must not be
|
|
102
|
+
reused as a hard-failure decision without a separate normative rule.
|
|
103
|
+
|
|
104
|
+
SMART Backend Services scope intent remains client-owned. In v0.4.x, Safire
|
|
105
|
+
preserves the historical `system/*.rs` fallback only for compatibility and
|
|
106
|
+
logs a deprecation warning whenever it is used. Callers should configure or
|
|
107
|
+
pass scopes explicitly; absent scopes become a runtime `ConfigurationError` in
|
|
108
|
+
v0.6.0. SMART discovery `scopes_supported` is non-exhaustive and is not used as
|
|
109
|
+
an exact allow-list for token requests.
|
|
110
|
+
|
|
111
|
+
`system/` scopes are the normal Backend Services context. The
|
|
112
|
+
[SMART STU2.2 Backend Services scope requirements](https://hl7.org/fhir/smart-app-launch/STU2.2/backend-services.html#scopes)
|
|
113
|
+
also state that `user/` and `patient/` scopes are not prohibited when context
|
|
114
|
+
is established through out-of-band coordination. Safire therefore preserves
|
|
115
|
+
and submits explicit non-system scopes without warning or rejection. The
|
|
116
|
+
library cannot determine whether the caller completed that external
|
|
117
|
+
coordination, and must not make a permitted healthy path noisy based on missing
|
|
118
|
+
local evidence.
|
|
119
|
+
|
|
120
|
+
Scope diagnostics log only the requested scope category and count, never the
|
|
121
|
+
raw token values. SMART v2 scopes may carry FHIR search constraints, and opaque
|
|
122
|
+
custom scopes may encode equally sensitive application context in forms Safire
|
|
123
|
+
cannot safely redact. Count-and-category logging preserves the operational and
|
|
124
|
+
migration signal without copying caller-controlled authorization details into
|
|
125
|
+
application logs.
|
|
126
|
+
|
|
127
|
+
The classifications below were checked against the published
|
|
128
|
+
[SMART App Launch STU2.2 conformance and discovery rules](https://hl7.org/fhir/smart-app-launch/STU2.2/conformance.html),
|
|
129
|
+
[SMART App Launch workflow](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html),
|
|
130
|
+
[SMART Backend Services profile](https://hl7.org/fhir/smart-app-launch/STU2.2/backend-services.html),
|
|
131
|
+
[RFC 7591 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591),
|
|
132
|
+
[UDAP Security STU2 discovery](https://hl7.org/fhir/us/udap-security/STU2/discovery.html),
|
|
133
|
+
and [UDAP Security STU2 registration](https://hl7.org/fhir/us/udap-security/STU2/registration.html).
|
|
134
|
+
|
|
135
|
+
## Operation Matrix
|
|
136
|
+
|
|
137
|
+
This matrix covers the public protocol operations currently implemented by the
|
|
138
|
+
`Safire::Client` facade.
|
|
139
|
+
|
|
140
|
+
| Operation | Client obligation | Required capability or input | Runtime hard failure | Runtime warning or negotiation | Explicit diagnostic | Server-owned decision |
|
|
141
|
+
|-----------|-------------------|------------------------------|----------------------|--------------------------------|---------------------|-----------------------|
|
|
142
|
+
| SMART `server_metadata` | Request and consume the well-known JSON object over a protected connection | FHIR base URL | Transport or HTTP failure; unusable JSON-object body | None | Caller may invoke `SmartMetadata#valid?` | Advertised endpoints and capabilities |
|
|
143
|
+
| SMART `authorization_url` | Supply client ID, redirect URI, scopes, and launch context as applicable; Safire adds state and S256 PKCE | Usable authorization endpoint; caller selects GET or POST | Invalid request method; missing client ID, scopes, or redirect URI; missing, malformed, or insecure endpoint | None | Metadata capability helpers and `SmartMetadata#valid?` remain caller-invoked | User authorization, launch context, accepted request method, and granted scopes |
|
|
144
|
+
| SMART `request_access_token` | Supply the authorization code, matching PKCE verifier, redirect URI, and client credentials required by the configured client type | Usable token endpoint | Missing credentials; invalid JWT assertion configuration; missing, malformed, or insecure endpoint; transport, OAuth, unusable response, or missing access-token failure | None | Caller may invoke `token_response_valid?` | Code acceptance and issued token contents |
|
|
145
|
+
| SMART `refresh_token` | Supply the refresh token and authenticate the client; any requested scopes must not exceed the original grant | Usable token endpoint | Missing credentials; invalid JWT assertion configuration; missing, malformed, or insecure endpoint; transport, OAuth, unusable response, or missing access-token failure | None | Caller may invoke `token_response_valid?` | Refresh-token acceptance and issued scope |
|
|
146
|
+
| SMART `request_backend_token` | Supply client ID, a registered signing identity, and explicit scope intent | Usable token endpoint and pre-authorized asymmetric client registration | Missing identity or signing credentials; invalid JWT assertion configuration; missing, malformed, or insecure endpoint; transport, OAuth, unusable response, or missing access-token failure | The v0.4.x legacy scope fallback warns; explicit scopes remain subject to server negotiation | Caller may invoke `token_response_valid?(flow: :backend_services)` | Out-of-band context, client authentication, requested scope, and token issuance |
|
|
147
|
+
| SMART `register_client` | Supply RFC 7591 client metadata and any required initial access token | Explicit or discovered HTTPS registration endpoint | Missing or unsafe endpoint; transport or OAuth failure; unusable response or missing/invalid client ID | None | Discovery capability helpers and `SmartMetadata#valid?` remain caller-invoked | Registration policy, accepted metadata, and issued credentials |
|
|
148
|
+
| SMART `token_response_valid?` | Choose whether to require the optional conformance diagnostic | Token-response Hash and selected flow | None; this method is not an operational gate | Logs each diagnosed response defect and returns `false` | This method is the explicit diagnostic | Caller decides how to handle a failed diagnostic |
|
|
149
|
+
| UDAP `server_metadata` | Supply the intended community and production trust/revocation policy; validate signed metadata before any later workflow | UDAP well-known endpoint and a trusted signed-metadata chain | Transport, HTTP, or 204 outcome; unusable JSON object; failed signature, chain, revocation, issuer, time, or endpoint validation | None | Caller may invoke `UdapMetadata#valid?` or explicitly re-run `signed_metadata_valid?` | Community-specific profiles and capabilities |
|
|
150
|
+
| UDAP `register_client` | Supply conformant metadata, client URI, certifications when required, and a matching private key/certificate chain | Trusted discovery; `udap_dcr`; authoritative registration endpoint; usable algorithm and certification-requirement metadata | Unsafe or unusable readiness data; invalid caller metadata or signing identity; transport or server rejection; pending or malformed completion response | Missing RS256 advertisement and unconfirmed scope support warn in v0.4.1 | Caller may invoke `UdapMetadata#valid?`; it is not a blanket gate | Registration policy, scope and certification acceptance, effective metadata, and issued client ID |
|
|
151
|
+
| UDAP `cancel_registration` | Supply the same stable client URI and trust-community identity; preserve local state until cancellation is confirmed | Registration readiness above and an existing registration to remove | Registration hard failures above; any response that does not provide final 2xx body confirmation with the client ID and empty grants | Scope compatibility and malformed scope advertisements remain warning-only so cleanup can proceed | Caller may invoke `UdapMetadata#valid?`; it is not a blanket gate | Cancellation policy and the confirmation response |
|
|
152
|
+
|
|
153
|
+
SMART cancellation and UDAP authorization, token, refresh, backend-token, and
|
|
154
|
+
token-response operations are not implemented. They raise
|
|
155
|
+
`NotImplementedError` through the shared protocol contract rather than implying
|
|
156
|
+
runtime support.
|
|
157
|
+
|
|
158
|
+
### Rules for Future Operations
|
|
159
|
+
|
|
160
|
+
Every new public protocol operation must update the matrix and apply these
|
|
161
|
+
questions in order:
|
|
162
|
+
|
|
163
|
+
1. Which normative actor owns the requirement: client, authorization server,
|
|
164
|
+
resource server, or trust community?
|
|
165
|
+
2. Does a failure prevent Safire from constructing a conformant request,
|
|
166
|
+
selecting a trusted endpoint, protecting credentials, or confirming the
|
|
167
|
+
operation's result? If so, fail before signing or network activity whenever
|
|
168
|
+
the condition is locally knowable.
|
|
169
|
+
3. Can Safire still send a safe, conformant request while the server remains
|
|
170
|
+
authoritative for policy or negotiation? If so, preserve the request and use
|
|
171
|
+
a warning only when it gives the caller actionable information.
|
|
172
|
+
4. Is the check broad server conformance rather than operation readiness? Keep
|
|
173
|
+
it in an explicit diagnostic and do not call it automatically.
|
|
174
|
+
5. Is untrusted response data consumed by the operation? Validate its type and
|
|
175
|
+
shape before use, and translate failure into the protocol-specific Safire
|
|
176
|
+
error rather than leaking a Ruby implementation exception.
|
|
177
|
+
6. Does an error prove rejection, or only an unavailable, malformed, pending,
|
|
178
|
+
or otherwise unconfirmed outcome? Use only the strongest description the
|
|
179
|
+
evidence supports.
|
|
180
|
+
7. Does the implementation invent client intent, retry a possibly committed
|
|
181
|
+
operation, or log caller credentials or authorization details? If so, stop
|
|
182
|
+
and require an explicit, documented policy instead.
|
|
183
|
+
|
|
184
|
+
Shared helpers may encode mechanism, such as JSON-object parsing or
|
|
185
|
+
warning-only UDAP scope coverage, but must not silently promote a diagnostic
|
|
186
|
+
inference into a hard-failure rule for another workflow.
|
|
187
|
+
|
|
188
|
+
## Consequences
|
|
189
|
+
|
|
190
|
+
**Benefits:**
|
|
191
|
+
|
|
192
|
+
- Runtime failures now have an operation or security rationale rather than
|
|
193
|
+
serving as blanket server certification.
|
|
194
|
+
- Callers retain explicit tools for enforcing stricter server conformance.
|
|
195
|
+
- Shared JSON-object parsing is total for untrusted adapter output and preserves
|
|
196
|
+
protocol-specific error ownership.
|
|
197
|
+
- Valid raw JSON objects remain usable even when response content-type handling
|
|
198
|
+
is imperfect.
|
|
199
|
+
|
|
200
|
+
**Trade-offs:**
|
|
201
|
+
|
|
202
|
+
- The same metadata defect can be diagnostic in one flow and fatal in another
|
|
203
|
+
when the latter needs the affected field to proceed safely.
|
|
204
|
+
- Tolerating a raw JSON object can hide an HTTP content-type defect unless the
|
|
205
|
+
caller separately audits server conformance.
|
|
206
|
+
- The operation matrix must be updated whenever a public protocol operation or
|
|
207
|
+
runtime gate is added or materially changed.
|