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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2cf78ffc87f7347b2d1fbdd6742bd1a4bf7869f18da55011b6c8ebe691d99781
4
+ data.tar.gz: 5e18ae4dfe1cf251d814400f4d3bc797a394c29c86ec0bae06d7ec2ad5abc531
5
+ SHA512:
6
+ metadata.gz: a7839427730506c42a442662cfe0913f5253ce56b1bdae49658924247a08700c16f2b93f7f4d33786b2058c78204d1fa5e79b8ad624052145383959adcf81e48
7
+ data.tar.gz: b66028b11a05ce189271fc482181dc95b04cc6378affc00e1a1a097450fe4ee1127d3b205c42e2205bc4d450de4ef299bc44f6511071d1ef0e49da1f67cde4d5
data/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — publication pending
4
+
5
+ First release of the CIBA OpenID Provider extension for rodauth-oauth.
6
+
7
+ - Require saved-consent authorization for optional refresh issuance and renewal.
8
+
9
+ - Poll delivery, Discovery, TLS/form request validation and confidential client
10
+ authentication, including integration with upstream JWT client authentication.
11
+ - Ruby completion and saved-consent APIs, idempotent completion, account/client
12
+ binding, transactional token issuance and replay handling.
13
+ - Same-database Sequel storage, explicit migrations, cleanup, transactional hooks
14
+ and best-effort post-commit observation events.
15
+ - Optional ping, additional identity hints, signed requests, user codes, claims,
16
+ resource indicators, authorization details and request context, with documented
17
+ activation settings and application callbacks.
18
+ - Optional DPoP, mTLS and ID Token encryption integrations with documented limits.
19
+ - Bounded outbound HTTP and JWKS caching with explicit freshness and a capped,
20
+ publisher-authorized stale-if-error allowance.
21
+ - HTTP 401 `invalid_client` for absent CIBA client authentication; rejected polls
22
+ preserve approval and allow a legitimate authenticated retry.
23
+ - Startup rejection of upstream single-row OAuth token storage, preserving token/consent provenance.
24
+ - Saved `require_auth_time` enforcement during approval, issuance and refresh, without fabricating timestamps.
25
+ - Local approval/client examples, installed-package smoke tests and Ruby/DB CI.
26
+
27
+ Refresh tokens, pairwise subjects and dynamic CIBA registration are experimental,
28
+ disabled by default and excluded from the initial supported release scope. Push
29
+ and encrypted ID Token hints are not implemented. This is not an OpenID Certified
30
+ implementation or a claim of conformance for every optional-feature combination.
31
+ See [README](README.md) and [release readiness](docs/release-readiness.md).
32
+
33
+ Detailed intermediate changes are retained in the repository's
34
+ [development history](https://github.com/kajisha/rodauth-ciba/blob/main/docs/research/pre-release-development-history.md).
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rodauth-ciba contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # rodauth-ciba
2
+
3
+ CIBA OpenID Provider extension for **rodauth-oauth**, with default poll and optional ping. Version 0.1.0 is prepared for its first release; RubyGems publication is pending.
4
+
5
+ It adds a backchannel authentication endpoint, Discovery metadata, Ruby approval/denial APIs, transactional hooks and post-commit observation events. Your app handles customer authentication, consent UI, notification transport, business authorization and any later delegation.
6
+
7
+ ## Supported scope
8
+
9
+ - Poll delivery, `login_hint`, optional [login_hint_token](docs/login-hint-token.md) and [id_token_hint](docs/id-token-hint.md), public subject identifiers, static CIBA client registration.
10
+ - Upstream confidential client authentication; basic/post and private-key/shared-secret JWT methods have integration coverage. Optional [mTLS](docs/research/mtls-reference-contract.md) adds explicit application trust callbacks, certificate binding, dynamic registration and verified TLS integration tests.
11
+ - Signed ID Tokens and opaque or JWT access tokens, with optional [ID Token encryption](docs/id-token-encryption.md); CIBA refresh tokens are disabled by default. The opt-in [refresh implementation](docs/refresh-tokens.md) is experimental and excluded from the initial supported release scope.
12
+ - Optional [DPoP](docs/research/dpop-reference-contract.md) covers token binding, proof/nonce validation, replay handling, UserInfo and introspection. The documented configuration and deployment limits apply.
13
+ - Same-database request storage with Sequel, explicit migration, bounded cleanup/recovery lookup.
14
+ - Optional [explicit claim consent](docs/claims.md) and [resource indicators](docs/resources.md), each with its own migration and activation flag. Their documented limitations apply.
15
+ - Optional [authorization details](docs/authorization-details.md) with mandatory application policy and separate requested/approved/issued data.
16
+ - Optional [request context](docs/request-context.md) with application validation and request-only storage.
17
+
18
+ Optional [signed authentication requests](docs/signed-requests.md) use registered client keys and separate client authentication. Optional [user codes](docs/user-code.md) require an application validation callback. Optional [ping](docs/ping.md) sends after commit and exposes explicit retry. No push or encrypted ID Token hints. Optional [CIBA pairwise subjects](docs/pairwise.md) and [CIBA dynamic registration](docs/research/registration-reference-contract.md) are experimental, disabled by default, and excluded from the initial supported release scope. Their integration tests do not establish support for every optional-feature combination. The extension preserves OP-wide authentication settings; CIBA requires a registered confidential client. Existing non-CIBA authorization-code behavior and registration validation are delegated to upstream; management responses suppress stored credential hashes. This project is not OpenID Certified.
19
+
20
+ ## Install
21
+
22
+ Ruby>=3.3 is required. Until a registry release exists, build from this checkout:
23
+
24
+ ```sh
25
+ bundle install
26
+ gem build rodauth-ciba.gemspec
27
+ gem install ./rodauth-ciba-0.1.0.gem
28
+ ```
29
+
30
+ The initial dependency bounds are intentionally narrow: rodauth-oauth1.7.0, Rodauth2.28.x, Sequel5.108.x and JWT3.3.x. Your application supplies its DB adapter and existing Rodauth/OAuth schema.
31
+
32
+ ## Integrate
33
+
34
+ 1. Copy [the migration](examples/migration.rb) into your app's Sequel migrations after its account/application/grant migrations and run it explicitly. See [migration customization](docs/operations.md).
35
+ 2. Require `rodauth/ciba`, enable `:ciba`, configure signing keys, a stable issuer/base URL, your login-hint resolver and AD initiation callback. Request lifetime and maximum default to 600 seconds; override them for your deployment.
36
+ 3. Statically provision a confidential OAuth application with the CIBA grant, `backchannel_token_delivery_mode: "poll"`, public subjects and appropriate scopes.
37
+ 4. Expose Discovery and Rodauth routes. Implement `trigger_ciba_authentication_device` to start customer authentication/approval, then call the Ruby completion API after your app verifies customer authentication and consent.
38
+
39
+ ```ruby
40
+ require "rodauth/ciba"
41
+
42
+ plugin :rodauth do
43
+ enable :ciba
44
+ # Existing db, account/OAuth schema, signing keys and issuer configuration here.
45
+ ciba_request_lifetime 300
46
+ ciba_max_request_lifetime 600
47
+ resolve_ciba_login_hint do |hint, oauth_application:|
48
+ # Example only: enforce tenant/client policy for your application.
49
+ db[:accounts].where(email: hint).get(:id)
50
+ end
51
+ trigger_ciba_authentication_device do |request|
52
+ # Start your app-owned approval flow using request[:id].
53
+ # Returning permits the success response; exceptions produce server_error.
54
+ end
55
+ end
56
+ ```
57
+
58
+ The app completes a request using its **internal integer ID**, fixed local customer ID and verified authentication context:
59
+
60
+ ```ruby
61
+ # After verifying that this customer may approve this request:
62
+ grant = rodauth.create_ciba_grant(account_id: customer_id,
63
+ oauth_application_id: client_application_id, scopes: approved_scopes)
64
+ rodauth.backchannel_result(request_id, grant,
65
+ auth_time: actual_authentication_time.to_i, acr: "urn:example:mfa", amr: ["pwd", "otp"])
66
+ # Or report refusal after the same application-side identity/authority checks:
67
+ rodauth.backchannel_result(request_id,
68
+ Rodauth::CibaSupport::ProtocolError.new("access_denied"))
69
+ ```
70
+
71
+ Keep the saved Grant ID for retries; wrap creation and completion in an application DB transaction if both must commit together. The demo shows this pattern. The convenience `approve_ciba_request` / `deny_ciba_request` APIs remain available.
72
+
73
+ Identical completion retries return `:already_completed`; conflicting decisions are rejected. Token collection succeeds only once. Public `auth_req_id` possession is not approval authority.
74
+
75
+ [Full configuration/API](docs/api.md) · [Deployment/operating boundaries](docs/operations.md) · [Protocol-to-test mapping](docs/protocol-coverage.md)
76
+
77
+ ## Try the local demo
78
+
79
+ The sample simulates a fixed customer; it does not authenticate a real user. It binds to127.0.0.1, uses disposable data and explicitly enables local HTTP. Do not deploy it as an approval service.
80
+
81
+ ```sh
82
+ bundle exec ruby examples/server.rb
83
+ # In another terminal:
84
+ bundle exec ruby examples/client.rb
85
+ ```
86
+
87
+ Open http://127.0.0.1:9292/ and approve or deny the request. The client polls and verifies the returned ID Token using the OP's published keys.
88
+
89
+ ## Test
90
+
91
+ ```sh
92
+ bundle exec ruby test/run.rb # local SQLite
93
+ bundle exec ruby -Ilib examples/smoke.rb
94
+ bundle exec ruby -Ilib examples/extensions_smoke.rb # optional claims/resource flows
95
+ ruby bin/verify-package # build, install outside checkout, run all smoke flows
96
+ ./bin/test-databases --seed 3363 # Docker: SQLite, PostgreSQL, MySQL
97
+ ./bin/test-rubies # Docker: Ruby 3.3 and 3.4 with SQLite
98
+ ```
99
+
100
+ The Docker runner builds the Ruby image, waits for database health, runs the same suite on each DB and removes its containers/network on exit. DB ports are not published. Data is on tmpfs; each test owns a random database. Only images/build cache remain. `pg` and `mysql2` are an optional Bundler group enabled inside Docker. The macOS/mise equivalent is `mise exec ruby -- ruby test/run.rb`.
101
+
102
+ [Release readiness](docs/release-readiness.md) records the current release checks and review limits. [Release validation](docs/release-validation.md) preserves historical results. The [authentication-fix CI run](https://github.com/kajisha/rodauth-ciba/actions/runs/36710533803) passed the Ruby 3.3/3.4/4.0 SQLite jobs and the Docker database job. CI results apply to the tested commit, not to future changes.
103
+
104
+ ## License
105
+
106
+ MIT. Copyright2026 rodauth-ciba contributors. Dependencies retain their own licenses.
@@ -0,0 +1,9 @@
1
+ # 状態変更の拡張点を Rodauth の before / after 慣例に合わせる
2
+
3
+ CIBA の主要な状態変更に、Rodauth の慣例に沿う before / after フックを設ける。専用フックを設けない案や独自の拡張機構を追加する案に対し、基盤と同じカスタマイズ方法を維持することを優先する。これらは監査専用ではなく、監査機能の導入を CIBA の利用条件にしない。
4
+
5
+ before / after フックは状態変更と同じ DB トランザクション内で呼ぶ。after は commit 後ではない。フックから伝播する例外では DB の変更をロールバックするが、外部システムへの副作用までは取り消せない。具体的なフック名・対象となる状態変更・エラー応答は今後設計する。
6
+
7
+ 任意の観測用イベントは別の契約とし、通知先の失敗で CIBA の成否を変えず、障害を報告する。Rodauth の既存 audit_logging と CIBA の連携可能範囲は、実装時に検証する。
8
+
9
+ 根拠: [Rodauth の create_account 実装](https://github.com/jeremyevans/rodauth/blob/master/lib/rodauth/features/create_account.rb)、[audit_logging](https://rodauth.jeremyevans.net/rdoc/files/doc/audit_logging_rdoc.html)、[製品比較](../research/idm-ciba-integration.md)。
@@ -0,0 +1,16 @@
1
+ ---
2
+ status: accepted
3
+ ---
4
+ # 発行の原子性を維持し、nodeとの整合化を段階的に進める
5
+
6
+ node-oidc-providerとの比較では、非transactionalなadapterで発行失敗後も要求の消費が残る動作と、このgemの同一DB rollbackに差がある。2026-09-30のユーザー合意により、rodauthのトランザクション内フックとDBの原子性を維持し、初版はpoll/login_hintで公開を目指しながら、より広い対応を[段階計画](../alignment-roadmap.md)で進める。再試行できるのは状態が許す場合であり、応答喪失をrollbackの証明としない[失敗時の契約](../failure-contract.md)を公開する。
7
+
8
+ ## 検討した代替案
9
+
10
+ 消費だけを独立して確定する案は、技術的失敗でも再承認が必要になるため採用しない。発行だけを内側のsavepointで戻す案も、外側のrollbackやプロセス停止を越えて消費を保持する保証にはならない。切替可能な複数方針は、実際の導入先で相反する要求が判明するまで追加しない。
11
+
12
+ 初版の限定範囲を最終到達点とする案と、全機能の整合化を初版公開条件とする案も採用しない。将来の個別機能は、仕様・互換性・検証条件を確定してから実装する。Jevの数値は判断の補助であり、合意や正しさの証明ではない。
13
+
14
+ ## 帰結
15
+
16
+ [ADR 0003](0003-follow-rodauth-hook-conventions.md)のフック契約を維持する。DB外の通知・署名サービス等の副作用はrollbackできず、導入アプリが冪等性や配送を管理する。完全なOP比較とDB別の競合・障害検証は未完了であり、この決定で免除しない。
@@ -0,0 +1,136 @@
1
+ # Alignment closeout
2
+
3
+ This is the current work queue for alignment with node-oidc-provider 9.12.2,
4
+ not a replacement for the original roadmap or its 159-row CIBA catalog.
5
+ Earlier validation logs are historical evidence, not a test of later changes.
6
+
7
+ ## Unresolved behavior differences
8
+
9
+ 1. **Completion errors: implemented and verified.** The gem now
10
+ saves a generic error code, consumes it on first poll, rejects replay and
11
+ retains the transactional negative-completion hooks. Existing denials have
12
+ an explicit nullable-column migration. Technical failure emits `failed`
13
+ instead of reporting customer refusal. Three DBs each pass18 tests /339
14
+ assertions ([log](validation/completion-errors-matrix.txt)). The reference
15
+ accepts custom codes but does not round-trip application descriptions, as
16
+ [measured](research/completion-error-boundaries.md). The installed artifact also passes, including the new custom-error flow
17
+ ([log](validation/completion-errors-installed.txt)); the full Ruby3.3 regression passes399 tests /10,719 assertions
18
+ ([log](validation/completion-errors-full-ruby33.txt)), with two SQLite row-lock
19
+ skips. Ruby3.4 three-DB full regression also passes399 tests on each backend.
20
+ 2. **Remote key cache: standards-informed policy implemented.** Explicit HTTP
21
+ freshness replaces the minimum60-second floor. A failed retrieval can use old
22
+ keys only with publisher stale-if-error, capped at60 seconds with fixed expiry.
23
+ Authentication/encryption share one policy without a dedicated switch.
24
+ This is an intentional reference difference approved after the standards/Jev
25
+ review. See [operations](operations.md#remote-jwks-cache-lifetime) and
26
+ [decision](research/jev-alignment-cache-rfc-review.md).
27
+ 3. **Hint validation profile: implemented and integration verified.** The gem
28
+ now follows the measured reference azp/typ/time-claim behavior for identity
29
+ hints, including optional iat/exp and fractional times. Unknown critical JOSE
30
+ extensions remain rejected; b64=true is recognized. All accepted hints still
31
+ require new approval. The [three-DB subset](validation/id-hint-validation-matrix.txt)
32
+ passes31 tests /983 assertions per DB. The installed gem also passes the new
33
+ profile scenario and existing smokes ([log](validation/id-hint-validation-installed.txt)).
34
+ The latest full Ruby3.3/4.0 runs include this change and pass400 tests;
35
+ the updated Ruby3.4 three-DB run also passes400 tests on every backend. User-code activation
36
+ remains an explicit opt-in configuration adaptation in CIBA-013.
37
+
38
+ ## Evidence reconciliation
39
+
40
+ - CIBA-072 is reconciled against the [mandatory parameter inventory](research/required-parameter-inventory.md) and is now verified. CIBA-067 is also reconciled against the [supported parser inventory](research/request-parameter-inventory.md); policy differences remain separate. Do not require every possible extension combination or
41
+ count these aggregate rows as new features. The new signed-request omission
42
+ regression removes each required field, supplies valid unsigned outer values,
43
+ and proves rejection without requests, grants or dispatch. Its valid control
44
+ still dispatches a pending request: [3 tests / 119 assertions](validation/signed-required-omissions.txt).
45
+ - Keep the other partial rows explicit: user-code activation policy (013), shared
46
+ lifetime timing (080), retained poll throttling (124), absent conditional
47
+ error_uri output (138), and deployment hint-retention policy (151). Their
48
+ classification does not automatically require new runtime features.
49
+ - Review the existing roadmap's remaining authentication, registration,
50
+ refresh and transport claims against the evidence already collected. Identify
51
+ a concrete missing behavior before adding another combination test.
52
+
53
+ ## Final validation gate
54
+
55
+ Current source is recorded in the
56
+ [runtime/test manifest](validation/id-hint-validation-source-manifest.json).
57
+ Subsequent gemspec/verifier edits add missing guide files to the artifact without
58
+ changing runtime behavior. Latest results:
59
+
60
+ | Gate | Result |
61
+ |---|---|
62
+ | Ruby3.3 / SQLite | 400 tests /10,747 assertions, no failures/errors, two row-lock skips ([log](validation/id-hint-validation-full-ruby33.txt)) |
63
+ | Ruby3.4 / SQLite | 400 /10,747, no failures/errors, two row-lock skips |
64
+ | Ruby3.4 / PostgreSQL | 400 /10,744, no failures/errors/skips |
65
+ | Ruby3.4 / MySQL | 400 /10,744, no failures/errors/skips ([three-DB log](validation/id-hint-validation-full-databases.txt)) |
66
+ | Ruby4.0.6 / SQLite | 400 /10,747, no failures/errors, two row-lock skips ([log](validation/id-hint-validation-full-ruby40.txt)) |
67
+ | Pinned reference npm test | Passed, including completion-error wire checks ([log](validation/alignment-consolidated-reference.txt)); subsequently modified lifecycle fixture also passes [hint-profile checks](validation/node-id-hint-validation-profile.txt) |
68
+ | Installed gem | Passed including new hint profile, completion failure/replay, migration guides and existing real-TLS smokes ([log](validation/alignment-closeout-installed.txt)); SHA256 ddf468c63c53e1d78783ff4c2875ce68ff765a3591a6ef54f1dfbdfdc196bf51 |
69
+
70
+ The containers use existing test images with current lib/test/examples mounted
71
+ read-only, not a fresh image build or dependency resolution. Ruby4.0 is local
72
+ macOS. SQLite skips are covered by the server-DB runs. The reference mTLS fixture
73
+ is separate from npm test and remains backed by its latest real-TLS execution;
74
+ it is not implicitly rerun by npm test. The prior395-test consolidated runs are
75
+ historical baselines preceding generic completion errors.
76
+
77
+ A passing test gate does not resolve the policy differences above. Do not
78
+ publish or mark the alignment goal complete until those differences and all
79
+ original roadmap obligations have been reconciled.
80
+
81
+ Accepted adaptations remain same-DB atomic issuance and Rodauth hooks
82
+ ([ADR 0004](adr/0004-retain-atomic-issuance-and-stage-alignment.md)), and the
83
+ roadmap's retained polling interval behavior. App-owned UI, identity verification,
84
+ business consent, trusted proxy configuration and deployment availability are
85
+ application contracts, not automatically gem implementation tasks.
86
+
87
+ ## Current decision gate
88
+
89
+ The cache-policy decision is now implemented. The user approved the
90
+ [standards-informed Jev decision](research/jev-alignment-cache-rfc-review.md).
91
+ Older minimum-floor results above and in historical logs do not describe the
92
+ current runtime. HTTP freshness directives take precedence; fallback is explicitly
93
+ publisher-authorized, bounded, and never renewed by failed retrievals.
94
+
95
+ The new cache is scoped per Rodauth configuration, bounded to1024 entries,
96
+ independent of upstream cache internals, and preserves existing explicit eviction
97
+ through `http_request_cache.uncache`. Malformed successful responses and security
98
+ failures invalidate stale eligibility; successful replacement/eviction prevents
99
+ in-flight stale snapshots from restoring old keys. See the operations guide for
100
+ custom-store and multi-process limitations.
101
+
102
+ This resolves the remote-cache policy work, not every obligation in the original
103
+ alignment roadmap. Do not infer overall release readiness from this change alone.
104
+
105
+ ## Current cache validation
106
+
107
+ - Ruby3.3 and3.4 / SQLite: each10 tests /91 assertions, zero failures/errors/skips
108
+ ([3.3](validation/jwks-stale-ruby33.txt), [3.4](validation/jwks-stale-ruby34.txt)).
109
+ - Installed gem: existing HTTP/TLS authentication/encryption smokes passed
110
+ ([log](validation/jwks-stale-installed.txt)). Dependencies reused locally.
111
+ - Ruby4.0.6 / SQLite:410 tests /10,838 assertions, zero failures/errors,
112
+ two existing row-lock skips ([full log](validation/jwks-stale-full.txt)).
113
+ - Database-specific concurrency is unchanged; this cache revision has not been
114
+ rerun against PostgreSQL/MySQL. Those older matrix results are historical.
115
+
116
+
117
+ ## Missing-client-authentication correction
118
+
119
+ CIBA Core section13 maps absent client authentication to401 invalid_client. The
120
+ previous400 invalid_request mapping for a request with neither a header, client_id
121
+ nor client_assertion is corrected at the Backchannel and CIBA poll endpoints.
122
+ Regression checks fail before the fix and pass afterward; rejected polls preserve
123
+ approval, create no grant and allow an authenticated retry. See
124
+ [before](validation/missing-client-auth-red.txt) and
125
+ [after](validation/missing-client-auth-green.txt). This deliberately differs from
126
+ the earlier reference-aligned error mapping. Other optional-authentication error
127
+ paths have not been exhaustively reassessed by this narrow correction.
128
+
129
+ Final Ruby4.0.6 / SQLite validation:410 tests /10,844 assertions, zero
130
+ failures/errors and two existing row-lock skips ([full log](validation/missing-client-auth-final.txt)).
131
+ The focused error/authentication regression run passed8 tests /171 assertions
132
+ ([targeted log](validation/missing-client-auth-targeted.txt)).
133
+
134
+ [Jev reassessment](research/jev-auth-fix-review/report.md) scored the narrow
135
+ correction3.95/4 (confidence0.96), with supported status (confidence0.97).
136
+ It did not establish complete coverage of all optional authentication-error paths.
@@ -0,0 +1,160 @@
1
+ # Staged alignment with node-oidc-provider
2
+
3
+ > Latest policy supersedes the historical minimum-floor notes below: use explicit
4
+ > HTTP freshness, and permit failed-fetch reuse only with publisher stale-if-error,
5
+ > capped at60 seconds without renewing deadlines. See [operations](operations.md#remote-jwks-cache-lifetime).
6
+
7
+
8
+ > Current cache decision: successful CIBA remote JWKS fetches now have a minimum
9
+ > 60-second lifetime for authentication and encryption, with no dedicated policy
10
+ > switch. This supersedes older shorter-TTL difference notes below. Reuse after
11
+ > failed refresh remains unresolved and is not implemented. See
12
+ > [alignment closeout](alignment-closeout.md#current-decision-gate).
13
+
14
+
15
+ The current [closeout queue](alignment-closeout.md) separates unresolved behavior
16
+ differences from evidence reconciliation and final validation. The historical
17
+ results below do not all describe the latest source state.
18
+
19
+ The original22 partial findings are [triaged](research/alignment-partial-triage.md)
20
+ into feature gaps, bounded coverage, shared limitations and adaptations. Sixteen have
21
+ since gained verified protocol evidence, including: Edwards signed requests, configured assertion algorithms,
22
+ ping response bodies and management updates, signed-request NumericDate, and
23
+ missing-client error mapping. One timing item is explicitly an unverified application contract; the current overlay has5 partial findings.
24
+ Edwards requests have fixed cross-library vectors, remote-key and installed
25
+ execution evidence. Classification is not completion of this roadmap.
26
+
27
+ Accepted on 2026-09-30: keep poll/login_hint for the initial release, retain atomic DB issuance, and pursue broader alignment in stages. Reference: node-oidc-provider v9.12.2. [ADR 0004](adr/0004-retain-atomic-issuance-and-stage-alignment.md) records the tradeoff; [current status](node-alignment.md) separates implementation from evidence. This is not a claim of feature parity or a commitment to ship every feature in the next release.
28
+
29
+ ## Current requirement audit (2026-09-30)
30
+
31
+ This table is the current cross-workstream audit; the numbered sections below
32
+ remain the accepted plan. A passing subset does not close the broader objective.
33
+
34
+ Latest local regression (Ruby4.0.6 / SQLite): [380-test run](validation/alignment-latest-local.txt)
35
+ had zero assertion failures,18 socket-bind permission errors and two row-lock skips.
36
+ All18 affected tests then passed with local listener permission ([392 assertions](validation/alignment-local-socket-retry.txt)).
37
+ This is a two-stage local result, not a clean single-run result or a refreshed
38
+ supported-Ruby/three-DB matrix. It includes the signed/ping required-field regression
39
+ and the recent recipient-selection, real-TTL and management-overlap tests.
40
+
41
+ Subsequent registration correction: `require_auth_time` now rejects non-booleans,
42
+ persists explicit values and preserves false on management GET, matching the
43
+ [reference contract](research/registration-reference-contract.md#require_auth_time-boolean-metadata).
44
+ Ruby3.4 three-DB validation passes2 tests /95 assertions each. Token auth_time
45
+ emission/defaulting remains a separate behavior to compare; metadata validation
46
+ alone does not close that boundary.
47
+
48
+ The subsequent [authentication claim selection](authentication-claims.md) correction
49
+ saves request/client selection at acceptance and applies it on initial issuance and
50
+ refresh, with an explicit application scope mapping. The reference comparison covers
51
+ 36 configurations with verified initial/refresh signatures; the Ruby3.4 three-DB
52
+ subset passes13 tests /951 assertions each. NULL legacy sources preserve their
53
+ previous disclosure contract; existing installations require an explicit migration.
54
+ This resolves the measured new-request auth_time/acr/amr output difference, not all
55
+ application claim policy combinations.
56
+
57
+ | Planned boundary | Current authoritative evidence | Remaining work / classification |
58
+ |---|---|---|
59
+ | Baseline lifecycle, persistence and package | [Full DB matrix](validation/alignment-current-databases.txt), [Ruby matrix](validation/alignment-current-rubies.txt), [installed artifact](validation/alignment-current-package.txt), [full reference OP suite](validation/alignment-current-reference.txt) | The pre-HMAC351-test baseline suite passes Ruby4.0.6 on SQLite/PostgreSQL/MySQL and Ruby3.3/3.4 on SQLite. SQLite has5,637 assertions and two row-lock skips; PostgreSQL/MySQL have5,634 assertions with no skips. The full configured reference suite and installed artifact smoke pass, including encrypted output and actual HTTPS recipient retrieval. Not all Sequel adapters, every Ruby × DB combination, or an independent audit. The159-item catalog now has103 verified boundaries,5 partial findings,17 reference-unsupported conditions,20 client duties and14 application contracts; all referenced test methods exist. These checks do not establish completion of broader alignment. |
60
+ | Request versus saved consent, scopes, nonce, claims, resources/RAR | `test/ciba_grant_test.rb`, `test/ciba_claims_test.rb`, `test/ciba_resources_test.rb`, `test/ciba_rar_test.rb`, `test/ciba_refresh_permissions_test.rb` | Implemented opt-in capabilities. Claim value/essential constraints and RAR semantics remain application policy; this is not arbitrary policy equivalence. |
61
+ | Other hints | [login hint token](login-hint-token.md), [ID hint](id-token-hint.md), `test/ciba_pairwise_test.rb`, installed smoke | Signed ID hints and application token resolver implemented. Pairwise hints resolve through an application callback to a canonical account and create a new pending request without inherited consent. Encrypted ID hints are rejected by both the gem and node v9.12.2 with encryption enabled ([measured boundary](research/id-token-hint-reference-contract.md#encrypted-hint-boundary-correction-after-direct-measurement)); this is not a missing parity feature. Arbitrary application resolver correctness is not established by the fixture. |
62
+ | ID Token signing and encrypted output | [ID hint/signing contract](research/id-token-hint-reference-contract.md), [encryption](id-token-encryption.md), independent jose probes, installed HTTPS recipient tests | Edwards signing/JWKS/hints now have independent verification, supported-Ruby, installed and refresh/pairwise evidence. RSA-recipient encryption supports two key algorithms and six content methods; independent jose checks issuance/refresh/tampering across36 combinations. DCR/defaults/removal and management recipient replacement are measured against the reference. HTTPS restrictions, cache eviction/rotation and retrieval-failure rollback are tested. CIBA HMAC signing/hints now use client secrets with three-DB and independent jose evidence. DCR secret lengths, installed-gem issuance/refresh/hints and backend secret replacement are verified; Ruby3.3/3.4 each pass354 tests/5,847 assertions ([evidence](validation/hmac-id-token-rubies.txt)). Backend replacement does not establish a standard rotation API. Client-secret-derived dir/AES-KW now has initial24-combination issuance/refresh and independent jose evidence ([contract](id-token-encryption.md#client-secret-derived-encryption)); installed-artifact coverage now passes16 configurations, and unrelated JWKS fetching is fixed with private-key authentication preserved; Ruby3.3/3.4 each pass358 tests/6,274 assertions ([evidence](validation/symmetric-encryption-rubies.txt)). AES-GCMKW now has a CIBA-local OpenSSL wrapping adapter with initial42-combination symmetric tests and84-token independent jose verification; the installed artifact passes28 symmetric configurations and Ruby3.3/3.4 each pass358 tests/6,724 assertions ([evidence](validation/gcmkw-rubies.txt)). ECDH has an initial CIBA-local adapter for four algorithms/four curves, with96 initial/refresh configurations and192-token independent jose validation; registration/Discovery, installed32-case validation and Ruby3.3/3.4 full361-test suites now pass; ECDH-specific HTTP/installed-HTTPS retrieval and sequential management pass, including Ruby3.4 three-DB regression; nonempty ECDH key_ops rejection is now aligned with reference import failures. Missing metadata-compatible recipients now return reference-style400 invalid_client_metadata with rollback, and RSA operation declarations are checked at selection/wrapping. Multi-key selection now validates after scoring and does not fall back from an unusable preferred key, with reference and three-DB evidence. Actual TTL expiry is measured: gem honors max-age=2 while reference retains keys for at least60 seconds; the cache policy difference remains open. A management PUT overlapping initial/refresh issuance now matches reference snapshot behavior in the measured three-DB cases in the [contract](research/ecdh-encryption-reference-contract.md). Remote-cache lifetime and failed-refresh retention differ; policy clarification is pending and no change is assumed. Encrypted input hints are a separate reference-unsupported boundary. |
63
+ | Signed requests / user code | [signed requests](signed-requests.md), [user code](user-code.md), remote JWKS tests | Implemented opt-in with documented stricter validation, key lookup and app-policy boundaries. |
64
+ | Ping | [ping contract](ping.md), actual HTTPS installed smoke, full node ping harness | Implemented opt-in. Process-crash recovery and durable delivery are not guaranteed by either measured handler; multi-process delivery remains unverified. Push is not a target of this reference version. |
65
+ | Refresh and revocation | [refresh](refresh-tokens.md), [AT revocation comparison](research/access-token-revocation-reference.md), package smoke | Implemented default confidential-client lifecycle plus hooks/cleanup. Legacy unmarked tokens retain upstream handling; arbitrary issue/rotation policies and sender-constrained refresh combinations are not established by current evidence. |
66
+ | Completion error types | [error boundary](research/completion-error-boundaries.md), `test/ciba_alignment_test.rb` | Generic completion error codes are now persisted and returned once, with migration, idempotency/conflict checks, transactional hooks and ping. Three DBs each pass18 tests /339 assertions. Normal pending/expiry remain state-derived; submitting pending as a completion is terminal as in the reference. Application descriptions are not exposed by this API. Final full regression is pending for this change. |
67
+ | Client registration and subject types | `CibaSupport::Registration`, `test/ciba_registration_test.rb`; [real node registration contract](research/registration-reference-contract.md), [installed package](validation/registration-package.txt) | Opt-in DCR lifecycle, rotation, ping/signature/user-code and RAR/resource/refresh composition implemented; defaults remain static-only. Deletion, input/storage repairs, update/opposite-client/delete-refresh concurrency, consent-before-client locking and rollback tested. Installed artifact covers basic registration/issuance/management rotation/deletion. The default_max_age registration boundary now validates safe integer values and persists them in explicit storage ([contract](research/registration-reference-contract.md#default_max_age-validation-and-persistence)); this does not implement freshness policy. Additional client-auth combinations and remaining metadata validation remain incomplete; race tests do not cover arbitrary app hooks or new-consent interleavings. An initial opt-in [pairwise integration](pairwise.md) now covers sector derivation, application subjects and opaque/JWT UserInfo/hints; opaque/JWT refresh, rotation and callback-failure rollback now have integration coverage; installed-gem TLS sector-document validation and bounded redirect following pass; sector-document cache reuse and fresh registration validation are tested; sector changes affecting issued-token UserInfo and refresh are tested using stored issuance subjects; sequential pending-request updates and installed pairwise issuance also pass; concurrent sector management/issuance now passes two opaque/JWT cases on all three DBs, with forced update-before-generation on PostgreSQL/MySQL ([log](validation/pairwise-concurrent-matrix.txt)); concurrent refresh/key replacement and arbitrary hook interleavings remain unverified. |
68
+ | Authentication/token capabilities | JWT client assertion, signed request, resource and revocation tests | Existing tested mechanisms are scoped to CIBA. The [DPoP reference contract](research/dpop-reference-contract.md) now has actual OP issuance/UserInfo/refresh evidence and a Ruby probe demonstrating incompatible jti, stale-proof acceptance and proof reuse. An initial CIBA token-endpoint boundary now fixes identifier/freshness/replay handling with same-DB rollback, proof-required policy and new-key confidential refresh; CIBA opaque/JWT UserInfo now verifies proof/key/ath, live issuance provenance and replay, with conjunctive token/expiry/revocation predicates. DPoP dynamic registration now covers boolean policy, storage, false/default responses, failed-update preservation and future-issuance policy replacement. Required/optional server nonce challenges, old-iat acceptance with valid nonce, cross-endpoint use, nonce expiry/secret rotation and replay-ledger retention now have reference/local evidence. Installed DPoP artifacts now pass DCR, nonce issuance, UserInfo and new-key refresh with explicit loaded-file provenance. POST UserInfo method binding and explicit plaintext/custom hash-column storage also pass the DPoP subset on all three DBs. Opaque CIBA DPoP introspection now returns active/cnf/type without proof presentation, while retaining authentication, token identity, expiry and revocation checks; resource audience/scope also have regression coverage. OIDC Discovery now preserves the introspection URL/auth metadata and DPoP algorithms from OAuth metadata; disabled features remain absent. Node omits the introspection auth-method list, while the gem retains upstream's existing list. The metadata correction passes 43 related local tests and installed-package validation. External resource enforcement, proxy handling, cluster nonce/clock behavior and broader combinations remain incomplete. Issuer-audience private_key_jwt/client_secret_jwt now match the measured CIBA issuance/refresh behavior, including wrong-audience and assertion-replay rejection (59 related tests on three DBs). The [mTLS real-TLS reference and Ruby probe](research/mtls-reference-contract.md) identified wrong certificate digests, missing UserInfo enforcement and self-signed auth500. An initial correction now implements explicit trust callbacks, certificate-DER matching/binding, opaque/JWT UserInfo, introspection and confidential refresh. Final mTLS/DPoP cases pass30 tests on three DBs. Installed artifacts now pass actual Ruby TLS for PKI/self-signed × opaque/JWT, including rejected absent/header-only/foreign certificates and bound UserInfo (mtls-installed-tls.txt). mTLS DCR now covers subject/binding metadata, storage, self-signed keys and management replacement, including actual node TLS clients and gem dynamic issuance. Installed mTLS DCR now passes the real TLS flow and management policy/credential changes in all four PKI/self-signed × opaque/JWT combinations. Capability is now advertised independently of per-client binding: CIBA ignores upstream global forcing while retaining old-token binding; 21 related cases pass on each DB and installed TLS checks pass with global forcing enabled ([contract](research/mtls-reference-contract.md#capability-versus-client-binding-policy)). Application-owned aliases now pass complete reference/installed-gem flows on separate TLS ports with a fixed canonical issuer ([contract](research/mtls-reference-contract.md#endpoint-aliases-are-application-deployment-configuration)); automatic alias routing is not a reference feature. Remote x5c cache retention and forced-expiry replacement now have reference and three-DB gem evidence; retrieval error mapping is corrected, while rejection after failed refresh is intentionally stricter than node ([contract](research/mtls-reference-contract.md#remote-x5c-rotation-and-retrieval-failures)). Installed-artifact remote rotation now passes HTTPS x5c retrieval, forced-expiry certificate replacement, refresh/new binding, retained old binding and failure/recovery in opaque/JWT configurations. A discovered JWT introspection cnf omission is fixed with live issuance validation; 14 cases pass on each DB ([contract](research/mtls-reference-contract.md#installed-https-certificate-rotation-and-jwt-introspection)). Real x5c TTL timing and deployed proxy/path rewriting remain unverified. DPoP alternate-origin binding now has reference HTTP and gem Rack/installed evidence; combined mTLS authentication+DPoP over real alias TLS now passes the reference and installed-gem fixtures (PKI/self-signed, with opaque/JWT gem tokens). Full mTLS support is not established. Remaining authentication combinations are also incomplete. |
69
+ | request_context | [Optional implementation](request-context.md), `test/ciba_request_context_test.rb`, full node HTTP harness and installed extension smoke | Implemented callback/storage boundary; app owns semantics. No automatic permission/token claim and no additional CIBA Core conformance claim. |
70
+ | Intentional transaction/poll differences | [ADR 0004](adr/0004-retain-atomic-issuance-and-stage-alignment.md), [failure contract](failure-contract.md) | Same-DB rollback and Rodauth hooks are accepted adaptations. Poll throttling and stricter validation are documented differences, not unimplemented node branches. |
71
+
72
+ The request-context reference test proves request_context is passed (including omission)
73
+ to the application's validation callback and stored in request.params; rejection
74
+ precedes device dispatch, and it is not automatically an ID Token claim. Node's
75
+ default callback raises until configured. The gem now provides an opt-in callback
76
+ and explicit nullable-column migration, without expanding claims/RAR to interpret
77
+ arbitrary business context.
78
+
79
+ ## 1. Establish the initial release baseline
80
+
81
+ - Publish the [failure contract](failure-contract.md), including ambiguous responses, replay revocation and external effects.
82
+ - Verify current migrations, approval/consent binding, concurrent polling, rollback and revocation on PostgreSQL and MySQL as well as SQLite. Run the supported Ruby matrix starting at 3.3; report actual tested combinations rather than claiming all Sequel adapters.
83
+ - Exercise a complete reference OP and the gem with comparable client scenarios: pending, denial, success, replay, expiry, wrong client and issuance failure. Preserve the intentional atomicity difference and record adapter/transaction setup.
84
+ - Build and install the gem outside the checkout, run its documented example, and ensure Discovery and protocol coverage match the supported subset. Review the public request/consent APIs against the next stage before freezing them.
85
+
86
+ Exit evidence: reproducible commands and logs, no unresolved correctness/security defects in the supported subset, documented limitations. The latest three-DB matrix, Ruby3.3/3.4 SQLite matrix and complete reference-OP lifecycle comparison now pass; see [status](node-alignment.md). The [API evolution review](research/consent-api-evolution.md) is recorded. These are initial baseline evidence, not completion of broader alignment or an independent security audit.
87
+
88
+ ## 2. Review how request and consent representations can grow
89
+
90
+ Compare node's claims, resource, nonce, RAR-related state and result errors with the existing request/saved-consent API. For each proposed extension, identify where requested and approved values differ, how issuance uses only approved values, and whether the upstream grant/token model supports it. Review consent expiry policy, including node's default versus the gem's optional expiry.
91
+
92
+ Exit evidence: concrete schema/API examples, rejection rules, forward migration and compatibility strategy. Preserve existing pending requests and issued-token provenance. Add fields and abstractions only when a selected capability needs them; do not create a generic framework in anticipation of all possibilities.
93
+
94
+ The [review](research/consent-api-evolution.md) supplies the proposed representation, compatibility rules and remaining gates. Individual increments still need final protocol mapping and implementation tests before activation.
95
+
96
+ ## 3. Implement additional capabilities in independent increments
97
+
98
+ The following are comparison workstreams, not already implemented features or a fixed delivery order. Select the next increment using a concrete interoperability need and upstream support; keep the remaining gaps visible.
99
+
100
+ | Workstream | Required evidence before claiming support |
101
+ |---|---|
102
+ | Other hints, signed requests and user_code | Specification mapping, issuer/audience/signature or application-validation boundaries, identity binding and negative tests; Discovery/registration accurately describes support. |
103
+ | Ping delivery | Client registration and notification-token validation, delivery/retry policy, endpoint security and independent client integration. A need for ping does not require unrelated features. |
104
+ | Claims, resources and richer consent | Request/approval separation, scope and audience enforcement, rejection handling and regression tests preventing unauthorized claims or access. RAR needs its own requirements analysis. |
105
+ | Completion error types | Explicit supported error mapping, terminal-state/retry semantics and protocol tests for each accepted error. |
106
+ | Client registration, subject types, token/authentication capabilities | Check upstream support and node behavior separately; test selected mechanisms without imposing CIBA-specific restrictions on unrelated OP flows. Refresh tokens require lifecycle and revocation coverage before advertisement. |
107
+
108
+ For each increment, update the protocol coverage, migration/API documentation, independent integration evidence and package smoke test. Push is not a parity target merely because CIBA defines it: the referenced node version supports poll/ping. No certification claim follows from these tests.
109
+
110
+ ## Deliberate differences and reconsideration
111
+
112
+ Same-DB rollback and Rodauth hooks remain the chosen adaptation. Consider durable one-attempt consumption only if an actual deployment requires consumption to survive issuance failure and process crashes. Consider configurable policies only when actual adopters require incompatible contracts, not from hypothetical demand.
113
+
114
+ The gem's advertised polling interval and `slow_down` differ from the referenced node handler. Keep the current behavior while reviewing interoperability evidence; absence of a branch in that handler is not evidence that every node deployment lacks rate limiting. Every remaining difference should be classified as an intentional adaptation, an unsupported capability or an unverified behavior before calling broader alignment complete.
115
+
116
+ Latest authentication-selection validation: [full local regression](validation/authentication-claims-full.txt),
117
+ Ruby4.0.6/SQLite385 tests /10,152 assertions, zero failures/errors, two row-lock
118
+ skips; [installed artifact](validation/authentication-claims-installed.txt) smoke
119
+ passes. The related Ruby3.4 three-DB subset above covers13 tests, not the full385.
120
+
121
+ Registration defaults: default_acr_values now validates configured strings,
122
+ preserves unique order in JSON storage/management GET, and supplies omitted
123
+ request acr_values. default_max_age now selects auth_time, including zero.
124
+ See the [contract](research/registration-reference-contract.md#default_acr_values-and-authentication-defaults)
125
+ for evidence and the remaining immutable max-age policy-input/empty ACR boundaries.
126
+
127
+ Maximum-age input is now [implemented and documented](max-age.md): the request
128
+ value takes precedence over default_max_age, its normalized seconds are saved
129
+ before device dispatch, and later metadata cannot reinterpret the request. Zero
130
+ represents fresh authentication; the application enforces freshness. Existing
131
+ request tables require the explicit nullable max_age migration. This closes the
132
+ previous missing device-policy input, not deployment authentication policy.
133
+
134
+ Latest max_age validation: [Ruby3.3/SQLite full suite](validation/max-age-ruby33.txt)
135
+ passes391 tests /10,418 assertions with two row-lock skips; the [Ruby3.4 three-DB
136
+ subset](validation/max-age-matrix.txt) passes22 tests /1,317 assertions per DB.
137
+ [Installed artifact](validation/max-age-installed.txt) passes smoke checks.
138
+ These are the measured combinations, not all Ruby × DB combinations.
139
+
140
+
141
+ The previously noted empty ACR boundary is corrected: an empty string uses
142
+ registered defaults like omission, including in signed requests without inheriting
143
+ unsigned outer values. [Reference/gem evidence](research/registration-reference-contract.md#empty-acr-request-correction)
144
+ covers the changed parameter/defaulting branch. Other parameter limits remain
145
+ separately tracked; no blanket relaxation of validation was made.
146
+
147
+ DPoP alias correction: [comparison](research/dpop-reference-contract.md#alternate-endpoint-origin-and-proxy-trust)
148
+ reproduces and fixes acceptance of canonical-origin proofs at another origin.
149
+ The issuer remains canonical; forwarding headers are ignored unless the app
150
+ explicitly supplies a trusted external endpoint through its callback.
151
+
152
+ CIBA ID Token projection now removes inherited access-token cnf, matching the
153
+ reference DPoP and real-TLS mTLS fixtures. Access-token/introspection confirmation
154
+ and proof enforcement remain tested. See the [comparison](research/dpop-reference-contract.md#id-token-versus-access-token-confirmation-claims).
155
+
156
+ The concrete mTLS-authentication plus DPoP-token alias gap is closed by
157
+ [real TLS integration](research/mtls-reference-contract.md#real-tls-aliases-with-certificate-authentication-and-dpop-tokens).
158
+ Both authentication methods pass in the reference; installed gem tests cover
159
+ all four method × opaque/JWT combinations. No further runtime change was needed.
160
+ Deployment proxy and cluster behavior remain application/integration boundaries.