Skip to content

docs(oidc): document fallback claim lists for acl.oidc.sub.claim and acl.oidc.groups.claim - #575

Open
glasstiger wants to merge 33 commits into
mainfrom
feat/qwp-entra-token-provider
Open

glasstiger wants to merge 33 commits into
mainfrom
feat/qwp-entra-token-provider

Conversation

@glasstiger

@glasstiger glasstiger commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Documents questdb/questdb-enterprise#1267: acl.oidc.sub.claim and acl.oidc.groups.claim now accept a comma-separated list of claim names in priority order, such as preferred_username,oid and roles,groups.

  • Page split: the OIDC page moves to security/oidc/index.mdx, and the Microsoft Entra ID and PingFederate guides move from it to their own pages, security/oidc/entra-id.mdx and security/oidc/pingfederate.mdx, under a new OpenID Connect (OIDC) sidebar category. The overview stays at /docs/security/oidc/, and the #active-directory, #microsoft-entraid and #pingfederate anchors stay on it as stubs that link to the new pages, so that existing links to them keep working. This includes the questdb.io redirects of the old guide URLs, until questdb/questdb.io#3041 points them at the new pages.
  • OIDC guide: new User and group claims section. It covers:
    • how QuestDB reads the principal and the groups from the user information, how to choose the principal claim, and that username and password (ROPC) logins use the username that the client sends as the principal;
    • the fallback rules: list order wins over token order, missing, null and empty claims fall through, null and empty group names are skipped, groups are never combined across claims, only top-level claims count, names are case-sensitive, listed claims must have the expected shape, and unlisted claims are ignored whatever their shape;
    • the startup validation, with the exact error messages.
  • OIDC guide, other new sections:
    • Token validation: what QuestDB checks in a JWT, and that with the User Info endpoint it checks neither the audience nor the client of a token. Its When QuestDB stops accepting a token subsection is the one place for how long QuestDB accepts a token after it expires and what outlives the token. It also covers the Web Console: it requests new tokens shortly before the access token expires but sends the ID token, so with Entra ID's default token lifetimes it keeps sending an expired ID token, which 4.0.2 logs as ExpiredSignature and accepts through the session;
    • Troubleshooting OIDC logins: the log lines of rejected logins, such as Failed to find required claims [subClaims=..., groupsClaims=...], and what ExpiredSignature means;
    • Versions before 4.0.2: how earlier versions differ, and what to check when you upgrade, including the ExpiredSignature errors of Web Console users with Entra ID;
    • Tables created by external users: ingestion fails when it has to create a table or add a column for an external user, and SQL DDL needs OWNED BY with one of the user's QuestDB groups;
    • Group changes, sessions, and revoking access: when a change in the identity provider takes effect, that open connections and Web Console sessions keep the groups of the last token that QuestDB accepted, and how to cut off access immediately;
    • Token lifetime, under Non-interactive clients: how a client with a fixed token behaves as the token expires, and how to switch a long-running client to a new token;
    • a minimal server.conf under Configuration options, right after the introduction.
  • Mapping user permissions: corrected. A missing or empty groups claim rejects the login. "Authenticated without permissions" applies only when none of the user's groups is mapped. Also lists the endpoint permission of each interface, with a GRANT example, and states that external aliases must match the group names exactly.
  • Non-interactive clients: which token to send with each value of acl.oidc.groups.encoded.in.token, tokens that services obtain for themselves, and that the setting also changes the token that the Web Console sends. The examples, a client credentials grant and two password grant variants, ingest into fx_trades over QWP.
  • Enable ROPC: the basic authentication example now imports parse and json, which it used without importing.
  • Entra ID guide:
    • preferred_username instead of the display name as the principal claim, a single-tenant registration, the Token configuration settings in the text, and a note on the ExpiredSignature errors that 4.0.2 logs for Web Console users;
    • new section on accepting managed identity and service principal (app-only) tokens alongside user logins: the preferred_username,oid / roles,groups configuration, v2.0 access tokens (requestedAccessTokenVersion: 2) so that aud matches acl.oidc.audience, one app role and one QuestDB group per service, mapped with CREATE GROUP ... WITH EXTERNAL ALIAS, restricting access to app roles, Python examples that ingest over QWP and renew the token, and how to cut off one service with REVOKE.
  • PingFederate guide: adds the QuestDB configuration.
  • Configuration reference:
    • list syntax and the startup validation for both claim settings, and the principal of ROPC logins;
    • acl.oidc.groups.claim now shows no default, because it is required when OIDC is enabled (this was true before #1267 as well);
    • acl.oidc.pkce.enabled renamed to acl.oidc.pkce.required, the name the server reads, with a note that the old name is ignored;
    • new entries for acl.oidc.state.required and acl.oidc.public.keys.expiry, and token validation in acl.oidc.audience, acl.oidc.cache.ttl and acl.oidc.groups.encoded.in.token, including that acl.oidc.audience is not used with the User Info endpoint.
  • RBAC: the HTTP permission covers QWP over WebSocket, and the users and authentication sections describe external users.
  • Clients: the C/C++, .NET, Go and Python pages link to Non-interactive clients and to the Entra ID section, say to build a new client before the token expires, then close the old one, and state the same condition for when QuestDB rejects an expiring token on new connections: QuestDB Enterprise 4.0.2 and later, or the User Info endpoint. The Python page also corrects when bad credentials raise, and that a terminal server rejection raises QuestDBServerRejectionError from flush(wait=True), now described in its own Flushing and acknowledgements section.
  • SQL and ILP: CREATE TABLE, ALTER TABLE ADD COLUMN and the ILP overview explain what external users need.
  • Changelog: October 2026 entries.

Dependencies

Follow-ups

  • questdb/questdb.io#3041: once this is live, merge it to point the redirects of /docs/guides/microsoft-entraid-oidc/ and /docs/guides/active-directory-pingfederate/ at /docs/security/oidc/entra-id/ and /docs/security/oidc/pingfederate/ instead of the stubs.
  • Web Console: it requests new tokens on the access token's expiry but sends the ID token when the groups are in the token. Refreshing on the ID token's exp instead would remove the ExpiredSignature errors, and the notes about them in this PR.

…acl.oidc.groups.claim

acl.oidc.sub.claim and acl.oidc.groups.claim accept a comma-separated list
of claim names in priority order (questdb/questdb-enterprise#1267).

- OIDC guide: new "User and group claims" section covering fallback claim
  lists, the rules for missing, null and empty claims, the startup
  validation and the "Failed to find required claims" log message.
- Entra ID guide: accepting managed identity and service principal
  (app-only) tokens alongside user logins.
- Configuration reference: claim list syntax for both settings;
  acl.oidc.groups.claim has no default and is required with OIDC enabled.
- Configuration reference: rename acl.oidc.pkce.enabled to
  acl.oidc.pkce.required, the name the server reads.
- Changelog: October 2026 entries.
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🚀 Build success!

Latest successful preview: https://preview-575--questdb-documentation.netlify.app/docs/

Commit SHA: 0208d13

📦 Build generates a preview & updates the link on each commit.

glasstiger and others added 24 commits October 8, 2026 15:38
…setup

- Require a single-tenant QuestDB app registration for app-role mapping:
  token mode checks the signature, aud and exp, but not the issuer or tenant
- Add 4.0.2 warnings to the claims section, the Entra ID subsection and the
  configuration reference, including the pre-4.0.2 requirement for a groups
  array in token mode
- Link the non-interactive clients section and the claim examples to the
  Entra ID subsection, and label the example payloads as Entra ID tokens
- Cover app role assignment, managed identity token caching, the HTTP and
  INSERT grants, the token scope, and token refresh for services
- Document token-mode validation (kid, aud, exp, non-empty sub) and its log
  messages under Rejected logins, and add sub to the app-only payload
- Describe token mode as reading the JWT the client presents, not only the
  ID token
- App-only tokens can carry a groups claim, so list roles before groups
  (acl.oidc.groups.claim=roles,groups) and reject only tokens with
  neither a role nor a group
- State that the principal must be unique and use preferred_username
  for Entra ID users instead of the display name
- List every behavior that is new in QuestDB Enterprise 4.0.2,
  including exp validation, and gate the config page statements
- Add a Token validation section and extend Rejected logins
- Reorder the Entra app-only setup into steps, select a single-tenant
  account type at registration, and rewrite the Python example with
  the current client over TLS
- Correct the acl.oidc.pkce.enabled note, file the new sections under
  New in the changelog, and link the managed identity section from the
  Entra intro and the Azure deployment page
…he Entra app-only guide

- An app-only token without a role falls back to the groups claim. Document
  it, and how to keep app roles the only way in: keep services out of mapped
  security groups, or enable Assignment required.
- QuestDB cannot create a table on ingestion for an external user. Replace
  the CREATE TABLE advice with creating tables in advance, and link OWNED BY.
- Promote the managed identity guide to an H3 with step H4s, keeping its
  anchor. Add prerequisites, the PGWIRE requirements, rejection handling, a
  token renewal example for long-running services, and a verify step.
- With acl.oidc.groups.encoded.in.token=true, the Web Console and ROPC use
  the ID token. Correct steps 7-8, non-interactive clients and the
  configuration reference.
- Move the claim rules and the pre-4.0.2 differences into their own
  sections. Document that exp is required, and that a token can be accepted
  for up to 60 seconds plus acl.oidc.cache.ttl after it expires.
- Configuration reference: single audience, what acl.oidc.cache.ttl caches,
  and links to the claim rules.
- RBAC: QWP uses the HTTP endpoint permission, and OIDC authenticates
  external users and services.
- Spell Entra ID consistently, and update the changelog.
Point the OIDC links of the Python, .NET, C and C++, Go and QWP pages at
the non-interactive clients section of the OIDC guide, and the Python page
at the Entra ID managed identity example.
…he Entra app-only guide

- Principal claim: add "Choose the principal claim" with the oid versus
  preferred_username trade-off. Entra ID documents preferred_username as
  mutable and reusable, so the Entra guide and the configuration reference
  no longer call it unique, and say when to use oid.
- External users cannot create a table or add a column on ingestion. State
  it for every OIDC user in "Tables created by external users", note that
  the non-interactive examples need the table to exist, and create
  fx_trades in the managed identity guide.
- Step 7: in token mode the Web Console sends the ID token, a readable JWT,
  so replace the "opaque token" reassurance with a TLS note.
- Managed identity guide: open with what and why, the 4.0.2 warning and the
  prerequisites. Move Assignment required into its own step, and require
  tenant-wide admin consent with it.
- Make Token validation, Troubleshooting OIDC logins and Versions before
  4.0.2 their own sections, add an upgrade checklist, rename "Active
  Directory" to "Identity provider guides" with its anchor kept, and link
  the managed identity guide from the intro.
- Make the token expiry wording consistent, document that the Web Console's
  HTTP session is not tied to the token, and state that a token whose aud
  list contains acl.oidc.audience is accepted.
- Renewal example: connect with the new token before closing the old
  handle, and keep both when Entra ID or QuestDB cannot be reached.
- Client pages, failover and RBAC: say which token to send, add QWP to the
  second endpoint table, and describe external users.
Split the Microsoft Entra ID and PingFederate guides out of the OIDC page
into security/oidc-entra-id and security/oidc-pingfederate, grouped under
OpenID Connect (OIDC) in the sidebar. Update links from the client,
configuration, deployment and SQL reference pages, document
acl.oidc.public.keys.expiry and acl.oidc.state.required, and correct when
the Python client raises on bad credentials.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…claim lists on 4.0.2

- Entra app-only guide: a missing INSERT grant or a type mismatch is a
  terminal rejection, so flush(wait=True) raises
  QuestDBServerRejectionError as well as calling the error handler. Say
  that the renewal example stops on it. python.md said that the wait never
  raises on a server rejection; correct it there too.
- Mapping user permissions and Token validation: QuestDB keeps the groups
  of the last token that it accepted. A rejected token falls back to the
  Web Console's session cookie, the Web Console's logout ends the session,
  and open connections outlive their token. Revoking the QuestDB group's
  permissions is the way to cut off access at once, also for a service
  whose app role was removed.
- External users: ingestion fails when it has to create a table or add a
  column, QuestDB rolls back the table but keeps the column, and
  Troubleshooting lists the errors that the client and the log show.
- Claim lists: state the 4.0.2 requirement in Fallback claim lists,
  Non-interactive clients, the Entra Configure QuestDB step and RBAC, and
  scope the expired-token grace period to 4.0.2.
- Non-interactive clients: acl.oidc.groups.encoded.in.token=true also
  makes the Web Console send the ID token, which must then carry the
  groups.
- Add a minimal server.conf to the OIDC page and the QuestDB
  configuration to the PingFederate guide.
- Logs can hold rejected tokens, token responses and claims: drop the
  "guaranteed" wording, warn in the token note, and add the Invalid token
  format error to Troubleshooting.
- Entra guide: describe the Token configuration settings in the text, and
  say that app-only tokens can, not always, carry a groups claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…en lifetime

- ROPC logins: the principal is the username that the client sends, not
  the claim that acl.oidc.sub.claim names, and group changes take effect
  after acl.oidc.cache.ttl. Say so in User and group claims, Choose the
  principal claim, the sub.claim and cache.ttl reference entries, and both
  provider guides.
- Token validation: with acl.oidc.groups.encoded.in.token=false, QuestDB
  checks neither the audience nor the client of a token, and
  acl.oidc.audience does not apply. Note it in the audience reference too.
- Non-interactive clients: add a client credentials example over QWP,
  move the password grant examples from ILP/HTTP and questdb.ingress to
  QWP and fx_trades, use the client ID questdb in every example, and add
  Token lifetime, the provider-neutral rules for expiring tokens that the
  client pages link to.
- Mapping user permissions: list the endpoint permission of each
  interface with a GRANT example, and state that external aliases match
  exactly. Move group changes, sessions and revocation to their own
  section, and put the minimal configuration right after the intro.
- python.md: connect() is eager by default everywhere on the page, and a
  terminal rejection raises from flush(wait=True). Move the barrier rules
  out of Value types into Flushing and acknowledgements.
- C/C++, .NET and Go clients: build the new client before the token
  expires, then close the old one.
- Entra guide: one app role and one QuestDB group per service, with
  QuestDB.FxIngest as the example, and REVOKE statements to cut a service
  off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g grants

- Token rotation: with sf_dir, two clients cannot hold the same
  store-and-forward slot, so building the new client before closing the
  old one fails. Token lifetime, the Entra renewal example and the
  Python, Go, C/C++ and .NET pages now say to flush and close the old
  client first, then reuse the sender_id so that the new client replays
  the slot. .NET: call SendAsync() before DisposeAsync(), which does not
  send.
- C/C++, .NET and Go: the client sends the token on every connection
  that it opens, and QuestDB rejects an expired token on new connections
  only on 4.0.2 and later or with the User Info endpoint, shortly after
  it expires.
- Non-interactive clients: create fx_trades and grant HTTP and INSERT to
  the client's group before the ingestion examples.
- Entra guide: emitting groups as role claims removes the app roles from
  the roles claim; it does not mix them.
- Changelog: add Python and the sf_dir order to the token rotation entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… findings

- Entra ID guide: with sf_dir, renew() never switches tokens, because the
  old handle holds the store-and-forward slot. Say so, and add a variant
  that closes the old handle first, then retries the connection with a
  current token instead of reusing the closed handle. Token lifetime
  states the retry rule for every client.
- Entra ID guide: QuestDB rejects a login without a groups claim. Drop the
  statement that users authenticate without customized tokens.
- Title the provider guides "Microsoft Entra ID (Azure AD) OIDC setup" and
  "PingFederate OIDC setup", and add OIDC and Azure AD to the Entra ID
  description.
- OIDC page: add "Where QuestDB reads the user information", comparing the
  User Info endpoint with reading the claims from the token, and add
  acl.oidc.groups.encoded.in.token=true to the fallback claim list
  snippets. The minimal configuration says Entra ID needs it.
- Document that QuestDB downloads the discovery document at startup and
  does not start without it, that with acl.oidc.host it starts while the
  provider is unreachable, and that the two settings cannot be combined.
- C/C++ client: the pool connects eagerly by default, as the 7.0.0 headers
  say; lazy_connect=on defers the errors to the first borrow.
- Changelog entries for the above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ired Web Console ID tokens

- Move the OIDC overview and the Entra ID and PingFederate guides to
  security/oidc/{index,entra-id,pingfederate}.mdx, the paths that #522
  uses, so neither PR moves a published URL again. The overview stays at
  /docs/security/oidc/.
- Add "When QuestDB stops accepting a token" under Token validation as
  the one place for the expiry window, and link to it from Token
  lifetime, Group changes and the Entra guide instead of restating it.
- Document that the Web Console refreshes on the access token's expiry
  but sends the ID token when the groups are in the token, so with Entra
  ID's default lifetimes 4.0.2 logs ExpiredSignature and accepts the
  requests through the session. Covered in the new section, step 6,
  Troubleshooting, When you upgrade, and the Entra guide.
- Give the Python client page the same 4.0.2 / User Info endpoint
  qualifier as the C/C++, .NET and Go pages.
- Fix the Enable ROPC example, which used parse and json without
  importing them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Wire and claim order

- Token lifetime and the Python, C/C++ and Go pages: wait until QuestDB
  has acknowledged what the old client sent before closing it, because a
  closed client discards the rows that are not acknowledged within
  close_flush_timeout_millis.
- Web Console: it refreshes its tokens before a query only when it holds
  a refresh token, and logs the user out when the provider refuses.
  Without a refresh token, a disabled user keeps working through the
  session. Document offline_access in the minimal configuration and in
  acl.oidc.scope.
- PGWire: which token to send in each mode, that the principal and the
  groups come from the token, the setting as a server.conf block, and TLS.
- Fallback claim lists: a user token that carries roles takes its groups
  from roles alone, so put the Keycloak realm-role mapper on the client of
  the service only. Entra guide: user-assignable app roles do the same.
- sf_dir: a terminally rejected batch stays in the slot and is sent again
  before later batches, so don't send it again (Entra guide, Python page).
  Describe what an ingesting service sees during a revoke.
- Entra guide: finish the setup before a managed identity requests its
  first token, because Azure caches its tokens for about 24 hours.
- PingFederate: the LDAP source is Microsoft Entra Domain Services or
  Active Directory, not Entra ID. Keep the old data source anchor, and fix
  userPrincipalName.
- Rust client: connect is eager by default, as in the 7.x core, and
  lazy_connect=on defers the errors.
- Changelog entries for the above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… renewal and Java token providers

Align the OIDC, RBAC and client pages with questdb/questdb-enterprise#1267,
questdb/questdb#7799, questdb/ui#603 and questdb/java-questdb-client#102.

- Issuer validation (QuestDB Enterprise 4.0.2): acl.oidc.issuer and
  acl.oidc.issuer.validation.enabled, a new "The expected issuer" section
  with where the issuer comes from, the issuers of Entra ID v1.0 and v2.0
  tokens, the common/organizations templates, issuers that depend on the
  host name of the request, the opt-out and its advisory, the
  InvalidIssuer and MissingRequiredClaim("iss") log lines, and the
  upgrade notes. acl.oidc.host needs acl.oidc.issuer unless validation
  is disabled. PGWire tokens need the expected issuer too.
- HTTP sessions: a request with a credential that QuestDB rejects, or
  cannot check because the provider is unreachable, gets 401 and ends
  its session. Sessions end with the password or REST token that opened
  them, or when the user is disabled or dropped, and on a replica when
  they are next used. acl.oidc.cache.ttl reuses only the check of a
  password that QuestDB verifies itself, even with OIDC disabled.
- Provider outages: with the User Info endpoint, Web Console users are
  logged out once acl.oidc.cache.ttl has passed; with tokens validated
  locally, once the ID token expires; ROPC logins need the token
  endpoint in both modes.
- Web Console: it renews the ID token by its exp claim, keeps its token
  while it cannot reach the provider, and without a refresh token logs
  the user out when the token of the login expires. Remove the stale
  "logged out about an hour after the login" text from the OIDC page
  and the Entra ID guide; Entra ID limits the refresh tokens of
  single-page applications to 24 hours. Its password login sends a REST
  token that survives a password change, so drop the user's REST tokens
  to log the user out.
- Java client: "Rotating tokens" with token_provider=azure, the
  questdb-client-azure artifact, RefreshingTokenProvider and how token
  failures are handled, "Connection health", the token provider keys in
  the connect string reference, and a Java example in the Entra ID
  guide.
- With the User Info endpoint, QuestDB treats the access token as opaque.
- Changelog entries for the above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…grade notes and Java startup

- Troubleshooting: QuestDB does not log the token of a request without a
  JWT, so correct that log line. Add MissingRequiredClaim("exp"),
  InvalidSignature, Sub is not set in token and a missing key ID, state
  the full check order, and split the section into subsections.
- The expected issuer: acl.oidc.issuer for a provider reached through an
  internal URL fixes token validation only, because the Web Console uses
  the endpoints of the same discovery document, so recommend a fixed
  public hostname. Add acl.oidc.host and opt-out examples.
- Web Console: one Web Console token renewal section for the console side
  and When QuestDB stops accepting a token for the server side, replacing
  the copies in step 6, Group changes, troubleshooting, the upgrade notes,
  the Entra ID guide and acl.oidc.scope. Add the missing 4.0.2
  qualifiers, the renewal and resend after a 401, and the logout after
  the token has expired.
- Upgrade: a warning after the introduction, and one Upgrade to 4.0.2 list
  that gives, for each change, the earlier behaviour and what to do.
- Define token mode and User Info mode, shorten the introduction, correct
  the PingFederate stub, the client credentials scope example and the
  principal claim guidance, and add dedup notes where replay is
  recommended.
- Java client: token providers and connection health need Java client
  1.3.10 or later; lazy_connect starts a QuestDB handle while the identity
  provider is down; a connected sender retries authentication failures
  with any credential. Correct the error policies and the error handler of
  the QuestDB handle, and the connection health example. Document the
  checked exceptions of a TokenSource, getLastFailure(), the wait limits,
  AzureTokenSource and orphan drainers.
- connect-string: the Java exception to terminal authentication failures
  on reconnect, the lazy_connect conflicts, and reconnect_* promotion for
  standalone senders only in the C, C++, Python and Rust clients.
- rust.md: token rotation, lazy_connect conflicts and reconnect_* promotion.
- Document http.session.timeout. State the owner that an external user
  names in CREATE VIEW, CREATE LIVE VIEW, CREATE MATERIALIZED VIEW and
  CREATE SERVICE ACCOUNT. Shorten the changelog to one line per change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@glasstiger

Copy link
Copy Markdown
Contributor Author

Level-2 review: approve-with-nits

Reviewed documentation commit 76062c0d56b6fdf587e425bd4985820a8d6e7350 against the intended post-merge behavior of all four implementation PRs, using frozen source snapshots rather than currently released behavior or PR descriptions:

The level-2 pass included the change-surface map, six reviewer passes, independent source verification, production build, plugin tests, and generated-output checks.

Blocking issues

None confirmed. No introduced Critical or Major issue survived verification.

Non-blocking recommendations

1. Minor: Document the ID-token requirement during refresh

Location: documentation/security/oidc/index.mdx:1508–1523

The new Web Console renewal section explains provider refusals and transient failures, but omits another logout condition: in token mode, a successful refresh response must include an id_token. If it returns only an access token, the UI logs out rather than retaining the previous ID token.

Suggested fix: State this requirement and document the “Missing ID token” symptom and provider-configuration remedy.

Verified against UI #603’s src/providers/AuthProvider.tsx:174–195, getMissingIdTokenError() in src/modules/OAuth2/utils.ts:140–150, and the corresponding refresh regression test.

2. Minor: Prefer server-verified TLS in the PGWire bearer-token example

Location: documentation/security/oidc/index.mdx:934–936

sslmode=require guarantees encryption, but does not require hostname verification. Prefer sslmode=verify-full and explain trusted-CA/sslrootcert configuration, or explicitly distinguish encryption-only setup from production server-identity verification.

Non-blocking nuance: This PR improves the previous example, which did not require TLS. It does not incorrectly claim that require verifies the hostname. The remaining hardening opportunity is confirmed by libpq’s SSL documentation.

What works

  • The OIDC split preserves the overview URL and legacy provider-step anchors, with clear sidebar entries and inbound links.
  • Java guidance correctly distinguishes startup, query-pool prewarm, lazy connection, established-sender retry, and bounded orphan quarantine.
  • JWT validation, claim fallback, credential-bound HTTP sessions, and external-user ownership guidance agree with the implementation snapshots.
  • The new drain() examples correctly distinguish an all-published acknowledgement barrier from an explicit flush returning FSN -1.
  • Generated Markdown resolves matching Java core/Azure dependency versions and expands the deduplication warning.
  • The changelog covers the user-facing changes.

Validation

  • yarn build: passed
  • Changed plugin tests: 25/25 passed, using node --test plugins/raw-markdown/convert-components.test.cjs plugins/fetch-java-client-release/index.test.cjs
  • Generated OIDC overview/Entra/PingFederate routes, selected legacy anchors, raw Markdown, and llms.txt: checked successfully.

Live Azure/PingFederate provisioning, browser authentication, client integration examples, and SQL execution were not exercised. Product behavior was checked against source; implementation tests were inspected, not executed. No documentation files were edited during the review.

Downgraded findings

Two recommendations retained; 20 unique candidates excluded after verification and deduplication. Exclusion does not mean that pre-existing concerns are technically false.

  • The adversarial pass found genuine pre-existing debt, including System.nanoTime() used as an epoch timestamp, uncorrelated read-after-write polling, ambiguous SQL write replay, periodic-durability wording, query acquisition/status/recovery descriptions, callback-threading contradictions, the DELETE result-count claim, notebook charset handling, delegated-permission wording, ROPC/MFA prerequisites, PingFederate mapping instructions, and the early architecture summary. These were not introduced by this PR.
  • Two navigation inconsistencies also predate the change: direct-query versus facade lazy-start wording, and the HA configuration page’s description of ingress handling of target.
  • The one-shot pass’s unresolved dependency versions and missing deduplication warning were source-only artifacts: both render correctly.
  • Requirements for a complete service framework and zero-loss acceptance contract exceeded what the client reference promises. System-assigned identity selection is explained behind its explicit setup link; lazy startup and health diagnostics are documented.

@glasstiger

Copy link
Copy Markdown
Contributor Author

Verdict: approve-with-nits

Completed a level-2 documentation review, including all six review passes, at commit 9a71cf2cc0af111d0f49e1109ae09465fdb1b91a. This assesses the intended behavior after all four implementation PRs merge, rather than currently released behavior:

Blocking issues

None confirmed. No introduced Critical or Major defects survived verification.

Non-blocking

  1. Minor — Link authorization setup from the opening setup path.
    documentation/security/oidc/index.mdx:22–30

    The introduction directs readers through minimal configuration and token modes, but does not link the required group mapping and permission grants. Those instructions exist later, so this is a discoverability improvement rather than missing documentation.

    Suggested fix: Add “map external groups and grant endpoint/table permissions” to that setup path, linking to #mapping-user-permissions.

  2. Minor — Make JWKS refresh semantics more explicit.
    documentation/configuration/oidc.md:266–275
    documentation/security/oidc/index.mdx:1463–1465

    The new guidance is accurate, but could distinguish the refresh interval from a hard signing-key lifetime. The implementation retains previously downloaded keys when refresh fails, and the authentication request performing a refresh can wait for the provider timeout—even when its signing key is already cached. This is confirmed by OidcPublicKeysStore.getPublicKey() and loadPublicKeys() in the frozen Enterprise source.

    Suggested fix: Briefly document stale-key retention and synchronous refresh latency beside the expiry setting or outage discussion.

What works

  • The OIDC split is findable through the sidebar and related pages. All 42 original heading anchors remain present in generated overview HTML.
  • Claim fallback, JWT validation, HTTP session credential binding, ownership restrictions, and Console renewal agree with the frozen implementation sources.
  • The Entra guide supplies the schema, least-privilege grants, production managed-identity credential selection, and long-running token rotation needed by its examples.
  • Java documentation correctly distinguishes startup rejection, established-sender recovery, query failures, and orphan-drainer policy.
  • Raw Markdown and llms.txt expose both provider guides; generated core and Azure dependency versions remain aligned.

Validation

Check Result
yarn build Passed
Markdown/version plugin tests 25 passed
Native-independent Java token-provider tests 53 passed
Frozen Java core/Azure modules Compiled
Complete Entra FxIngest Java example Compiled
Python example syntax 33 fences parsed successfully
git diff --check Passed

The 53 passing Java tests comprise RefreshingTokenProviderTest (35), TokenProviderRegistryTest (2), and AzureTokenSourceTest (16).

The build’s GitHub release lookup returned HTTP 403; its tested fallback correctly generated version 1.3.10.

Limits: No live IdP, server/SQL, or end-to-end authentication execution was performed. Python checks establish syntax validity, not runtime correctness. Broader Java tests could not complete because the extracted source build lacks darwin-aarch64/libquestdb.dylib; that is a validation limitation, not a demonstrated documentation defect.

Downgraded findings

18 candidates checked: 2 retained as Minor suggestions, 12 inherited-content findings excluded from this PR’s verdict, and 4 consumer false positives removed.

The consumer objections were dropped because:

  • Using QWP does not require denying every alternative transport.
  • The explicitly linked Microsoft role-assignment procedure is a valid setup dependency.
  • The supplied schema matches the page’s concrete producer and encoder.
  • An attached managed identity, secure endpoint, and deployment-specific values are legitimate prerequisites.

The inherited findings include TLS/keystore wording, ROPC guidance, redirect/discovery details, PingFederate instructions, and adjacent QWP operational-reference drift. These were checked against the original content and should not be presented as regressions introduced here.

@glasstiger

Copy link
Copy Markdown
Contributor Author

Level-2 review — approve-with-nits

Reviewed documentation commit 77c98d6b185005c5085a817abe0198310ecd707c, assuming all four implementation PRs merge—not against currently released behavior:

Blocking issues

No introduced Critical or Major issues confirmed.

Non-blocking

Minor — Qualify all-replica startup behavior by retry mode
documentation/connect/wire-protocols/qwp-client-behavior.md:433

The changed row says:

Initial connect throws QwpRoleMismatchException; the running sender retries.

That describes fail-fast startup, but omits the other initial-connect modes:

  • OFF: throws QwpRoleMismatchException.
  • SYNC: retries within the startup budget; exhaustion throws a wrapping LineSenderException.
  • ASYNC: returns the sender and retries in the background.

Suggested replacement:

OFF startup throws QwpRoleMismatchException; SYNC startup retries within its budget; ASYNC startup and running senders retry in the background.

Verified against the frozen Java mode dispatch and retry implementation. This is a table qualification issue; the nearby initial-mode section already describes those modes correctly.

What works

  • The OIDC/provider split is discoverable through the sidebar and relevant Azure, RBAC, and client pages. All 42 original OIDC heading anchors survive in generated HTML.
  • Token selection, issuer/audience checks, claim-fallback priority, and external-user permissions agree with the intended implementation.
  • Java automatic renewal and lifecycle-specific authentication recovery are documented consistently.
  • Fixed-token rotation preserves disk-backed slot identity and closes the old owner before replacement.
  • The examples distinguish publication, acknowledgements, durability, and at-least-once replay.

Validation

All six review passes completed: technical correctness, placement/retrieval, examples/grammar, cross-page coherence, fresh-context adversarial review, and an isolated-page consumer exercise. Findings were independently checked before inclusion.

Check Result
Production documentation build Passed
Changed plugin tests 25/25 passed
Frozen Java token-provider tests 54/54 passed
Entra Java example compilation Passed
Python code-fence syntax 33 parsed successfully
Provider HTML/raw Markdown, llms.txt, legacy anchors Verified
Diff checks and documentation working tree Passed; clean

Limitations: No live Azure/PingFederate authentication, QuestDB SQL/server, or browser end-to-end testing was performed. An additional role-rejection test could not exercise its scenario because the source archive lacks the Darwin/AArch64 native library; the finding above is source-verified. Python syntax parsing is not runtime validation.

Downgraded / excluded

  • Missing production Python credential wiring: not a Major one-shot failure—the page names the constructors and directly links the Azure Identity reference.
  • Missing service-only Entra setup: an enhancement, not a broken stated workflow; this guide explicitly configures one application for both Console users and services.
  • Inherited issues: excluded from the PR verdict after baseline comparison, including comparison of the moved provider content against the original OIDC page. These include the existing System.nanoTime() timestamp example, Transport.WS enum example, query-acquisition timing, and shutdown/durability inconsistencies. They remain follow-up debt, not regressions introduced here.

Final tally: 1 introduced finding confirmed; 24 inherited candidates excluded; 2 proposed gaps/scope extensions dismissed.

@glasstiger

Copy link
Copy Markdown
Contributor Author

Verdict: approve — level 2

Reviewed documentation commit d960544e4359b8ed3ce20c86d448150706836b5c against the intended combined behavior of java-questdb-client#102, questdb#7799, questdb-enterprise#1267, and ui#603, using frozen snapshots of their implementation heads—not currently released behavior.

Blocking issues

None confirmed. No PR-introduced Critical or Major findings survived verification.

Non-blocking issues

None attributable to this PR. Existing documentation problems surfaced by the adversarial review are separated below rather than made merge requirements.

What works

  • The OIDC split preserves discoverability. The overview and both provider guides have appropriate sidebar placement and inbound links. The old route remains valid, and all 42 legacy heading anchors survive in generated output.
  • The authentication contracts agree with the implementations. Claim precedence, independent fallback, malformed-claim rejection, issuer/audience/expiry checks, and credential-bound HTTP sessions are accurately distinguished.
  • The Entra service path establishes prerequisites before ingestion. App roles, mappings, endpoint/table grants, and schema precreation precede Java/Python examples with matching columns.
  • Java renewal and failure handling are unusually thorough. Provider ownership, startup token waits, established-sender recovery, query failures, orphan drainers, health reporting, and acknowledgement barriers are distinguished.
  • The SQL ownership additions are consistent. The overview and six touched SQL references agree on external users naming a mapped QuestDB group.

Validation

Check Result
Production yarn build Passed
Release-version/raw-Markdown plugin tests 28/28 passed
Focused Java provider, registry, and Azure tests 54/54 passed
Exact Entra FxIngest Java example Compiled against frozen Java source
Python fences across changed pages 36 parsed successfully
SQL fences in the three OIDC guides 11 parsed successfully with the local SQL parser
Generated HTML/raw Markdown Provider pages present; dependency versions resolved; no interpolation leftovers
Legacy OIDC heading anchors 42 retained; none missing
git diff --check Passed

Limits: No live Entra/PingFederate provisioning, Enterprise SQL execution, or end-to-end renewal/revocation/outage exercise was performed. SQL parsing and source inspection do not substitute for those runtime tests.

Downgraded findings

33 agent-raised candidate reports assessed; 0 accepted as PR-introduced defects. This count includes one duplicate startup finding. These dispositions distinguish inherited observations from genuine false positives.

Existing passages or assumptions—not regressions introduced here

Candidate Why dropped from this PR review
Java nanosecond example uses System.nanoTime() Already present in the baseline Java guide.
Replica-only example starts the default sender pool Unchanged QWP quick-start example.
“Bounded” Java polling contains parameterless await() Unchanged baseline example.
Connect-string example uses Transport.WS Unchanged baseline example.
Notebook decoding lacks a charset fallback Copied from the former OIDC page.
Query acquisition described at submit() rather than borrowQuery() Existing Java/QWP contract wording.
Generic auto_flush=off advice lacks the Java restriction Existing connect-string reference content.
Legacy buffer keys presented as QWP settings Existing reference content.
Ingress async startup presented as bypassing query prewarm failure Unchanged startup table; raised independently twice.
QWP text denies a separate TCP connect timeout Existing contradictory specification text.
Single-host Java reconnect warning Unchanged baseline warning.
Error-handler threading descriptions disagree Existing Java guide inconsistency.
Fast close said to discard disk-backed data Existing overbroad connect-string wording.
Automatic SQL replay lacks a repeated-write warning Existing failover documentation gap.
Automatic schema creation stated without the OIDC exception Existing Java statement; the new OIDC path explicitly precreates schema.
Architecture overview says every request contains a valid token The absolute statement is inherited; the edit corrects token types, while the new session section explains the exceptions.
Self-signed certificate guidance recommends disabling verification Unchanged configuration guidance.
transaction lacks behavioral documentation Existing key-index-only coverage.
Entra configuration enables optional ROPC Copied from the former provider instructions.
PingFederate LDAP settings appear only in a screenshot Inherited provider instructions.
Generic HA upgrade-error classifications lack lifecycle qualifications Existing cross-page drift, not created by the revised recovery rules.

These are not endorsements of the existing passages; several warrant separate cleanup.

One-shot Entra exercise: gaps rejected on verification

Candidate Disposition
G1: Missing tenant/application/deployment values User-specific inputs, with instructions for locating them.
G2: Missing TLS setup Explicit prerequisite with direct TLS and trust-store links.
G3: Missing managed-identity role-assignment procedure Direct link to the relevant Microsoft assignment instructions.
G4: Missing identity provisioning/type Deployment provisioning is external; identity selection is documented.
G5: Missing real human-group membership Tenant-specific input, not an undocumented QuestDB parameter.
G6: Missing second service’s schema and permission policy The exercise supplied no policy; the page provides the repeatable role/group/grant pattern.
G7: Missing ownership-transfer SQL Not needed for the documented admin-precreation route; ownership rules are linked from the overview.
G8: Java example sends one row rather than running indefinitely A minimal authentication example, not a complete business producer; renewal and lifecycle behavior are explained.
G9: Missing complete Java project/build recipe Dependency coordinates and Java-client links are supplied; the exact example compiled.
G10: Missing complete durability/backpressure policy Direct Java store-and-forward and acknowledgement references provide that contract; operational budgets remain deployment choices.
G11: Missing signing-key outage behavior and ROPC default Covered through the linked overview/configuration references; optional ROPC wording is inherited.

No repository changes were made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant