diff --git a/SECURITY.md b/SECURITY.md
index d27cbed..6ae00ad 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,14 +4,15 @@ pk-auth is an authentication library, so security reports are handled with prior
## Supported versions
-Fixes land on the latest published `1.x` line. Maven Central and npm releases are immutable, so a
+Fixes land on the latest published `2.x` line. Maven Central and npm releases are immutable, so a
security fix ships as a new patch (or minor) release rather than a re-publish of an affected
version. Upgrading to the latest release picks up the fix.
| Version | Supported |
| -------------------- | ----------------------------- |
-| Latest `1.x` release | :white_check_mark: |
-| Older `1.x` releases | Fix only via the next release |
+| Latest `2.x` release | :white_check_mark: |
+| Older `2.x` releases | Fix only via the next release |
+| `1.x` | :x: (upgrade to `2.x`) |
| `0.x` (pre-stable) | :x: |
The browser SDK (`@pk-auth/passkeys-browser`) shares the same version line as the JVM artifacts and
diff --git a/clients/passkeys-browser/package-lock.json b/clients/passkeys-browser/package-lock.json
index e999f36..b62b9f3 100644
--- a/clients/passkeys-browser/package-lock.json
+++ b/clients/passkeys-browser/package-lock.json
@@ -3855,9 +3855,9 @@
}
},
"node_modules/fast-uri": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
- "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"dev": true,
"funding": [
{
@@ -5068,9 +5068,9 @@
}
},
"node_modules/qs": {
- "version": "6.15.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
- "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
+ "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -5364,9 +5364,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
diff --git a/clients/passkeys-browser/test/ceremonies.test.ts b/clients/passkeys-browser/test/ceremonies.test.ts
index 564492f..658a51d 100644
--- a/clients/passkeys-browser/test/ceremonies.test.ts
+++ b/clients/passkeys-browser/test/ceremonies.test.ts
@@ -9,9 +9,12 @@ import {
encodeRegistrationResponse,
} from "../src/ceremonies";
import type {
+ FinishAuthenticationRequest, FinishAuthenticationResponse,
FinishRegistrationRequest, FinishRegistrationResponse,
PublicKeyCredentialCreationOptionsJson,
- PublicKeyCredentialRequestOptionsJson, StartRegistrationRequest, StartRegistrationResponse,
+ PublicKeyCredentialRequestOptionsJson,
+ StartAuthenticationRequest, StartAuthenticationResponse,
+ StartRegistrationRequest, StartRegistrationResponse,
} from "../src/types";
import {
decodeJson,
@@ -25,12 +28,6 @@ import {
import * as http from "node:http";
import {CeremonyService} from "./helpers/ceremony";
import {FakeAuthenticator} from "./helpers/fakeAuthenticator";
-import {
- FinishAuthenticationRequest,
- FinishAuthenticationResponse,
- StartAuthenticationRequest,
- StartAuthenticationResponse
-} from "../dist/src";
const CREATE_OPTIONS_JSON: PublicKeyCredentialCreationOptionsJson = {
rp: { id: "example.com", name: "Example" },
diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md
index 30653cc..9e9be3c 100644
--- a/docs/adr/0001-record-architecture-decisions.md
+++ b/docs/adr/0001-record-architecture-decisions.md
@@ -1,4 +1,4 @@
-# 1. Record architecture decisions
+# 1. Architecture decision records
Date: 2026-05-12
diff --git a/docs/adr/0004-dagger-for-dropwizard.md b/docs/adr/0004-dagger-for-dropwizard.md
index ac681b8..d4a0b06 100644
--- a/docs/adr/0004-dagger-for-dropwizard.md
+++ b/docs/adr/0004-dagger-for-dropwizard.md
@@ -1,4 +1,4 @@
-# 4. Use Dagger 2 for the Dropwizard adapter's DI
+# 4. Dagger 2 for Dropwizard adapter wiring
Date: 2026-05-14
diff --git a/docs/adr/0006-userlookup-spi-not-owned.md b/docs/adr/0006-userlookup-spi-not-owned.md
index c3604fa..5b6321d 100644
--- a/docs/adr/0006-userlookup-spi-not-owned.md
+++ b/docs/adr/0006-userlookup-spi-not-owned.md
@@ -1,4 +1,4 @@
-# 6. `UserLookup` is an SPI, not an owned table
+# 6. `UserLookup` as a host-implemented SPI
Date: 2026-05-15
diff --git a/docs/adr/0008-dynamodb-single-table-design.md b/docs/adr/0008-dynamodb-single-table-design.md
index 958d361..3816784 100644
--- a/docs/adr/0008-dynamodb-single-table-design.md
+++ b/docs/adr/0008-dynamodb-single-table-design.md
@@ -1,4 +1,4 @@
-# 8. DynamoDB single-table design for auth items, separate users table
+# 8. DynamoDB single-table design with a separate users table
Date: 2026-05-14
diff --git a/docs/adr/0010-dropwizard-track-latest.md b/docs/adr/0010-dropwizard-track-latest.md
index dd4ebed..2864a46 100644
--- a/docs/adr/0010-dropwizard-track-latest.md
+++ b/docs/adr/0010-dropwizard-track-latest.md
@@ -1,4 +1,4 @@
-# 10. Track latest Dropwizard rather than pin to 4.x
+# 10. Latest Dropwizard release line over a 4.x pin
Date: 2026-05-15
diff --git a/docs/adr/0011-spring-boot-4.md b/docs/adr/0011-spring-boot-4.md
index dc4d11f..2f81393 100644
--- a/docs/adr/0011-spring-boot-4.md
+++ b/docs/adr/0011-spring-boot-4.md
@@ -1,4 +1,4 @@
-# 11. Spring Boot 4 / Spring Security 7 for the Spring starter
+# 11. Spring Boot 4 and Spring Security 7
Date: 2026-05-16
diff --git a/docs/adr/0015-stateful-access-tokens.md b/docs/adr/0015-stateful-access-tokens.md
index f71d111..f857360 100644
--- a/docs/adr/0015-stateful-access-tokens.md
+++ b/docs/adr/0015-stateful-access-tokens.md
@@ -93,10 +93,9 @@ Adapter wiring:
- Con: the store holds one row per issued token. Hosts that issue at high volume (millions per day)
and do not need fast revocation should stay with `RevocationCheck` and a small deny-list. The
library does not auto-detect this, and the operator picks the binding.
-- Con: the `ttl` field on `JwtClaims` is not yet a first-class device-id channel. When stateful,
- `record(...)` always passes `Optional.empty()` for `deviceId`. A future ADR (when refresh tokens
- land) extends this surface to bind issued JWTs to a refresh family or device for "log out this
- device" granularity.
+- Con: `JwtClaims` carries no device identifier, so `PkAuthJwtIssuer` always passes
+ `Optional.empty()` for `deviceId` to `record(...)`. Binding issued JWTs to a refresh family or
+ device, for "log out this device" granularity, is left to a future ADR.
- Con: the validator's hot path now includes a store lookup. For the noop case this is a
hash-lookup-then-return, and for JDBI it is one indexed query per validate. Adopters needing the
absolute lowest validation latency (for example CDN-near edge validation) should benchmark before
diff --git a/docs/adr/0016-user-deletion-fan-out.md b/docs/adr/0016-user-deletion-fan-out.md
index c727af9..c81cffd 100644
--- a/docs/adr/0016-user-deletion-fan-out.md
+++ b/docs/adr/0016-user-deletion-fan-out.md
@@ -1,4 +1,4 @@
-# 16. User deletion fan-out is sequential and best-effort
+# 16. Sequential best-effort user deletion fan-out
Date: 2026-05-16
diff --git a/docs/adr/0018-remove-sonarqube-cloud.md b/docs/adr/0018-remove-sonarqube-cloud.md
index b0dd806..d7c20d8 100644
--- a/docs/adr/0018-remove-sonarqube-cloud.md
+++ b/docs/adr/0018-remove-sonarqube-cloud.md
@@ -1,4 +1,4 @@
-# 18. Remove SonarQube Cloud; enforce coverage with native JaCoCo line + branch gates
+# 18. Native JaCoCo coverage gates replacing SonarQube Cloud
Date: 2026-06-11
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 1227e29..ac19b3d 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -8,23 +8,23 @@ ADR is added and the prior one is marked `Superseded by NNNN`. See
| # | Status | Date | Title |
|---|---|---|---|
-| [0001](./0001-record-architecture-decisions.md) | Accepted | 2026-05-12 | Record architecture decisions |
+| [0001](./0001-record-architecture-decisions.md) | Accepted | 2026-05-12 | Architecture decision records |
| [0002](./0002-webauthn4j-over-yubico.md) | Accepted | 2026-05-13 | WebAuthn4J over Yubico java-webauthn-server |
| [0003](./0003-jdbi-over-jpa.md) | Accepted | 2026-05-14 | JDBI over JPA |
-| [0004](./0004-dagger-for-dropwizard.md) | Accepted | 2026-05-14 | Use Dagger 2 for the Dropwizard adapter's DI |
+| [0004](./0004-dagger-for-dropwizard.md) | Accepted | 2026-05-14 | Dagger 2 for Dropwizard adapter wiring |
| [0005](./0005-stateless-jwt-default.md) | Accepted | 2026-05-14 | Stateless JWT as the default post-ceremony credential |
-| [0006](./0006-userlookup-spi-not-owned.md) | Accepted | 2026-05-15 | `UserLookup` is an SPI, not an owned table |
+| [0006](./0006-userlookup-spi-not-owned.md) | Accepted | 2026-05-15 | `UserLookup` as a host-implemented SPI |
| [0007](./0007-dynamodb-local-vs-localstack.md) | Accepted | 2026-05-14 | DynamoDB Local over LocalStack for integration tests |
-| [0008](./0008-dynamodb-single-table-design.md) | Accepted | 2026-05-14 | DynamoDB single-table design for auth items, separate users table |
+| [0008](./0008-dynamodb-single-table-design.md) | Accepted | 2026-05-14 | DynamoDB single-table design with a separate users table |
| [0009](./0009-jackson-3-over-jackson-2.md) | Accepted | 2026-05-13 | Jackson 3 (`tools.jackson`) over Jackson 2 (`com.fasterxml.jackson`) |
-| [0010](./0010-dropwizard-track-latest.md) | Accepted | 2026-05-15 | Track latest Dropwizard rather than pin to 4.x |
-| [0011](./0011-spring-boot-4.md) | Accepted | 2026-05-16 | Spring Boot 4 / Spring Security 7 for the Spring starter |
+| [0010](./0010-dropwizard-track-latest.md) | Accepted | 2026-05-15 | Latest Dropwizard release line over a 4.x pin |
+| [0011](./0011-spring-boot-4.md) | Accepted | 2026-05-16 | Spring Boot 4 and Spring Security 7 |
| [0012](./0012-micronaut-4.md) | Accepted | 2026-05-16 | Micronaut 4 for the Micronaut adapter |
| [0013](./0013-refresh-tokens-family-rotation.md) | Accepted | 2026-05-16 | Rotating refresh tokens with family-based replay detection |
| [0014](./0014-per-audience-ttl-policy.md) | Accepted | 2026-05-16 | Per-audience JWT TTL via a TokenTtlPolicy SPI |
| [0015](./0015-stateful-access-tokens.md) | Accepted | 2026-05-16 | Stateful access tokens via AccessTokenStore SPI |
-| [0016](./0016-user-deletion-fan-out.md) | Accepted | 2026-05-16 | User deletion fan-out is sequential and best-effort |
+| [0016](./0016-user-deletion-fan-out.md) | Accepted | 2026-05-16 | Sequential best-effort user deletion fan-out |
| [0017](./0017-sonarqube-cloud-static-analysis.md) | Superseded by [0018](./0018-remove-sonarqube-cloud.md) | 2026-06-11 | SonarQube Cloud for static analysis and coverage tracking |
-| [0018](./0018-remove-sonarqube-cloud.md) | Accepted | 2026-06-11 | Remove SonarQube Cloud; enforce coverage with native JaCoCo line + branch gates |
+| [0018](./0018-remove-sonarqube-cloud.md) | Accepted | 2026-06-11 | Native JaCoCo coverage gates replacing SonarQube Cloud |
| [0019](./0019-crypto-agility-post-quantum-readiness.md) | Accepted | 2026-06-17 | Crypto-agility and post-quantum readiness for passkey algorithms |
| [0020](./0020-browser-sdk-testing-strategy.md) | Accepted | 2026-06-18 | Browser SDK testing strategy |
diff --git a/site/index.html b/site/index.html
index a574d29..a3c0d18 100644
--- a/site/index.html
+++ b/site/index.html
@@ -328,8 +328,8 @@
.wordmark .mark .dot { fill: var(--accent); }
.wordmark .mark .ring { stroke: var(--accent); }
.wordmark .mark { width: 26px; height: 26px; display: inline-block; }
- .wordmark strong { font-weight: 600; font-variant: small-caps; letter-spacing: 0.16em; }
- .wordmark em { font-style: italic; color: var(--ink-muted); font-size: 13px; letter-spacing: 0.06em; }
+ .wordmark .wordmark-name { font-weight: 600; font-variant: small-caps; letter-spacing: 0.16em; }
+ .wordmark .wordmark-tag { color: var(--ink-muted); font-size: 13px; letter-spacing: 0.06em; }
.nav-wrap { display: flex; align-items: center; gap: 22px; }
nav.primary { display: flex; gap: 26px; align-items: center; }
@@ -478,7 +478,6 @@
font-optical-sizing: auto;
}
h2 { font-size: clamp(34px, 4.2vw, 54px); margin-bottom: var(--s5); max-width: 18ch; }
- h2 em { font-style: italic; color: var(--accent-text); }
h3 { font-size: 23px; margin-bottom: var(--s4); letter-spacing: -0.01em; }
h4 {
font-size: 16px; font-variant: small-caps; letter-spacing: 0.14em;
@@ -545,7 +544,7 @@
animation: rise .9s .4s ease-out backwards;
}
.framework-chips span { display: inline-flex; align-items: center; gap: 8px; }
- .framework-chips i {
+ .framework-chips .marker {
width: 6px; height: 6px; background: var(--brass);
display: inline-block; transform: rotate(45deg);
}
@@ -654,9 +653,9 @@
font-family: var(--mono); font-size: 12px; color: var(--ink-muted);
content: attr(data-idx); letter-spacing: 0.1em;
}
- .boundary-col li b { display: block; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
- .boundary-col li i.gloss { color: var(--ink-muted); font-style: italic; font-weight: 400; }
- .boundary-col--isnt li b {
+ .boundary-col li .item-label { display: block; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
+ .boundary-col li .gloss { color: var(--ink-muted); font-weight: 400; }
+ .boundary-col--isnt li .item-label {
color: var(--accent-text); text-decoration: line-through;
text-decoration-color: color-mix(in srgb, var(--accent) 45%, transparent);
text-decoration-thickness: 1px;
@@ -773,9 +772,9 @@
text-align: right; border-right: 1px solid var(--rule); padding-right: 16px;
white-space: nowrap;
}
- .step-num b {
+ .step-num .step-index {
display: block; font-family: var(--serif); font-size: 42px; color: var(--accent-text);
- font-style: italic; font-weight: 400; letter-spacing: 0; margin-bottom: 8px;
+ font-weight: 400; letter-spacing: 0; margin-bottom: 8px;
font-variant-numeric: lining-nums;
}
.step-body h3 { margin-bottom: 8px; }
@@ -803,7 +802,6 @@
margin-top: var(--s5); font-size: 14.5px; color: var(--ink-muted);
line-height: 1.6; max-width: 60ch;
}
- .try-caption strong { color: var(--ink); font-weight: 600; }
/* ============== MODULES ============== */
.module-intro {
@@ -816,10 +814,10 @@
display: flex; gap: 22px; flex-wrap: wrap; align-self: end; padding-bottom: 6px;
}
.module-key span { display: inline-flex; align-items: center; gap: 8px; }
- .module-key i { width: 10px; height: 10px; display: inline-block; transform: rotate(45deg); }
- .module-key .req i { background: var(--accent); }
- .module-key .pick i { background: var(--brass); }
- .module-key .opt i { background: var(--patina); }
+ .module-key .marker { width: 10px; height: 10px; display: inline-block; transform: rotate(45deg); }
+ .module-key .req .marker { background: var(--accent); }
+ .module-key .pick .marker { background: var(--brass); }
+ .module-key .opt .marker { background: var(--patina); }
.module-groups { display: grid; gap: 0; }
.module-group {
@@ -859,7 +857,7 @@
.module-group.g-opt .module-card { border-left-color: var(--patina); }
.module-card-top { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; }
.module-card-name { font-family: var(--mono); font-size: 13.5px; color: var(--ink); letter-spacing: -0.01em; }
- .module-card-name b { color: var(--accent-text); font-weight: 500; }
+ .module-card-name .group-id { color: var(--accent-text); font-weight: 500; }
.module-card .tag {
font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
text-transform: uppercase; color: var(--ink-muted); white-space: nowrap; flex-shrink: 0;
@@ -913,7 +911,6 @@
font-family: var(--serif); font-size: 15px; color: var(--ink-soft); line-height: 1.6;
}
.wire-note + .wire-note { margin-top: 14px; border-left-color: var(--patina); }
- .wire-note strong { color: var(--accent-text); font-weight: 600; }
.wire-note code { font-size: 12.5px; }
/* ============== SECURITY ============== */
@@ -955,7 +952,6 @@
}
.maturity p { font-size: 15.5px; color: var(--ink-soft); line-height: 1.6; max-width: 78ch; }
.maturity p + p { margin-top: 12px; }
- .maturity strong { color: var(--accent-text); font-weight: 600; }
/* ============== FOOTER ============== */
footer.site {
@@ -1011,7 +1007,7 @@
text-align: left; border-right: 0; padding-right: 0; padding-bottom: 4px;
border-bottom: 1px solid var(--rule); padding-top: 0;
}
- .step-num b { display: inline; margin-right: 14px; font-size: 34px; }
+ .step-num .step-index { display: inline; margin-right: 14px; font-size: 34px; }
.footer-grid { grid-template-columns: 1fr; }
.code { font-size: 12px; padding: 22px 18px; }
.hero h1 .rule { display: none; }
@@ -1037,8 +1033,8 @@