rodauth-ciba 0.1.0

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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE +21 -0
  4. data/README.md +106 -0
  5. data/docs/adr/0003-follow-rodauth-hook-conventions.md +9 -0
  6. data/docs/adr/0004-retain-atomic-issuance-and-stage-alignment.md +16 -0
  7. data/docs/alignment-closeout.md +136 -0
  8. data/docs/alignment-roadmap.md +160 -0
  9. data/docs/api.md +337 -0
  10. data/docs/authentication-claims.md +80 -0
  11. data/docs/authorization-details.md +50 -0
  12. data/docs/claims.md +46 -0
  13. data/docs/failure-contract.md +34 -0
  14. data/docs/id-token-encryption.md +288 -0
  15. data/docs/id-token-hint.md +174 -0
  16. data/docs/login-hint-token.md +35 -0
  17. data/docs/max-age.md +65 -0
  18. data/docs/node-alignment.md +336 -0
  19. data/docs/operations.md +130 -0
  20. data/docs/outbound-http.md +52 -0
  21. data/docs/pairwise.md +76 -0
  22. data/docs/ping.md +118 -0
  23. data/docs/protocol-coverage.md +114 -0
  24. data/docs/refresh-tokens.md +151 -0
  25. data/docs/release-readiness.md +75 -0
  26. data/docs/release-validation.md +498 -0
  27. data/docs/request-context.md +51 -0
  28. data/docs/requirements-audit-current.json +3221 -0
  29. data/docs/requirements-audit-current.md +424 -0
  30. data/docs/requirements-audit.json +3190 -0
  31. data/docs/requirements-audit.md +175 -0
  32. data/docs/research/access-token-revocation-reference.md +81 -0
  33. data/docs/research/alignment-partial-triage.md +50 -0
  34. data/docs/research/claims-reference-contract.md +33 -0
  35. data/docs/research/client-assertion-algorithm-contract.md +177 -0
  36. data/docs/research/completion-error-boundaries.md +56 -0
  37. data/docs/research/consent-api-evolution.md +59 -0
  38. data/docs/research/dpop-reference-contract.md +423 -0
  39. data/docs/research/id-token-hint-reference-contract.md +254 -0
  40. data/docs/research/mtls-reference-contract.md +449 -0
  41. data/docs/research/node-ciba-lifecycle.md +30 -0
  42. data/docs/research/node-oidc-provider-comparison.md +45 -0
  43. data/docs/research/pairwise-reference-contract.md +264 -0
  44. data/docs/research/ping-reference-contract.md +165 -0
  45. data/docs/research/rar-reference-contract.md +55 -0
  46. data/docs/research/refresh-reference-contract.md +85 -0
  47. data/docs/research/registration-reference-contract.md +567 -0
  48. data/docs/research/request-parameter-inventory.md +42 -0
  49. data/docs/research/required-parameter-inventory.md +39 -0
  50. data/docs/research/resource-reference-contract.md +40 -0
  51. data/docs/research/signed-request-reference-contract.md +113 -0
  52. data/docs/resources.md +55 -0
  53. data/docs/security-review.md +294 -0
  54. data/docs/signed-requests.md +162 -0
  55. data/docs/user-code.md +73 -0
  56. data/docs/validation/node-alignment-tests.txt +10 -0
  57. data/examples/client.rb +39 -0
  58. data/examples/demo_app.rb +121 -0
  59. data/examples/dpop_smoke.rb +169 -0
  60. data/examples/ecdh_id_token_smoke.rb +84 -0
  61. data/examples/edwards_id_token_smoke.rb +48 -0
  62. data/examples/edwards_smoke.rb +87 -0
  63. data/examples/edwards_tls_smoke.rb +73 -0
  64. data/examples/encrypted_id_token_smoke.rb +73 -0
  65. data/examples/encryption_tls_smoke.rb +123 -0
  66. data/examples/extensions_smoke.rb +226 -0
  67. data/examples/fixtures/edwards-requests.json +30 -0
  68. data/examples/hmac_id_token_smoke.rb +72 -0
  69. data/examples/migration.rb +18 -0
  70. data/examples/mtls_smoke.rb +395 -0
  71. data/examples/ping_smoke.rb +226 -0
  72. data/examples/registration_smoke.rb +73 -0
  73. data/examples/server.rb +14 -0
  74. data/examples/smoke.rb +46 -0
  75. data/examples/symmetric_id_token_smoke.rb +83 -0
  76. data/lib/rodauth/ciba/authorization_details.rb +60 -0
  77. data/lib/rodauth/ciba/dpop.rb +250 -0
  78. data/lib/rodauth/ciba/http.rb +69 -0
  79. data/lib/rodauth/ciba/id_token_encryption.rb +175 -0
  80. data/lib/rodauth/ciba/jwks_cache.rb +149 -0
  81. data/lib/rodauth/ciba/mtls.rb +174 -0
  82. data/lib/rodauth/ciba/pairwise.rb +105 -0
  83. data/lib/rodauth/ciba/registration.rb +489 -0
  84. data/lib/rodauth/ciba/schema.rb +182 -0
  85. data/lib/rodauth/ciba/version.rb +6 -0
  86. data/lib/rodauth/ciba.rb +4 -0
  87. data/lib/rodauth/features/ciba.rb +2185 -0
  88. metadata +231 -0
@@ -0,0 +1,177 @@
1
+ # Configured client assertion algorithm matrix
2
+
3
+ This comparison explicitly configures twelve advertised authentication algorithms:
4
+ RS256/384/512, PS256/384/512, ES256/384/512 and HS256/384/512. The first nine use
5
+ private_key_jwt; the HMAC algorithms use client_secret_jwt and a64-byte client
6
+ secret. Shared-secret authentication requires upstream's plaintext-secret mode;
7
+ this fixture is not a recommendation to weaken an existing hashed-secret store.
8
+
9
+ Both actual node9.12.2 HTTP endpoints authenticate each configured algorithm and
10
+ reject the wrong key. Token collection while pending returns authorization_pending
11
+ only after successful client authentication. The Ruby integration additionally
12
+ approves each request and obtains a signature/issuer/audience-verified ID Token.
13
+ Wrong audience or issuer and wrong keys do not create/consume the request. The
14
+ HMAC wrong key is the OP key, ensuring the client secret is the verification key.
15
+
16
+ The matrix checks that Discovery's algorithm list equals the configured list;
17
+ it does not infer algorithm support solely from a generic JWT library constant.
18
+ The shared metadata test separately checks OAuth/OIDC equality. Basic/post and
19
+ actual TLS authentication remain in their dedicated fixtures, not this JWT test.
20
+
21
+ - [Pinned reference](../validation/node-client-auth-algorithms.txt)
22
+ - [Gem local:1 test /137 assertions](../validation/client-auth-algorithms-current.txt)
23
+ - [Three-DB execution](../validation/client-auth-algorithms-matrix.txt)
24
+
25
+ This resolves the missing matrix for these configured algorithms. It does not
26
+ establish all combinations of load order, management updates or sender-bound
27
+ access tokens. CIBA signed-request Ed25519/EdDSA support does not imply Edwards
28
+ client assertion support; the latter still follows upstream JWT capabilities.
29
+ The upstream metadata also includes a legacy JWT assertion-type URN as an
30
+ advertised method when plaintext secrets are enabled. This test does not claim
31
+ that URN can independently serve as a registered CIBA authentication method;
32
+ the registration defect is now fixed as described below. The later metadata correction below removes only the unusable URN; actual
33
+ upstream authentication dispatch remains unchanged.
34
+
35
+ ## Legacy assertion-type URN is not a registered authentication method
36
+
37
+ The gem previously accepted the JWT assertion-type URN as the sole registered
38
+ CIBA authentication method. Upstream dispatch subsequently required the actual
39
+ private_key_jwt or client_secret_jwt method, leaving this registration unusable.
40
+ The red test records HTTP201 for that invalid configuration. The pinned reference
41
+ instead rejects it at HTTP registration.
42
+
43
+ CIBA eligibility now explicitly excludes this URN. Opt-in DCR and management
44
+ candidate validation reuse that boundary; the normal JWT assertion_type request
45
+ parameter remains accepted with the real registered authentication method.
46
+ At that stage shared Discovery advertisement was left unchanged. The later
47
+ correction below removes the URN from the advertised list without rewriting
48
+ non-CIBA authentication dispatch.
49
+
50
+ [Red regression](../validation/client-auth-urn-red.txt),
51
+ [reference rejection](../validation/node-client-auth-urn.txt),
52
+ [three-DB regression](../validation/client-auth-urn-matrix.txt).
53
+
54
+ ## Confirmed Edwards client-authentication gap
55
+
56
+ The reference matrix now configures fourteen algorithms, adding Ed25519 and
57
+ EdDSA. Both authenticate the backchannel and token endpoints; wrong keys return
58
+ 401 at each. This is separate from signed authentication request verification.
59
+ [Reference execution](../validation/node-client-auth-edwards.txt).
60
+
61
+ The initial explicit gem probe (now promoted into
62
+ [regression tests](../../test/ciba_edwards_auth_test.rb)) signs with
63
+ OpenSSL, registers an OKP Ed25519 client key and pins each authentication
64
+ algorithm. Both algorithms initially returned401 invalid_client at both endpoints
65
+ ([red execution](../validation/client-auth-edwards-red.txt)). Each token test has
66
+ its own pre-approved request, avoiding replay from masking authentication.
67
+ That initial failing probe was not counted among the331 passing regression
68
+ tests. The implementation increment below replaces it with passing regressions.
69
+
70
+ Upstream dispatch correctly selects private_key_jwt for non-HMAC algorithms.
71
+ The missing boundary is JWT/JWK verification: the CIBA private-key method passes
72
+ client keys to the upstream JWT backend, while the existing Ed25519 public-key
73
+ importer is used only for signed CIBA requests. Authentication algorithm metadata
74
+ also derives from upstream OP signing keys; adding an Edwards verifier alone
75
+ would leave registration/Discovery incomplete.
76
+
77
+ The next implementation must provide explicit authentication-algorithm
78
+ configuration and matching metadata, use client OKP keys (never OP fallback),
79
+ retain audience/issuer/subject/expiry/nbf checks and assertion replay protection,
80
+ and exercise wrong keys, registered algorithm mismatch, inline/remote keys and
81
+ non-CIBA behavior. Edwards ID Token signing is a separate boundary and must not
82
+ be advertised as a side effect of client authentication support.
83
+
84
+ ## Initial Edwards authentication implementation
85
+
86
+ `ciba_client_assertion_signing_algorithms` is optional (default nil retains
87
+ upstream-derived registration defaults). An explicit array configures the CIBA
88
+ client assertion algorithms independently of OP token-signing keys. For example,
89
+ `%w[RS256 Ed25519 EdDSA]` permits those algorithms for CIBA clients. OAuth and
90
+ OIDC Discovery add these capabilities to the upstream authentication list; they
91
+ do not remove upstream capabilities or alter ID Token algorithm advertisement.
92
+
93
+ Edwards verification uses the existing local RFC8410 Ed25519 importer with the
94
+ client's registered OKP key. It checks exact alg/curve, kid, use, key_ops, signature
95
+ length and signature over the original compact bytes. No global ruby-jwt
96
+ algorithm or OP signing key is installed. Unknown critical headers and duplicate
97
+ JSON fields are rejected. Shared issuer/subject/expiry/iat/audience checks and
98
+ atomic assertion replay storage remain in the existing authentication wrapper;
99
+ the Edwards decoder additionally validates nbf.
100
+
101
+ DCR accepts the algorithms only when explicitly enabled for private_key_jwt and
102
+ rejects them for client_secret_jwt. The two new tests check Discovery, registration,
103
+ both endpoints, wrong signing keys, key metadata, malformed/future nbf, expiry,
104
+ issuer/audience and replay. The existing twelve-algorithm test remains in the
105
+ regression subset. [Three-DB execution](../validation/client-auth-edwards-matrix.txt).
106
+
107
+ The follow-up below adds remote keys, ordinary-flow coexistence and installed
108
+ artifact evidence. Full authentication-workstream completion is not inferred.
109
+
110
+ ## Remote keys and installed authentication follow-up
111
+
112
+ A real HTTP JWKS fixture now exercises both Edwards algorithm names. A live cache
113
+ retains its old key, explicit cache invalidation switches to the replacement key,
114
+ and the old signature then fails. This is deterministic cache expiry, not a
115
+ wall-clock TTL measurement.503 and invalid JSON reject authentication without
116
+ creating requests or consuming the saved approval; recovery permits token
117
+ collection with a signature-verified ID Token. Each CIBA-capable client also
118
+ authenticates a normal authorization_code token exchange using its Edwards key.
119
+
120
+ [Three-DB execution](../validation/client-auth-edwards-remote-matrix.txt): five
121
+ related tests /247 assertions per DB, no failures/errors/skips. The reference
122
+ fixture independently fetches real HTTP remote JWKS for both Edwards names and
123
+ rejects wrong keys at both CIBA endpoints
124
+ ([execution](../validation/node-client-auth-edwards-remote.txt)). Its new remote
125
+ cases do not claim identical rotation/failure-cache policy.
126
+
127
+ The installed artifact smoke adds Edwards authentication at both endpoints,
128
+ assertion replay rejection, customer approval, opaque/JWT access tokens and
129
+ verification of the OP's RS256 ID Token. The sample explicitly creates the
130
+ client assertion replay table before enabling authentication.
131
+ [Installed execution](../validation/client-auth-edwards-installed.txt).
132
+ This authentication smoke uses inline keys; HTTPS Edwards client-key retrieval
133
+ in the installed package is not established by it.
134
+
135
+ The full suite on Ruby3.3 and3.4 now passes334 tests /4,316 assertions per
136
+ version, with the existing two SQLite row-lock skips
137
+ ([execution](../validation/client-auth-edwards-rubies.txt)). The installed artifact
138
+ includes this contract document and passes the package-presence check; SHA256
139
+ `d43fd74be78fc8f615a8f6125488af244dda284820ae723293696d20c6d61edf`.
140
+ That artifact predates this results paragraph; implementation is unchanged.
141
+
142
+ ## Installed HTTPS retrieval and configuration scope
143
+
144
+ The installed TLS fixture now serves Edwards client JWKS over HTTPS for both
145
+ algorithm names and both opaque/JWT configurations. Default loopback filtering
146
+ rejects retrieval before the handler; the fixture then allows exactly its owned
147
+ loopback address while preserving certificate verification. Trusted TLS permits
148
+ authentication at both endpoints and issuance of a verified ID Token. A separate
149
+ process without trust in the fixture CA rejects authentication without any key
150
+ handler access or persisted CIBA request. JWKS requests are GETs without forwarded
151
+ authentication credentials.
152
+ [Execution](../validation/client-auth-edwards-https-installed.txt), SHA256
153
+ `d99ffb61d2645e040b3a7ab1bbc0240e38c743d778a978d37aa9717020ac1a4c`.
154
+ This artifact predates this report paragraph.
155
+
156
+ The configuration-scope regression sets the CIBA list to Edwards only, then
157
+ authenticates an unrelated RSA authorization_code client. It succeeds without
158
+ CIBA request or replay-ledger writes; shared Discovery retains RS256 alongside
159
+ the explicitly added Edwards capabilities.
160
+ [Three DBs](../validation/client-auth-edwards-scope-matrix.txt): five tests /114
161
+ assertions each, no failures/errors/skips. This closes the configured-method
162
+ coverage gap. The remaining legacy-URN metadata mismatch is corrected below.
163
+
164
+ ## Shared authentication-method metadata correction
165
+
166
+ OAuth/OIDC Discovery now removes the assertion-type URN from
167
+ `token_endpoint_auth_methods_supported`, matching the reference. Both discovery
168
+ documents retain the same remaining list. The underlying upstream method list
169
+ and dispatch are untouched: no authentication mode is globally disabled, and
170
+ ordinary RSA clients still authenticate. The normal client_assertion_type
171
+ parameter remains required for JWT assertions.
172
+
173
+ [Initial mismatch](../validation/client-auth-metadata-urn-red.txt),
174
+ [reference Discovery and registration](../validation/node-client-auth-metadata-urn.txt),
175
+ [three DBs](../validation/client-auth-metadata-urn-matrix.txt): four tests /162
176
+ assertions each, including existing twelve-algorithm authentication and normal
177
+ OAuth coexistence. This resolves CIBA-007 for the configured metadata boundary.
@@ -0,0 +1,56 @@
1
+ # Poll completion errors: protocol boundary versus generic result API
2
+
3
+ ## Current alignment finding
4
+
5
+ The gem now persists a generic completion error and returns it once before
6
+ rejecting replay with `invalid_grant`. Codes are validated as bounded OAuth
7
+ error strings; descriptions are not an input to the Ruby API. Existing denials
8
+ retain NULL and continue returning `access_denied`. The negative state and
9
+ transactional denial hooks are retained, while observation distinguishes a
10
+ technical `failed` event from customer `denied`. Migration, idempotent retry,
11
+ conflicting completion, hook rollback and ping have direct tests.
12
+
13
+ The latest reference HTTP test also covers a custom `application_failure` and
14
+ an application-provided description. Both `transaction_failed` and the custom
15
+ code are returned once with HTTP 400, followed by `invalid_grant`. Neither
16
+ retains the supplied description. In 9.12.2, `backchannelResult` assigns
17
+ `error_description`, while the model persists `errorDescription`; the grant
18
+ handler reconstructs the error from the latter. The measured response bodies
19
+ contain only `error` for these two cases. Do not infer a public description
20
+ round-trip from the input object's API. [Execution](../validation/node-completion-error-descriptions.txt)
21
+ and [fixture](../../test/reference/full-op/lifecycle.mjs).
22
+
23
+ 2026-09-30. Reviewed CIBA Core and executed the complete node-oidc-provider v9.12.2 on Node24.21.0 with its built-in memory adapter.
24
+
25
+ [CIBA Core §11](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#token_error_response) defines polling errors and distinguishes pending, throttling, expiry and denial. `transaction_failed` appears in the [Push error payload, §12](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html#push_error_payload), not the poll-specific additions. Pending and slow_down describe continuing requests; expiry follows the request deadline. This does not define an application-to-OP completion API.
26
+
27
+ ## Observed behavior
28
+
29
+ The complete reference OP accepted both `TransactionFailed` and `AuthorizationPending` through `backchannelResult`. Polling returned the supplied error once, consumed the request, and returned `invalid_grant` on the next poll. This verifies the generic error propagation in `provider.js` and `grant_common.js`; it does not establish those application-supplied results as suitable terminal poll outcomes.
30
+
31
+ The earlier access_denied-only restriction has been removed. The public API
32
+ still does not duplicate node's error class hierarchy: it accepts a
33
+ `CibaSupport::ProtocolError` code. Passing a continuing-state error as a result
34
+ is a terminal application decision, not a substitute for normal pending polls.
35
+ This matches the reference's one-use completion semantics without treating an
36
+ application outage as customer refusal. The default completion HTTP status is
37
+ 400; invalid_token and insufficient_scope use401/403 as in the reference.
38
+
39
+ ## Device-dispatch exception comparison
40
+
41
+ The reference harness records a request in the application's dispatch callback and then injects an exception. HTTP initiation returns 500 without auth_req_id, but the pending request remains in the adapter and can still be polled using the internally observed identifier. This matches the gem's existing `test_device_failure_is_not_an_observer_failure_or_success_response`. No test here demonstrates actual notification delivery or durable recovery.
42
+
43
+ ## Evidence
44
+
45
+ - [Node LTS HTTP log](../validation/node-full-op-lts.txt): generic completion errors, dispatch failure and prior lifecycle scenarios.
46
+ - [Gem log](../validation/completion-boundaries.txt): Ruby4.0.6 / SQLite, 105 tests / 783 assertions, no failures/errors/skips.
47
+ - [Gem regression test](../../test/ciba_alignment_test.rb): rejects transaction_failed, authorization_pending, slow_down, expired_token, invalid_grant and server_error as completion results without mutation.
48
+ - The prior Ruby/DB matrix covered 104 tests. No runtime or schema change was made for this review; the added test has not been rerun on every matrix entry.
49
+
50
+ ## Current gem evidence
51
+
52
+ [Focused regression](../validation/completion-errors-current.txt) passes7 tests /166 assertions.
53
+ The old105-test log and unsupported-error regression above describe the previous
54
+ implementation, not the current API.
55
+
56
+ Ruby3.4 SQLite/PostgreSQL/MySQL each pass18 tests /339 assertions ([matrix](../validation/completion-errors-matrix.txt)). The [installed artifact](../validation/completion-errors-installed.txt) verifies custom failure/replay alongside existing smokes; SHA256 `0b0b395c7ff90ffe4e697afe7f5c17c18bc7d8d339e4134415d65ca384a76626`.
@@ -0,0 +1,59 @@
1
+ # Request and consent API evolution review
2
+
3
+ 2026-09-30. Reviewed the installed node-oidc-provider 9.12.2 source, rodauth-oauth 1.7.0 source and the current gem. This is an implementation review and staged design proposal, not an announcement that the additional capabilities are available.
4
+
5
+ ## Finding
6
+
7
+ Keep the public boundary `backchannel_result(request_id, saved_grant_or_id, ...)`. Reloading consent by ID, checking account/client ownership, and serializing issuance with revocation are appropriate foundations for richer consent. Do not replace that boundary with caller-supplied token claims or authorization hashes. Current snapshots are scope-only; their existence is not evidence of node's complete Grant model.
8
+
9
+ The next extension needs separate requested and approved representations. Simply copying `claims` or `resource` into upstream OAuth grant columns could bypass consent. In upstream `generate_id_token`, keys from stored ID Token claims are added to the requested scope/claim list before the account provider runs; this is not a substitute for the gem's approval filtering. Resource handling also needs per-resource authorization rather than one global scope intersection.
10
+
11
+ ## Concrete migration and API shape
12
+
13
+ Update: nonce propagation is now implemented with a nullable request column and explicit forward-migration helper. Its scope/identity separation remains as described below; other additions in this table remain proposals. See [API](../api.md) and [operations](../operations.md).
14
+
15
+ Further update: [optional explicit claims](../claims.md) now implements request/consent separation and both output targets. The API uses `claims:` and `rejected_claims:` arrays on `create_ciba_grant`; predicates remain available in request JSON for application policy. [Resources](../resources.md) also implement their separate saved permission and token audience. RAR and additional result errors remain proposals; the [RAR reference contract](rar-reference-contract.md) now supplies actual CIBA HTTP evidence and completion-policy boundaries.
16
+
17
+ These are proposed additions for individually selected increments. They must not be accepted or advertised until implemented and tested.
18
+
19
+ | Capability | Request storage | Consent storage / proposed API | Issuance and rejection boundary |
20
+ |---|---|---|---|
21
+ | Explicit OIDC claims | Validated `requested_claims` JSON, split by ID Token/UserInfo target | Optional `oidc_claims:` and explicit rejected claims on `create_ciba_grant` | Intersect requested claims with consent and current client policy before invoking upstream claim acquisition. Preserve essential/value constraints for validation; do not flatten them into unrestricted claim names. Keep protocol-required claims separate from optional disclosure. |
22
+ | Resources | Validated requested resource identifiers, preserving multiple values | `resources: {resource_uri => scopes}` on saved consent | Selected resource must be both requested and approved. Limit access-token scope/audience per resource; ID Token audience remains the client. Invalid or unauthorized target must not fall back to unrestricted issuance. |
23
+ | Nonce | Validated request nonce | No consent field: it is request correlation, not a permission | Persist through completion and include the original value in the ID Token. A poll parameter must not replace it. |
24
+ | RAR | Validated requested authorization details | Separately approved authorization details, with type-specific validation | No generic JSON merge or equality-as-authorization. Specify a type's narrowing rules and downstream enforcement before implementing it. No reviewed upstream RAR bridge currently establishes this behavior. |
25
+ | Additional completion errors | Persist explicit terminal error code independently of successful grant ID | Continue accepting a typed result error; extend only a documented allowlist | Define which errors are terminal, their polling responses and retry equivalence. Current `denied` state plus grant absence encodes only access_denied; adding Ruby subclasses alone is insufficient. Never expose exception messages as protocol descriptions. |
26
+
27
+ The resource increment now accepts `create_ciba_grant(account_id: ..., oauth_application_id: ..., scopes: "openid", resources: {"https://api.example.test" => "read"})` after its explicit migration and activation. Existing scope-only calls and `backchannel_result(id, grant)` retain their meaning; granting `openid` does not implicitly authorize a newly added resource.
28
+
29
+ Use explicit forward migrations for each selected increment, initially with nullable new columns. Existing rows mean no additional explicit authorization; do not backfill consent from request parameters. Claims implicitly permitted by existing scope semantics need a documented compatibility mapping before enabling explicit claims rejection. Check existing client/application eligibility during issuance as today.
30
+
31
+ A mixed-version deployment needs a capability gate: migrate first, deploy readers/writers, then enable the feature only once all workers understand it. Old workers must not process richer grants as scope-only grants. If rolling compatibility cannot enforce this, drain old workers before activation. Downgrade cannot silently discard richer approval restrictions; require deactivation and handling of affected pending requests. Keep the existing consent-to-issued-token link and revocation behavior through every migration.
32
+
33
+ ## Consent lifetime difference
34
+
35
+ node's default `GrantTTL` is 14 days. The follow-up implementation now applies the same default to new gem consent via `ciba_grant_lifetime`. Omitted expiry uses that configured lifetime; explicit nil or a future timestamp remains an application override. This changes the earlier default of nil for newly created rows, including convenience approval.
36
+
37
+ Existing rows retain their original expiry without migration. Access-token lifetime and request lifetime remain separate. The tests cover default/explicit/custom expiry, preservation of legacy nil rows, expiry at collection and during approval/issuance hooks; the complete node OP test now reloads saved Grant data and checks its actual exp-minus-iat value.
38
+
39
+ ## Acceptance gates
40
+
41
+ The [resource reference contract](resource-reference-contract.md) supplies real HTTP tests and identifies the need for separate OIDC/access-token scope handling; merely enabling upstream resource_indicators is insufficient for CIBA consent binding.
42
+
43
+ The [explicit-claims reference contract](claims-reference-contract.md) now provides passing complete-OP cases for requested/allowed/rejected values and ID Token/UserInfo target separation. It also identifies the upstream UserInfo filtering path that the gem implementation must cover.
44
+
45
+ - Retain tests proving that mutating a supplied grant snapshot cannot broaden consent, that account/client mismatches fail, and that revocation serializes with issuance.
46
+ - Each new field needs positive tests and negative tests for unrequested/unapproved values, request/poll tampering and partial consent. Exercise the upstream claim provider with assertions that rejected data is never requested or emitted.
47
+ - JSON-bearing snapshots require deep immutability or an equivalent defensive-copy contract; the current snapshot implementation only needs to freeze flat values.
48
+ - Add forward migration tests on all three DBs and an explicit mixed-version activation test. Keep current scope-only fixtures to establish compatibility.
49
+ - Add the selected capability to the real node HTTP harness and compare behavior, including deliberately unsupported cases. Do not infer RAR or claim enforcement from successful scope-only issuance.
50
+
51
+ The current API can remain for the initial subset. This review closes the question of whether an immediate wholesale API rewrite is necessary; it does not close the feature gaps. Next implementation work should select one concrete capability and its protocol requirements, rather than introducing all of these schema fields in advance.
52
+
53
+ Follow-up: the [completion-error analysis](completion-error-boundaries.md) found that node's generic error acceptance must not be equated with terminal poll errors. It preserves the current allowlist and documents an explicit cancellation/failure API as a separate future task.
54
+
55
+ ## Source map
56
+
57
+ - node v9.12.2: `lib/models/grant.js` (openid/resources/rejected/rar), `lib/models/backchannel_authentication_request.js`, `lib/helpers/grant_common.js`, `lib/provider.js` (`backchannelResult`), `lib/helpers/defaults.js` (`GrantTTL`). Reproducible source is installed by the [locked reference package](../../test/reference/full-op/package-lock.json).
58
+ - rodauth-oauth 1.7.0: `lib/rodauth/features/oidc.rb` (`generate_id_token`, `id_token_claims`), `oauth_resource_indicators.rb`.
59
+ - Gem: [feature](../../lib/rodauth/features/ciba.rb), [schema](../../lib/rodauth/ciba/schema.rb), [consent tests](../../test/ciba_grant_test.rb).