Skip to content

Client config reporting (3.x) — stage 2: full DRIVER_CONFIG report - #974

Merged
dkropachev merged 6 commits into
scylladb:scylla-3.xfrom
nikagra:feature/driver-config-reporting-3x-phase2
Aug 13, 2026
Merged

Client config reporting (3.x) — stage 2: full DRIVER_CONFIG report#974
dkropachev merged 6 commits into
scylladb:scylla-3.xfrom
nikagra:feature/driver-config-reporting-3x-phase2

Conversation

@nikagra

@nikagra nikagra commented Jul 27, 2026

Copy link
Copy Markdown

What

Stage 2 of driver configuration reporting for the 3.x driver: fills in the full DRIVER_CONFIG JSON report, replacing the stage-1 {"version":1} placeholder. With reporting enabled — the default — the control connection reports the driver's effective configuration in the normative cross-driver schema shape, so an engineer investigating an incident can read a client's settings out of system.clients. Stage 1 (#973) is merged, so this branch sits directly on scylla-3.x, as five per-concern commits; SESSION_ID behaviour is unchanged by this stage.

The report is built afresh for every control connection, inside its STARTUP frame assembly, and never cached — it describes the objects in force at that handshake rather than the configuration the Cluster was constructed from. Cluster.Manager.init() builds the Connection.Factory before it calls LoadBalancingPolicy#init, and the first control connection's STARTUP goes out in between, so a datacenter or rack the policy infers cannot be known on the first report and is known on every later one. Conventions throughout: kebab-case keys, nested objects, and omission (never null) of any key or group with no value.

driver-core/src/test/resources/config/driver-config-report-v1.schema.json is a byte-for-byte copy of the design doc's normative block, currently revision v6. The report's version stays 1 — the doc has never bumped it, so "v4"/"v5"/"v6" name doc revisions only and are deliberately not used as labels in the code.

The report

Everything hangs off three groups under version:

  • connection — connect/read timeouts, the per-connection in-flight request limit, the shard-aware-port setting, the TCP/socket options, the reconnection policy, the datacenter the driver holds connections to, and tls only when TLS is on (the group has no enabled flag; its presence is what reports it).
  • control-plane — the system-query (client-side) and schema-agreement timeouts.
  • query — the per-request defaults plus the three policies that act on a query: retry, load-balancing (with the node preference beside it), and speculative-execution when configured.

The default report, 858 bytes on the wire:

{
  "version": 1,
  "connection": {
    "connect": { "timeout-ms": 5000 },
    "read": { "timeout-ms": 12000 },
    "requests": { "in-flight": { "max": 1024 } },
    "pool": { "shard-aware": { "enabled": true } },
    "socket": { "tcp-no-delay": true, "keep-alive": false, "reuse-address": false },
    "reconnection": {
      "policy": { "type": "exponential", "base-ms": 1000, "max-ms": 600000 }
    },
    "node-preference": { "type": "dc-auto" }
  },
  "control-plane": {
    "queries": { "system": { "timeout": { "client-side-ms": 12000 } } },
    "schema": { "agreement": { "timeout-ms": 10000 } }
  },
  "query": {
    "defaults": {
      "page": { "size": 5000 }, "consistency": "LOCAL_ONE", "serial-consistency": "SERIAL",
      "idempotence": false, "client-timestamps": true, "request": { "timeout-ms": 12000 }
    },
    "retry": { "policy": { "type": "standard-error-aware" } },
    "load-balancing": {
      "policy": {
        "type": "token-aware",
        "load-distribution": "shuffle",
        "fallback-to-non-preferred-nodes": false
      },
      "node-preference": { "type": "dc-auto" }
    }
  }
}

Invariants: reporting must never prevent a connection

SESSION_ID rides on every connection independently of this feature, so in all four cases only the DRIVER_CONFIG blob is dropped and the connection is unaffected.

  • Fail-safe. Any failure while building the report is swallowed and logged at WARN. The catch also covers InternalErrorcustomPolicy() calls getClass().getSimpleName() on arbitrary user policy objects, which has a documented JDK edge case — but deliberately not bare Error, so OutOfMemoryError/StackOverflowError still surface.
  • Size cap — 32KiB, matching Driver config reporting — stage 2: full DRIVER_CONFIG report #968, Report driver and session configuration via STARTUP options - stage 1 gocql#964 and Report SESSION_ID and the driver configuration in the STARTUP options - stage 1 csharp-driver#262. Not only parity: CBUtil.writeString writes each STARTUP value with a 16-bit length prefix via ByteBuf.writeShort and no bounds check, so a value over 65535 bytes truncates the prefix modulo 65536 while still appending the whole body — a corrupt frame and a failed handshake, and not something the try/catch can save, since nothing throws. Parts of the report are user-supplied and unbounded (DC/rack names, custom policy class names), so without this the invariant simply wasn't true. Measured on the UTF-8 encoded bytes, since that is what the prefix counts.
  • Bounded policy-chain walk — 16 policies. The report unwraps the LB chain via ChainableLoadBalancingPolicy.getChildPolicy() on arbitrary user policies, so a cyclic chain would spin forever on the Cluster initialization path — the one failure mode the try/catch cannot contain, because it hangs rather than throws.
  • Unloadable reporter — a classpath without Jackson. Stage 1 made jackson-core/jackson-databind required compile-scope deps of driver-core, and DefaultDriverConfigReporter holds an ObjectMapper in a static field. Exclude jackson-databind — realistic on a maintenance line under a CVE policy — and merely initializing that class raises NoClassDefFoundError: an Error, raised while initializing the class rather than from any method it declares, so neither the reporter's own fail-safe nor its caller could contain it. Cluster.init() then failed outright, taking a classpath that merely lacks an optional serializer from "the report is skipped" to "the driver does not work" — the exact inverse of this section's invariant. Connection.Factory.buildDriverConfigReport now contains it, catching LinkageError (not just NoClassDefFoundError, so a version-mismatched Jackson surfacing as ExceptionInInitializerError is covered too) and reporting nothing, with a WARN naming the likely cause. This is the fallback SnappyCompressor already applies for its own optional library, and it does not contradict buildReport() deliberately not catching bare Error: that is about report building never masking a real JVM failure, while this is a call site tolerating a missing optional dependency. 4.x fixed the same gap in its own stage 2; stage 1 shipped it, so stage 2 hardens it.

Non-obvious derivations

  • Only token-aware has a built-in shape in the schema, and that shape carries no name — so it is claimed only for a chain every element of which the group can describe. Anything else is custom, named after every policy in the chain, outermost first: WhiteListPolicy(TokenAwarePolicy(DCAwareRoundRobinPolicy)). The capability keys it can still state (load-distribution, fallback-to-non-preferred-nodes, adaptive-ordering) stay alongside the name, which the custom branch admits as additional properties, so naming a wrapper costs nothing that was reported before. Describable: TokenAwarePolicy, LatencyAwarePolicy, DCAwareRoundRobinPolicy, RackAwareRoundRobinPolicy, RoundRobinPolicy, the HostFilterPolicy the reported node preference was read from, and the internal PagingOptimizingLoadBalancingPolicy (which describes nothing and is never named). Not: WhiteListPolicy, ErrorAwarePolicy, an opaque filter, any user policy — nor a filter naming a datacenter the report does not carry, since that one narrows the session on top of what is reported rather than being it. Its DC and rack still show up in node-preference either way.

  • load-distribution comes from TokenAwarePolicy.getReplicaOrdering(): RANDOM (the default) → shuffle, TOPOLOGICALreplica-set, NEUTRALround-robin, since it keeps the child's plan order and every built-in child rotates its starting host across successive plans.

  • fallback-to-non-preferred-nodes is true whenever the policy can reach a node outside the preference reported beside it. For DCAwareRoundRobinPolicy that is usedHostsPerRemoteDc > 0. RackAwareRoundRobinPolicy reports a rack, and the other racks of its local DC are outside it yet are always reachable — the second tier of a normal query plan, and folded into the first for an LWT or serial-consistency statement, which skips rack prioritization and plans over liveHostsAllLocalDC instead; distance() returns REMOTE, not IGNORED, for them either way — so it is always true there, remote-DC hosts or not.

  • adaptive-ordering is omitted unless a LatencyAwarePolicy is in the chain, in which case {"signals":["latency"]} — that policy genuinely reorders, moving slow hosts to the end of the plan rather than removing them. It is reported wherever that policy appears, token-aware or not: adaptive ordering is a property of the chain, so LatencyAwarePolicy(RoundRobinPolicy) carries it while claiming neither load-distribution nor fallback-to-non-preferred-nodes, which it cannot state.

  • The node preference fills both of the schema's slots, from the same policy chain: query.load-balancing.node-preference describes what a query is routed by, connection.node-preference the part of the cluster the driver holds connections to. One LoadBalancingPolicy decides both here, since distance(Host) governs whether a host gets a pool at all — so the connection slot carries the datacenter half alone. The rack does not belong there: RackAwareRoundRobinPolicy.distance() returns REMOTE, never IGNORED, for a local-DC host in another rack, so those hosts are still pooled and reporting type:"rack" would claim a restriction the driver does not apply. The datacenter genuinely scopes pooling — a host outside the preferred one is IGNORED unless the policy is configured to use hosts there, and an ignored host gets no pool. Matches Driver config reporting — stage 2: full DRIVER_CONFIG report #968 (toDatacenterPreference()) and gocql fix(deps): align Jackson modules with BOM #987, which fills its own slot from HostFilter. Configured and inferred parts are split: both configured → type:"rack"; either inferred → type:"rack-auto" with the configured part under local-* and the inferred one under inferred-local-* (the schema admits only one key per part). The first control connection reports nothing inferred — the policy is initialized only after its STARTUP — and every later one carries what the policy has since inferred, which is what the inferred-local-* keys exist for.

    Both slots are an approximation once a wrapper sits above the located policy, and the class javadoc now says so. HostFilterPolicy.distance() — and therefore WhiteListPolicy's, since it extends it — returns IGNORED for any host failing its predicate, including one inside the reported datacenter, and a custom chainable policy computes distance() itself and need honor nothing below it. So WhiteListPolicy(DCAware("dc1")) reports local-dc: "dc1" while the whitelist is what actually decides the pool set: the datacenter is necessary but not sufficient. Reported anyway, on the same grounds Driver config reporting — stage 2: full DRIVER_CONFIG report #968 settled in 57386f6dc3 — hiding a datacenter the operator really did configure is the worse failure mode — and the asymmetry is deliberate: nothing is inferred on a third party's behalf, but what was explicitly configured is passed through. The restriction itself is now visible in the policy name rather than nowhere. Pinned by should_report_the_preferred_datacenter_under_a_filtering_wrapper.

    Read the other way round when the filter is the only thing that prefers a datacenter. HostFilterPolicy.fromDCWhiteList retains the datacenters it was handed, so fromDCWhiteList(new RoundRobinPolicy(), ["dc1"]) — nothing in the chain preferring a DC of its own — fills both slots with {"type":"dc","local-dc":"dc1"}, the same shape gocql fix(deps): align Jackson modules with BOM #987 took from DataCenterHostFilter. A location-aware policy still wins over a filter above it, since it is what builds the query plan — and the filter is then named, having restricted the session without being stated anywhere: TokenAwarePolicy(fromDCWhiteList(DCAware("dc1"), ["dc2"])) reports custom/TokenAwarePolicy(HostFilterPolicy(DCAwareRoundRobinPolicy)), not plain token-aware. Everything a filter can express that names no single datacenter keeps the group omitted: a blacklist names no preferred DC, several allowed DCs name no single one, a blank name is no name (nothing validates the strings fromDCWhiteList is handed, and local-dc is a required non-empty string), WhiteListPolicy filters on addresses, and a caller-supplied Predicate<Host> stays opaque.

  • Custom policies{type:"custom", name:<simple class name>}, falling back to the binary name for an anonymous class, which has no simple name where the schema requires a non-empty string. For load balancing the name is the outermost policy the user configured, skipping the PagingOptimizingLoadBalancingPolicy wrapper Cluster.Manager adds — which is also why that class now implements ChainableLoadBalancingPolicy: without unwrapping it, every report would degrade to {type:"custom"} and lose the flags and the preference.

  • in-flight.maxgetMaxRequestsPerConnection(LOCAL), falling back to a protocol default when PoolingOptions is still UNSET, since the report is built before pool sizing is finalized. Only UNSET falls back, so a limit of 0 set deliberately is not reported as 1024. The default row is resolved with the same walk PoolingOptions.setProtocolVersion applies — the highest DEFAULTS key not above the version — driven by the version the user pinned with withProtocolVersion when they pinned one, and by v3 otherwise (the lowest ScyllaDB negotiates, and the reference row for everything above it). That fallback is an assumption, and the one way this field can be wrong: an unpinned cluster negotiates downward from the highest version the driver supports, so one that settles on v2 — a Cassandra 2.0 cluster, no ScyllaDB being that old — is sized from v1's 128 while the report has already said 1024, and the build-once report has nothing later to correct it with. DEFAULTS holds only v1 and v3 rows, so a cluster pinned to v2 reports v1's 128, not v3's 1024; pinning is the one part of negotiation knowable at report time.

  • control-plane.queries.system.timeout.client-side-ms ← the read timeout; 3.x has no dedicated control-connection timeout. A maxSchemaAgreementWaitSeconds <= 0 passed to the ProtocolOptions constructor (which, unlike Cluster.Builder, accepts it) normalizes to 0 — exactly how a negative wait behaves.

  • reconnection.policy discrimination uses instanceof, so a user subclass of a built-in is reported as that built-in, with its real delays, rather than custom — unlike Driver config reporting — stage 2: full DRIVER_CONFIG report #968's exact-class matching. The retry policies are unaffected: all three 3.x built-ins have private constructors. Happy to tighten if reviewers prefer parity.

The PR adds the public getters this needs on DCAwareRoundRobinPolicy, RackAwareRoundRobinPolicy, TokenAwarePolicy and the two built-in speculative execution policies.

The whole reporting API is @Beta

Every public member of this feature carries Guava's @Beta, stage 1's and stage 2's alike — the convention this repo already applies to its recent additions (TabletMap, Metadata#getReplicasList, BatchStatement#getRoutingStatement). Stage 1 merged as 8ee83490ad, after the 3.11.5.17 release, so none of the surface has shipped and its shape is still free to change.

Type level, as TabletMap is: DriverConfigReporter and DefaultDriverConfigReporter — the latter covering DRIVER_CONFIG_KEY, the constructor, buildReport(), and the protected buildJson() / populateConfig() / configuration that a subclass compiles against. Method level, as Metadata and BatchStatement are: the fourteen policy accessors above, plus Cluster.Builder#withDriverConfigReporting, Configuration#isDriverConfigReportingEnabled and Configuration.Builder#withDriverConfigReporting.

PagingOptimizingLoadBalancingPolicy is marked on getChildPolicy() rather than on the class: the class has been public API since 2018 and is not itself provisional, while its implementing ChainableLoadBalancingPolicy at all is what this work introduced.

⚠️ One public-API behaviour change

QueryOptions.setConsistencyLevel now rejects null with a NullPointerException, where it previously accepted one. Called out separately because it is the only behaviour change this PR makes to an existing public method on a maintenance branch.

A null default already failed any statement that did not set one of its own: SessionManager:557-558 falls back to it for every request, and CBUtil.writeConsistencyLevel then dereferences it (cb.writeShort(consistency.code)) to write the frame. So accepting it turned a schema-required key into a missing one for a configuration that could never work. The reporter still omits a null defensively — only a subclass overriding the getter can produce one now, and letting it through would throw inside buildJson() and cost the whole report rather than one key.

setSerialConsistencyLevel is deliberately left alone (filed separately as #993), even though a null there is fatal by the identical path (Requests.java:403 sets SERIAL_CONSISTENCY whenever the level != SERIAL, which null satisfies, and :449 writes it). The schema makes serial-consistency optional, so a null is faithfully reported as an omission rather than as a missing required key — there is nothing for the reporter to protect. That it also accepts a non-serial level, where Statement.setSerialConsistencyLevel throws, is pre-existing and went into that issue rather than being widened into this PR.

⚠️ Limitations & omissions

Settings with no public getter (or no equivalent) in 3.x, left out per the schema's "omit what doesn't apply" rather than guessed: connection.write (no socket write timeout), connection.heartbeat (reserved-empty; getHeartbeatIntervalSeconds() has no home in this schema version), control-plane.…​.server-side-ms (no client-configurable USING TIMEOUT), reconnection.policy.max-attempts (both built-ins retry forever — the maxAttempts field ExponentialReconnectionPolicy carries is an overflow guard on the doubling, not a give-up bound: past it nextDelayMs() keeps returning maxDelayMs), query.retry.backoff (no built-in policy delays an attempt; a custom one exposes no schedule), query.retry.policy.max-retries (below), and both node preference slots when the policy carries no DC/rack notion at all (a bare RoundRobinPolicy, or a custom policy with nothing introspectable).

connection.socket.{tcp-no-delay,keep-alive,reuse-address} are best-effort: Connection.Factory applies each option only when SocketOptions has a value, then hands the bootstrap to NettyOptions.afterBootstrapInitialized, which can set any ChannelOption afterwards unintrospectably.

Two keys are omitted for a different reason — the schema admits only a boolean and 3.x cannot observe which one applies. Both are documented as absent exactly when the behaviour is unknown, which is this case:

Field Reported Omitted
query.defaults.client-timestamps ServerSideTimestampGeneratorfalse (always returns Long.MIN_VALUE); AbstractMonotonicTimestampGeneratortrue (never can) any other generator: returning Long.MIN_VALUE is a per-call decision, so whether timestamps are client-side is not a property of the configuration at all
connection.tls.hostname-verification SniSSLOptionstrue (SniSSLOptions:103 is the driver's only setEndpointIdentificationAlgorithm call) every other SSLOptions: the engine comes from a user SSLContext, or the whole handler from SslContext.newHandler(...), neither readable for it

Values the schema cannot express

A distinct case: the setting is introspectable, but the schema has no room for what 3.x accepts — and none of these setters validate their argument. The rule follows #968: omit where the key or group is optional, report as-is where it is required, never fabricate an in-range number.

Configuration Report
setReadTimeoutMillis(<= 0) all three of connection.read, control-plane.queries.system.timeout.client-side-ms and query.defaults.request omitted
setConnectTimeoutMillis(<= 0) connection.connect.timeout-ms omitted
setMaxRequestsPerConnection(LOCAL, 0) as-is — required and positive-only, while PoolingOptions rejects only negatives, so 0 is a limit an operator can set deliberately
setSoLinger(< 0) connection.socket.linger omitted; 0 is still reported, since the schema admits a non-negative interval
setReceiveBufferSize/setSendBufferSize(<= 0) group omitted (the schema requires a positive size)
setSerialConsistencyLevel(<not serial>) query.defaults.serial-consistency omitted — optional, and QueryOptions (unlike Statement) does not check that the level is serial
PercentileSpeculativeExecutionPolicy(…, 0.0, …) as-is — required by that branch, which bounds it 0..100 exclusive while the policy validates >= 0.0. Failing the branch drops the object out of the union, so a test asserts the fallout stays inside that one object

connection.requests.orphaned.max used to head that list and no longer does. A request the driver stops waiting for keeps its stream identifier until the response arrives (Connection.Dispatcher.removeHandler(handler, false) marks it); there is no configurable bound and no close-and-replace of the connection, so there is nothing to report — only 4.x has advanced.connection.max-orphan-requests. The schema revision vendored here (v6) makes the group optional for exactly that case, so omission is now the schema-valid answer, and a default report validates cleanly. Only a percentile of 0 can still produce a violation.

The as-is cases produce an accurate document that fails validation on one field. Deliberate: fabricating a value would misreport a setting an operator may have chosen on purpose, and dropping the whole report over one field would lose everything else. Each gap is pinned by name, so if the schema later gains a way to express one, the test says where to look.

Why max-retries is omitted

RequestHandler.SpeculativeExecution.retriesByPolicy is one counter shared by all four callbacks, incremented only on Type.RETRY — and RetryDecision.tryNextHost(cl) is Type.RETRY, so it counts exactly like retry(cl). In both built-ins:

path guard in the policy gate at the call site bound
onReadTimeout nbRetry != 0 → rethrow ≤ 1
onWriteTimeout nbRetry != 0 → rethrow idempotent statements only ≤ 1
onUnavailable nbRetry != 0 → rethrow ≤ 1
onRequestError nonenbRetry unread idempotent statements only one per remaining host

The shared counter means the first three give one retry between them, not one each; "idempotent only" means RequestHandler substitutes rethrow() without calling the policy at all, because after a write timeout or a request error "there is no guarantee that the mutation has been applied server-side or not".

So a non-idempotent statement — the default — is bounded at exactly 1 retry (speculative executions are idempotence-gated too), while an idempotent one is bounded only by the query plan. Statement.setIdempotent picks per statement, so one Cluster runs both regimes and the reported idempotence is only the default. No single number describes the policy, and the schema's wording for the key — "absent when no explicit retry limit is configured" — fits: both built-ins are parameterless singletons. 4.x's DefaultRetryPolicy has the identical split, so its reporter must answer the same way, and the doc's driver-mapping row already reads max-retries | java | (n/a). Some retries never reach the policy or its counter at all: IS_BOOTSTRAPPING calls retry(false, null) directly, as do connection/pool failures on write and UNPREPARED re-prepares.

The cap that would make this unconditional was added in 150f4e732e (CUSTOMER-331) and then silently reverted in full by 7da1f87cc2 — filed as #992. If it is restored, "max-retries": 1 becomes correct and I will report it.

Parity with the merged siblings

All three sibling stage 2s have now merged#968 (4.x, a3d7be633e), scylladb/gocql#987 and scylladb/csharp-driver#263 — so parity here is against shipped code rather than against a moving target. Re-checked at b6a01b24:

driver vendored schema status
java 4.x (#968) byte-for-byte equal to this branch's (md5 3961317…) in sync
gocql (#987) byte-for-byte equal (docs/driver-config-schema.json) in sync
csharp (#263) v5orphaned still in required, older orphaned.max description needs a resync, below

The shapes agree where the drivers agree, and differ only where the drivers themselves do. adaptive-ordering.signals is the clearest case: each driver names the observations its own reordering actually consults — ["latency"] here, from LatencyAwarePolicy; ["response-rate","in-flight-requests","recovery-state"] in 4.x, from DefaultLoadBalancingPolicy's slow-replica avoidance; ["in-flight-requests"] in gocql. The connection.socket group is identical rule for rule with 4.x, defaults included, down to which values are omitted rather than emitted out of range.

dkropachev's latest round on #968 produced four report-accuracy fixes there. None of them changes anything here, which is worth stating so it need not be re-derived:

#968 fix 3.x
report the schema-query server-side timeout on every backend, not only ScyllaDB N/A — 3.x has no client-configurable USING TIMEOUT at all, so server-side-ms is omitted outright and there was never a backend gate (nor the NodeShardingInfo argument that carried it in 4.x)
report a padded local datacenter as configured rather than trimmed already the behaviour — nothing in the reporter or in DCAwareRoundRobinPolicy/RackAwareRoundRobinPolicy trims, so getLocalDc() reaches the report verbatim. nonEmptyString is minLength: 1, so " dc1 " is valid to emit, and hiding the whitespace would hide the typo the report exists to expose
read the latched load-balancing state rather than reloadable config structurally N/A — 3.x has no runtime configuration reload, and the reporter reads the live policy objects rather than a config snapshot
report the speculative execution parameters the policy is running with already done here, by the getters this PR adds to ConstantSpeculativeExecutionPolicy and PercentileSpeculativeExecutionPolicy; 4.x was catching up to 3.x on this one

Unresolved in all four drivers, and so in the document rather than in any of them: pool sizes under connection.pool. $defs/connection-pool admits shard-aware and nothing else, additionalProperties: false, identically in every vendored copy — so no driver can report them until the document makes room. Two things need settling in the shape: whether 0 is representable, and that 3.x's per-distance core/max pair has no single "connections count" to report. #968 merged without it too, so this is now a spec item, not a 4.x-first one.

Testing

DefaultDriverConfigReporterTest covers gating, the fail-safe and the two limits, the default report as a golden test that also pins the top-level key set, every discriminated-union branch and optional-group case the reporter can emit, and each omission rule above. Schema conformance validates the shipped resource with com.networknt:json-schema-validator (pinned to 1.5.x, the last minor line targeting Java 8), comparing the validator's message set against the documented gaps — so a configuration that grows an undocumented violation fails, and so does one whose documented gap silently disappears.

DriverConfigReportingCcmTest covers SESSION_ID shared across a session's connections and across sessions of one Cluster, DRIVER_CONFIG stored on exactly the control connection, and nothing stored when reporting is off. What the server stored is compared byte-for-byte against the string Connection.Factory built, rather than spot-checked key by key: an oversized STARTUP value is silently truncated by the unchecked 16-bit prefix rather than rejected — which is the whole reason for the size cap — and truncation is exactly what a key check would miss. Live-verified against ScyllaDB 2026.1 via CCM.

The reconnect test runs against a Cluster of its own, and the poll that waits for the new report is scoped to that cluster's SESSION_ID. Both matter because the class shares one cluster and TestNG orders methods alphabetically: forcing a reconnect leaves the superseded control connection in system.clients until the server reaps its row, so two rows would carry a DRIVER_CONFIG — which should_store_session_id_on_all_connections_and_driver_config_on_control, running later against that same cluster, asserts against. An unscoped poll had the matching failure mode from the other side: every other driver connection to the node is equally "new" to a key snapshot, the class-level session's own control connection included.

Green from a clean target: 81 tests in DefaultDriverConfigReporterTest, the whole unit group (708 run, 1 skipped, 0 failures), and make check (the full-reactor mvn verify -DskipTests that CI's Full verify runs, including clirr and animal-sniffer) on JDK 11 across all 15 modules.

Follow-up

  • Reporting ships enabled (stage 1's default, pinned by should_enable_driver_config_reporting_by_default). Cluster.Builder.withDriverConfigReporting(false) turns the DRIVER_CONFIG blob off, but SESSION_ID is unconditional, so "off" is not "zero change on the wire". The server prerequisites that default relies on — tolerance of unknown STARTUP keys and the client_options value length — were validated live against 2026.1.
  • This one should merge first — overtaken by events, and it changes nothing here. That bullet argued for landing this branch ahead of the siblings so the re-nested shape would become normative. All three merged first instead: gocql fix(deps): align Jackson modules with BOM #987 (Aug 11), csharp-driver Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 (Aug 11), Driver config reporting — stage 2: full DRIVER_CONFIG report #968 (Aug 12). The meaning of version: 1 is therefore already fixed, and this branch conforms to it — its vendored schema is byte-for-byte equal to the copies Driver config reporting — stage 2: full DRIVER_CONFIG report #968 and fix(deps): align Jackson modules with BOM #987 merged with, and the shapes agree wherever the drivers do (see Parity). The one asymmetry left is unchanged: Driver config reporting — stage 2: full DRIVER_CONFIG report #968 can fill orphaned.max and 3.x cannot, which v6 makes legal rather than a violation.
  • csharp-driver Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 #263 merged with the v5 schema and needs a resync. Its src/Cassandra.Tests/Requests/driver-config-report-v1.schema.json still has orphaned in required and carries the pre-v6 orphaned.max description; the other three copies are byte-for-byte identical at v6. Its own report fills orphaned.max, so nothing it emits is invalid against either revision — the drift is in the vendored contract, not in the wire shape, and it should be pulled forward so all four keep validating against one document.
  • The commits. Five, per concern, since this repo rebase-merges rather than squashes:
    1. test: ship the normative v1 driver-config schema and its validator
    2. feat(policies): expose the state the config report reads
    3. fix: reject a null default consistency level in QueryOptions
    4. feat: report the full driver configuration to the cluster at connection time
    5. fix: build the config report per control connection, and only when it can be
  • Spec feedback is now Driver config reporting: gaps in the normative schema, and doc prose that contradicts it (revision v6) #996, not a list here. Everything this work turned up about the normative document — the schema gaps (percentile needing minimum: 0, in-flight.max with no lower escape hatch, the unspecified 32KiB size limit, the built-in LB branch having no name, fallback-to-non-preferred-nodes required with no referent, pool sizes with nowhere to go, the two unguided homes for the node preference, version pinned at 1 across meaning changes) and the places the document's own prose contradicts the block it ships (design principles 4 vs 5, the Top-level envelope example, the per-driver mapping tables, four contradictions in the retry-policy section, standard-error-aware left with no behavioural definition) — is collected there, every claim cited against revision v6 and against the md5 chain across all four drivers. Filed as one issue so the document's owner has one artifact to answer, and so Driver config reporting — stage 2: full DRIVER_CONFIG report #968, gocql#987 and csharp-driver#263 can point at the same place. The one item that has already landed stays visible above: connection.requests.orphaned became optional in v6, which is why a default 3.x report now validates cleanly.

Fixes DRIVER-382

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

DRIVER_CONFIG now emits a complete v2-shaped JSON report with connection, socket, control-plane, policy, load-balancing, pool, query-default, and TLS groups. The reporter omits unsupported values, handles cyclic policy chains, enforces a 32 KiB UTF-8 limit, and suppresses failures. Policy classes expose effective location and pooling data. A JSON Schema and expanded unit and CCM tests validate default, customized, cyclic, oversized, and invalid reports.

Sequence Diagram(s)

sequenceDiagram
  participant DriverConfigReporter
  participant DefaultDriverConfigReporter
  participant DRIVER_CONFIG
  DriverConfigReporter->>DefaultDriverConfigReporter: buildReport()
  DefaultDriverConfigReporter->>DefaultDriverConfigReporter: serialize configuration groups
  DefaultDriverConfigReporter->>DRIVER_CONFIG: emit UTF-8 JSON within 32 KiB
Loading

Possibly related PRs

Suggested reviewers: sylwiaszunejko, dkropachev

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The pull request explicitly references and states that it fixes DRIVER-382.
Out of Scope Changes check ✅ Passed The changes align with the stated objective of implementing stage 2 full DRIVER_CONFIG reporting and its supporting tests.
Title check ✅ Passed The title clearly identifies the stage 2 implementation of full DRIVER_CONFIG reporting for the 3.x driver.
Description check ✅ Passed The description directly explains the full DRIVER_CONFIG report, its safeguards, schema behavior, and test coverage.

Comment @coderabbitai help to get the list of available commands.

nikagra added a commit to nikagra/java-driver that referenced this pull request Jul 30, 2026
…stage 1)

Stage 1 (groundwork) of client configuration reporting for the 3.x
driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967).
Lets the driver report its effective configuration to ScyllaDB at
connection time via new STARTUP options, so operators can inspect
driver settings (system.clients.client_options) while investigating
incidents.

Gated behind Cluster.builder().withDriverConfigReporting(boolean),
which ships disabled (zero change on the wire when off). When enabled:

- SESSION_ID: a dedicated, driver-generated per-session UUID, sent on
  every connection (control and pool) so the server can group all of
  a session's connections. Independent of the user-settable CLIENT_ID.
- DRIVER_CONFIG: a compact JSON blob, sent only on the control
  connection. Stage 1 emits only {"version":1}; the full report
  follows in stage 2 (scylladb#974).

Reporting is fail-safe: any failure while building the report is
swallowed and never breaks connection initialization.

New DriverConfigReporter / DefaultDriverConfigReporter (package
com.datastax.driver.core), mirroring the existing ApplicationInfo
pattern, invoked from Connection.onOptionsReady(). The control
connection is marked via a new reportConfig flag threaded from
ControlConnection.tryConnect through a new
Connection.Factory.open(host, reportConfig) overload -- 3.x has no
pre-existing signal identifying the control connection at STARTUP
time.

Fixes DRIVER-382
nikagra added a commit to nikagra/java-driver that referenced this pull request Jul 30, 2026
…stage 1)

Stage 1 (groundwork) of client configuration reporting for the 3.x
driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967).
Lets the driver report its effective configuration to ScyllaDB at
connection time via new STARTUP options, so operators can inspect
driver settings (system.clients.client_options) while investigating
incidents.

Gated behind Cluster.builder().withDriverConfigReporting(boolean),
which ships disabled (zero change on the wire when off). When enabled:

- SESSION_ID: a dedicated, driver-generated per-session UUID, sent on
  every connection (control and pool) so the server can group all of
  a session's connections. Independent of the user-settable CLIENT_ID.
- DRIVER_CONFIG: a compact JSON blob, sent only on the control
  connection. Stage 1 emits only {"version":1}; the full report
  follows in stage 2 (scylladb#974).

Reporting is fail-safe: any failure while building the report is
swallowed and never breaks connection initialization.

New DriverConfigReporter / DefaultDriverConfigReporter (package
com.datastax.driver.core), mirroring the existing ApplicationInfo
pattern, invoked from Connection.onOptionsReady(). The control
connection is marked via a new reportConfig flag threaded from
ControlConnection.tryConnect through a new
Connection.Factory.open(host, reportConfig) overload -- 3.x has no
pre-existing signal identifying the control connection at STARTUP
time.

Fixes DRIVER-382
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from d00e683 to 12571f1 Compare July 30, 2026 20:40
nikagra added a commit to nikagra/java-driver that referenced this pull request Jul 30, 2026
…stage 1)

Stage 1 (groundwork) of driver configuration reporting for the 3.x
driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967).
Lets the driver report its effective configuration to ScyllaDB at
connection time via new STARTUP options, so operators can inspect
driver settings (system.clients.client_options) while investigating
incidents.

Gated behind Cluster.builder().withDriverConfigReporting(boolean),
which ships disabled (zero change on the wire when off). When enabled:

- SESSION_ID: a dedicated, driver-generated UUID, sent on every
  connection (control and pool) so the server can group all of a
  Cluster's connections -- including across multiple Sessions obtained
  from the same Cluster, since the control connection has no
  affiliation with any single Session. Independent of the
  user-settable CLIENT_ID.
- DRIVER_CONFIG: a compact JSON blob, sent only on the control
  connection. Stage 1 emits only {"version":1}; the full report
  follows in stage 2 (scylladb#974).

Reporting is fail-safe: any failure while building the report is
swallowed and never breaks connection initialization.

New DriverConfigReporter / DefaultDriverConfigReporter (package
com.datastax.driver.core), mirroring the existing ApplicationInfo
pattern, invoked from Connection.onOptionsReady(). Only constructed
when reporting is enabled -- a new NoopDriverConfigReporter is used
otherwise, so Jackson (used to build the JSON blob) is never loaded
when the feature is off; jackson-core/jackson-databind are marked
optional in driver-core/pom.xml accordingly. The control connection is
marked via a new reportConfig flag threaded from
ControlConnection.tryConnect through a new
Connection.Factory.open(host, reportConfig) overload -- 3.x has no
pre-existing signal identifying the control connection at STARTUP
time.

system.clients.client_options is per node, so DRIVER_CONFIG only
appears on the node holding the control connection.

Fixes DRIVER-382
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from 12571f1 to 4ef72fd Compare July 30, 2026 20:54
nikagra added a commit to nikagra/java-driver that referenced this pull request Jul 30, 2026
…stage 1)

Stage 1 (groundwork) of driver configuration reporting for the 3.x
driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967).
Lets the driver report its effective configuration to ScyllaDB at
connection time via new STARTUP options, so operators can inspect
driver settings (system.clients.client_options) while investigating
incidents.

Gated behind Cluster.builder().withDriverConfigReporting(boolean),
which ships disabled (zero change on the wire when off). When enabled:

- SESSION_ID: a dedicated, driver-generated UUID, sent on every
  connection (control and pool) so the server can group all of a
  Cluster's connections -- including across multiple Sessions obtained
  from the same Cluster, since the control connection has no
  affiliation with any single Session. Independent of the
  user-settable CLIENT_ID.
- DRIVER_CONFIG: a compact JSON blob, sent only on the control
  connection. Stage 1 emits only {"version":1}; the full report
  follows in stage 2 (scylladb#974).

Reporting is fail-safe: any failure while building the report is
swallowed and never breaks connection initialization.

New DriverConfigReporter / DefaultDriverConfigReporter (package
com.datastax.driver.core), mirroring the existing ApplicationInfo
pattern, invoked from Connection.onOptionsReady(). Only constructed
when reporting is enabled -- a new NoopDriverConfigReporter is used
otherwise, so Jackson (used to build the JSON blob) is never loaded
when the feature is off; jackson-core/jackson-databind are marked
optional in driver-core/pom.xml accordingly. The control connection is
marked via a new reportConfig flag threaded from
ControlConnection.tryConnect through a new
Connection.Factory.open(host, reportConfig) overload -- 3.x has no
pre-existing signal identifying the control connection at STARTUP
time.

system.clients.client_options is per node, so DRIVER_CONFIG only
appears on the node holding the control connection.

Fixes DRIVER-382
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from 4ef72fd to 2120a94 Compare July 30, 2026 23:53
nikagra added a commit to nikagra/java-driver that referenced this pull request Jul 31, 2026
…stage 1)

Stage 1 (groundwork) of driver configuration reporting for the 3.x
driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/scylladb#967).
Lets the driver report its effective configuration to ScyllaDB at
connection time via new STARTUP options, so operators can inspect
driver settings (system.clients.client_options) while investigating
incidents.

Two STARTUP options are added:

- SESSION_ID: a dedicated, driver-generated UUID sent on every
  connection (control and pool) unconditionally, like DRIVER_NAME and
  DRIVER_VERSION, so the server can group all of a Cluster's
  connections -- including across multiple Sessions obtained from the
  same Cluster, since the control connection has no affiliation with
  any single Session. Independent of the user-settable CLIENT_ID.
- DRIVER_CONFIG: a compact JSON blob describing the effective
  configuration, sent only on the control connection. Stage 1 emits
  only {"version":1}; the full report follows in stage 2 (scylladb#974).
  Enabled by default; opt out with
  Cluster.builder().withDriverConfigReporting(false).

The report is built once, while the Cluster initializes, and the
resulting string is reused for every control connection that Cluster
opens -- it is never rebuilt while the session is in flight. Building
it is fail-safe: any failure is swallowed and simply leaves
DRIVER_CONFIG unset instead of breaking cluster initialization.

New DriverConfigReporter / DefaultDriverConfigReporter (package
com.datastax.driver.core) build the blob. Connection.Factory, of which
there is one per Cluster, holds that Cluster's session id and the built
report, and hands the report to the control connection as a constructor
argument -- null everywhere else, which is what suppresses reporting.
The control connection is identified by threading a reportConfig flag
from ControlConnection.tryConnect through a new
Connection.Factory.open(host, reportConfig) overload, since 3.x has no
pre-existing signal identifying the control connection at STARTUP time.

jackson-core/jackson-databind are enforced as plain required
dependencies (as they already were in released 3.11.5.17), used to
build the JSON blob; the orphaned jackson-dataformat-yaml dependency
(dead since the Scylla Cloud config code was removed) is dropped, so
consumers no longer inherit SnakeYAML.

system.clients.client_options is per node, so DRIVER_CONFIG only
appears on the node holding the control connection.

Fixes DRIVER-382

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from 2120a94 to b2d9bab Compare July 31, 2026 17:01
dkropachev pushed a commit that referenced this pull request Aug 3, 2026
…stage 1)

Stage 1 (groundwork) of driver configuration reporting for the 3.x
driver -- the 3.x counterpart of the 4.x feature (DRIVER-381/#967).
Lets the driver report its effective configuration to ScyllaDB at
connection time via new STARTUP options, so operators can inspect
driver settings (system.clients.client_options) while investigating
incidents.

Two STARTUP options are added:

- SESSION_ID: a dedicated, driver-generated UUID sent on every
  connection (control and pool) unconditionally, like DRIVER_NAME and
  DRIVER_VERSION, so the server can group all of a Cluster's
  connections -- including across multiple Sessions obtained from the
  same Cluster, since the control connection has no affiliation with
  any single Session. Independent of the user-settable CLIENT_ID.
- DRIVER_CONFIG: a compact JSON blob describing the effective
  configuration, sent only on the control connection. Stage 1 emits
  only {"version":1}; the full report follows in stage 2 (#974).
  Enabled by default; opt out with
  Cluster.builder().withDriverConfigReporting(false).

The report is built once, while the Cluster initializes, and the
resulting string is reused for every control connection that Cluster
opens -- it is never rebuilt while the session is in flight. Building
it is fail-safe: any failure is swallowed and simply leaves
DRIVER_CONFIG unset instead of breaking cluster initialization.

New DriverConfigReporter / DefaultDriverConfigReporter (package
com.datastax.driver.core) build the blob. Connection.Factory, of which
there is one per Cluster, holds that Cluster's session id and the built
report, and hands the report to the control connection as a constructor
argument -- null everywhere else, which is what suppresses reporting.
The control connection is identified by threading a reportConfig flag
from ControlConnection.tryConnect through a new
Connection.Factory.open(host, reportConfig) overload, since 3.x has no
pre-existing signal identifying the control connection at STARTUP time.

jackson-core/jackson-databind are enforced as plain required
dependencies (as they already were in released 3.11.5.17), used to
build the JSON blob; the orphaned jackson-dataformat-yaml dependency
(dead since the Scylla Cloud config code was removed) is dropped, so
consumers no longer inherit SnakeYAML.

system.clients.client_options is per node, so DRIVER_CONFIG only
appears on the node holding the control connection.

Fixes DRIVER-382

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from b2d9bab to cc39729 Compare August 3, 2026 19:38
@nikagra
nikagra marked this pull request as ready for review August 3, 2026 19:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java-199-236 (1)

199-236: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep numeric fields within the report schema. SocketOptions stores non-positive values without validation, and the reporter emits them into positiveInteger and nonNegativeInteger fields. Omit read, linger, receive-buffer, and send-buffer when their values are disabled or out of range. request mirrors the read timeout, while connect and request are required by the schema; define their behavior for non-positive values instead of omitting them or emitting invalid values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`
around lines 199 - 236, Update DefaultDriverConfigReporter.connection() and
socket() to omit read, linger, receive-buffer, and send-buffer entries when
their configured numeric values are non-positive or outside the schema’s allowed
range, and ensure request follows the same read-timeout handling. Keep connect
and request present as required schema fields, defining a valid representation
for non-positive timeouts rather than emitting invalid numbers or omitting them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 346-350: Update the unknown-policy branch around customPolicy in
DefaultDriverConfigReporter so it reports the last non-wrapper policy-chain
element rather than the outermost PagingOptimizingLoadBalancingPolicy. Select
the final user-supplied element from policyChain before passing it to
customPolicy, while preserving the existing built-in type handling and output
structure.

---

Other comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 199-236: Update DefaultDriverConfigReporter.connection() and
socket() to omit read, linger, receive-buffer, and send-buffer entries when
their configured numeric values are non-positive or outside the schema’s allowed
range, and ensure request follows the same read-timeout handling. Keep connect
and request present as required schema fields, defining a valid representation
for non-positive timeouts rather than emitting invalid numbers or omitting them.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 71d93a92-786f-4b86-8f5c-9063bf5cc120

📥 Commits

Reviewing files that changed from the base of the PR and between a732d83 and cc39729.

📒 Files selected for processing (10)
  • driver-core/pom.xml
  • driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java
  • driver-core/src/main/java/com/datastax/driver/core/DriverConfigReporter.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/DCAwareRoundRobinPolicy.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/PagingOptimizingLoadBalancingPolicy.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/RackAwareRoundRobinPolicy.java
  • driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java
  • driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java
  • driver-core/src/test/resources/config/driver-config-report-v1.schema.json
  • pom.xml

@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from cc39729 to ba059af Compare August 3, 2026 19:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java-398-401 (1)

398-401: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Verify that desired-connections-count maps to core connections, not max connections.

getMaxConnectionsPerHost(HostDistance.LOCAL) is the pool ceiling, not the number of connections the pool keeps open. With core=2, max=8 the report claims 8 desired connections. The schema describes it as "Number of connections to open per host or per shard". getCoreConnectionsPerHost(HostDistance.LOCAL) matches that description. Both defaults are 1 for protocol v3, so the unit test cannot distinguish them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`
around lines 398 - 401, Update the desired-connections-count mapping in
DefaultDriverConfigReporter to use
pooling.getCoreConnectionsPerHost(HostDistance.LOCAL) instead of
getMaxConnectionsPerHost, while preserving the existing effective fallback and
report structure.
🧹 Nitpick comments (1)
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java (1)

105-114: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move the pool default lookup out of static initialization.

buildReport() cannot contain a failure in a static initializer. If PoolingOptions.DEFAULTS.get(ProtocolVersion.V3) ever returns null, class initialization throws ExceptionInInitializerError on the cluster-initialization path, which defeats the documented fail-safe contract.

♻️ Proposed change
-  private static final int V3_MAX_CONNECTIONS_PER_HOST =
-      PoolingOptions.DEFAULTS.get(ProtocolVersion.V3).get(PoolingOptions.MAX_POOL_LOCAL_KEY);
-  private static final int V3_MAX_REQUESTS_PER_CONNECTION =
-      PoolingOptions.DEFAULTS
-          .get(ProtocolVersion.V3)
-          .get(PoolingOptions.MAX_REQUESTS_PER_CONNECTION_LOCAL_KEY);
+  private static int v3Default(Object key) {
+    return PoolingOptions.DEFAULTS.get(ProtocolVersion.V3).get(key);
+  }

Then call v3Default(...) from connectionPool(), inside the try of buildReport().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`
around lines 105 - 114, Move the ProtocolVersion.V3 pool-default lookups out of
the static V3_MAX_CONNECTIONS_PER_HOST and V3_MAX_REQUESTS_PER_CONNECTION
initializers. Add or reuse a v3Default(...) helper that performs the lookup at
runtime, and call it from connectionPool() within buildReport()’s existing try
block so lookup failures remain covered by the report’s fail-safe handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Other comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 398-401: Update the desired-connections-count mapping in
DefaultDriverConfigReporter to use
pooling.getCoreConnectionsPerHost(HostDistance.LOCAL) instead of
getMaxConnectionsPerHost, while preserving the existing effective fallback and
report structure.

---

Nitpick comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 105-114: Move the ProtocolVersion.V3 pool-default lookups out of
the static V3_MAX_CONNECTIONS_PER_HOST and V3_MAX_REQUESTS_PER_CONNECTION
initializers. Add or reuse a v3Default(...) helper that performs the lookup at
runtime, and call it from connectionPool() within buildReport()’s existing try
block so lookup failures remain covered by the report’s fail-safe handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 663bd4c5-b5fd-4973-a3a7-a2f6b75b323f

📥 Commits

Reviewing files that changed from the base of the PR and between cc39729 and ba059af.

📒 Files selected for processing (10)
  • driver-core/pom.xml
  • driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java
  • driver-core/src/main/java/com/datastax/driver/core/DriverConfigReporter.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/DCAwareRoundRobinPolicy.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/PagingOptimizingLoadBalancingPolicy.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/RackAwareRoundRobinPolicy.java
  • driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java
  • driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java
  • driver-core/src/test/resources/config/driver-config-report-v1.schema.json
  • pom.xml

@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from ba059af to 91f1061 Compare August 3, 2026 21:06
@nikagra

nikagra commented Aug 3, 2026

Copy link
Copy Markdown
Author

Both items from the last round are addressed in 91f1061.

desired-connections-count core-vs-max — confirmed and fixed to getCoreConnectionsPerHost: HostConnectionPool opens core connections at init and only grows towards max under load. The sharper case is core-only — setCoreConnectionsPerHost(LOCAL, 4) leaves max UNSET, so the old code took the v3 fallback and reported 1 for a pool that opens 4. Two tests pin both shapes.

V3 pool defaults in static initializers — moved into a v3PoolDefault(key) helper called from connectionPool(), so the lookup runs inside buildReport()'s fail-safe handling rather than at class-init time on the Connection.Factory path.

A self-review pass added one more thing in the same area: optional keys whose value the schema cannot express are now omitted rather than emitted invalid (disabled read timeout, negative SO_LINGER, non-positive buffer sizes), matching 4.x #968. The required positive-only fields that 3.x can still violate are documented on the class and pinned by a test asserting exactly one violation remains. See "Values the schema cannot express" in the PR description.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java-87-91 (1)

87-91: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require JSON node type assertions.

Use isIntegralNumber() for version, isObject() for the required groups, and isBoolean() for shard-aware.enabled. asInt() coerces values, and has() also accepts explicit null.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java`
around lines 87 - 91, Strengthen the assertions in DriverConfigReportingCcmTest
by checking report.path("version").isIntegralNumber(), verifying the required
connection, load-balancing-policy, and connection-pool nodes are objects with
isObject(), and asserting shard-aware.enabled isBoolean(). Replace the current
asInt() and has() checks while preserving the existing paths and required-node
coverage.
🧹 Nitpick comments (2)
driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java (1)

471-476: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the validation path by its segments.

getInstanceLocation().toString() still depends on PathType. Assert getNameCount() and getName(0..2) for query-defaults, request, and timeout-ms instead of asserting validator message text or a rendered path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java`
around lines 471 - 476, Update the validation assertions in
DefaultDriverConfigReporterTest to inspect the ValidationMessage instance
location by asserting its name count and each segment: query-defaults, request,
and timeout-ms. Replace the current rendered message-text assertion while
preserving the expected single validation error.
driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java (1)

336-425: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated DC-aware/rack-aware chain scan.

loadBalancingPolicy (Lines 342-363) and nodeLocationPreference (Lines 400-408) run the same scan over chain to find the DCAwareRoundRobinPolicy and RackAwareRoundRobinPolicy instances. Extract one helper that returns both, and call it from both methods.

♻️ Proposed refactor
+  private static final class LocalityPolicies {
+    final DCAwareRoundRobinPolicy dcAware;
+    final RackAwareRoundRobinPolicy rackAware;
+
+    LocalityPolicies(DCAwareRoundRobinPolicy dcAware, RackAwareRoundRobinPolicy rackAware) {
+      this.dcAware = dcAware;
+      this.rackAware = rackAware;
+    }
+  }
+
+  private static LocalityPolicies findLocalityPolicies(List<LoadBalancingPolicy> chain) {
+    DCAwareRoundRobinPolicy dcAware = null;
+    RackAwareRoundRobinPolicy rackAware = null;
+    for (LoadBalancingPolicy current : chain) {
+      if (current instanceof DCAwareRoundRobinPolicy) {
+        dcAware = (DCAwareRoundRobinPolicy) current;
+      } else if (current instanceof RackAwareRoundRobinPolicy) {
+        rackAware = (RackAwareRoundRobinPolicy) current;
+      }
+    }
+    return new LocalityPolicies(dcAware, rackAware);
+  }

Then in loadBalancingPolicy, replace the local dcAware/rackAware scan with a call to findLocalityPolicies(chain), and do the same in nodeLocationPreference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`
around lines 336 - 425, Extract the duplicated DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy scan from loadBalancingPolicy and
nodeLocationPreference into a shared findLocalityPolicies helper that returns
both discovered policies. Update both methods to use the helper while preserving
their existing type selection, failover calculation, and location preference
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@driver-core/src/test/resources/config/driver-config-report-v1.schema.json`:
- Around line 810-822: Update the `request.timeout-ms` schema to match the
reporter behavior: reference `#/$defs/nonNegativeInteger` and document `0` as
meaning the read timeout is disabled. Keep the field required so reports
containing the disabled value remain valid.

---

Other comments:
In
`@driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java`:
- Around line 87-91: Strengthen the assertions in DriverConfigReportingCcmTest
by checking report.path("version").isIntegralNumber(), verifying the required
connection, load-balancing-policy, and connection-pool nodes are objects with
isObject(), and asserting shard-aware.enabled isBoolean(). Replace the current
asInt() and has() checks while preserving the existing paths and required-node
coverage.

---

Nitpick comments:
In
`@driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java`:
- Around line 336-425: Extract the duplicated DCAwareRoundRobinPolicy and
RackAwareRoundRobinPolicy scan from loadBalancingPolicy and
nodeLocationPreference into a shared findLocalityPolicies helper that returns
both discovered policies. Update both methods to use the helper while preserving
their existing type selection, failover calculation, and location preference
behavior.

In
`@driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java`:
- Around line 471-476: Update the validation assertions in
DefaultDriverConfigReporterTest to inspect the ValidationMessage instance
location by asserting its name count and each segment: query-defaults, request,
and timeout-ms. Replace the current rendered message-text assertion while
preserving the expected single validation error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: QUIET

Plan: Pro Plus

Run ID: 7517ba2c-a329-47ef-8059-0de0e16d920e

📥 Commits

Reviewing files that changed from the base of the PR and between ba059af and 91f1061.

📒 Files selected for processing (10)
  • driver-core/pom.xml
  • driver-core/src/main/java/com/datastax/driver/core/DefaultDriverConfigReporter.java
  • driver-core/src/main/java/com/datastax/driver/core/DriverConfigReporter.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/DCAwareRoundRobinPolicy.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/PagingOptimizingLoadBalancingPolicy.java
  • driver-core/src/main/java/com/datastax/driver/core/policies/RackAwareRoundRobinPolicy.java
  • driver-core/src/test/java/com/datastax/driver/core/DefaultDriverConfigReporterTest.java
  • driver-core/src/test/java/com/datastax/driver/core/DriverConfigReportingCcmTest.java
  • driver-core/src/test/resources/config/driver-config-report-v1.schema.json
  • pom.xml

@nikagra

nikagra commented Aug 4, 2026

Copy link
Copy Markdown
Author

Force-pushed 91f1061de3f89e9f52bf: re-cut against the revised design-doc schema (the test resource is replaced verbatim from the updated doc; only the amended stage-2 commit changed).

Four groups changed shape, so the report on the wire differs from the one reviewed earlier:

  • top-level connection-poolconnection.pool, and it lost type / desired-connections-count — the schema no longer carries pool keying or a size;
  • new required connection.requests = {in-flight: {max}, orphaned: {max}}. in-flight.max is the old connection.max-requests; orphaned.max has no 3.x equivalent (a request we stop waiting for keeps its stream id until the response arrives, with no bound and no close-and-replace — only 4.x has max-orphan-requests), so it is omitted and is now the one violation every report carries. The conformance suite asserts "violates the schema in exactly the documented ways and no other" instead of "validates cleanly";
  • load-balancing-policy's built-in type narrowed to token-aware alone, with load-distribution + adaptive-ordering replacing token-aware / shuffle / latency-awareness. A bare DCAwareRoundRobinPolicy / RoundRobinPolicy / WhiteListPolicy is therefore reported as custom with its class name (its DC/rack still shows up in node-location-preference). load-distribution comes from a new TokenAwarePolicy.getReplicaOrdering(), which incidentally fixes an inaccuracy: the default policy orders replicas RANDOM, so the old report's "shuffle": false understated it;
  • node-location-preference.rack-auto now separates configured from inferred parts (local-* vs inferred-local-*).

The revision also cleared four of the five "required field 3.x cannot express in range" cases. query-defaults.request.timeout-ms and a serial consistency remain, and two new ones appeared: the omitted orphaned.max, and in-flight.max's cap of 32767 against the 32768 stream ids protocol v3 provides. All of it is written up in the description, including a list of spec items to reconcile with the doc owner — the doc's per-driver mapping tables and example instance were not updated with the revision.

Verified: 50 unit tests (was 43), the whole unit group at 679, verify green on JDK 11, and DriverConfigReportingCcmTest 3/3 against live ScyllaDB 2026.1 — the JSON in the description is the value read back from system.clients.client_options, not a hand-derived one. The 4.x sibling #968 still carries the pre-revision schema and needs the same pass; unlike 3.x it can fill orphaned.max.

@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from f89e9f5 to bb41178 Compare August 4, 2026 21:29
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nikagra

nikagra commented Aug 4, 2026

Copy link
Copy Markdown
Author

Force-pushed f89e9f52bfbb41178e36: a self-review round on the re-cut, three fixes, no shape change to the default report (still byte-identical).

  • serial-consistency could emit a non-serial level. QueryOptions.setSerialConsistencyLevel doesn't check isSerial() (Statement's does), so QUORUM reached the report and the schema's SERIAL/LOCAL_SERIAL enum rejected it. The key is optional, so it is now omitted — the rule the rest of the reporter already follows.
  • effective() conflated UNSET with a configured 0. PoolingOptions rejects only negatives, so setMaxRequestsPerConnection(LOCAL, 0) was reported as the protocol default 1024. Same class as the core-vs-max fix last round; it now falls back on PoolingOptions.UNSET only, and 0 joins the documented gaps.
  • A null default consistency NPE'd, discarding every other group via the fail-safe; now omitted, costing one documented gap instead of the whole report.

Plus three schema-conformance cases that were emittable but unvalidated (bare FallthroughRetryPolicy, zero SO_LINGER, rack-auto with the rack configured and the DC inferred). 50 → 55 tests, unit group 684/0, verify green on JDK 11.

One thing left as a spec question rather than a fix, added to the follow-ups: a host-restricting wrapper is invisible over a token-aware chain — WhiteListPolicy(RoundRobinPolicy) reports custom/WhiteListPolicy, but WhiteListPolicy(TokenAwarePolicy(…)) reports plain token-aware.

@nikagra

nikagra commented Aug 10, 2026

Copy link
Copy Markdown
Author

Rebased onto scylla-3.x and re-pushed as 00c97c08c7. Two things in this round:

connection.node-preference is now reported, having previously been deliberately omitted. The old rationale was that 3.x has one knob where the schema has two slots — but #968 has the same single-knob structure (distance() / computeNodeDistance is what scopes pooling) and fills the slot with the datacenter half, and gocql #987 fills it too, from HostFilter, at your request. So 3.x was the only driver leaving the key absent for equivalent configuration. It now carries the datacenter half alone: RackAwareRoundRobinPolicy.distance() returns REMOTE, never IGNORED, for a local-DC host in another rack, so those hosts are still pooled and reporting type:"rack" there would claim a restriction the driver does not apply. The five existing node-preference cases now assert both slots; the default report goes 821 → 858 bytes.

The previous head never ran CI. Tests (Driver 3.x) last ran on 6d1ced5c01; the force-push to a0e3cc0131 produced only the Jira-sync run, so the schema-revision migration in it was never built or tested by CI — the PR's green ticks were CodeRabbit, snyk and jira-sync. This push re-triggers the full set. Locally, from a clean target: 68 reporter tests, the whole unit group at 695, make check green on all 15 modules (JDK 11), and DriverConfigReportingCcmTest 3/3 live against ScyllaDB 2026.1 — the first live run of its byte-for-byte payload comparison.

The body also gains a Parity with the 4.x sibling section: of the four report-accuracy fixes from your latest #968 round, one is N/A (3.x has no client-configurable USING TIMEOUT), one was already the behaviour (nothing trims a local datacenter), one is structurally N/A (no runtime config reload), and one 3.x already had. The pool-sizes ask is blocked here by the same additionalProperties: false as in #968.

@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch 2 times, most recently from ac44ace to 088a03f Compare August 11, 2026 13:05
nikagra added a commit to nikagra/java-driver that referenced this pull request Aug 11, 2026
Motivation:

The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has
to be checked against the normative schema rather than against this driver's
own idea of the shape.

Modifications:

Vendors the schema block verbatim from the design doc, revision v5 -- whose
report version field is still 1 -- as a test resource. This is the same
resource the 3.x sibling PR scylladb#974 ships, so the two drivers are held to one
document.

Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x
line because it is the last minor line still targeting Java 8. The
conformance tests assert on this validator's exact ValidationMessage
wording, so a bump may need those strings updated; that is noted on the
version property in the parent pom.

Result:

The resource and the dependency are in place. Nothing consumes them yet --
the reporter and the conformance tests that validate against them follow in
later commits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra

nikagra commented Aug 11, 2026

Copy link
Copy Markdown
Author

Force-pushed ac44ace7f8088a03fe2a: history only. The single stage-2 commit is split into five per-concern commits; git diff ac44ace7f8 088a03fe2a is empty, so "Files changed" is byte-identical and every review comment still points at the same lines.

  1. 4a15deee5 test: ship the normative v1 driver-config schema and its validator
  2. b6eb90c2d feat(policies): expose the state the config report reads
  3. 4de52e5d5 fix: reject a null default consistency level in QueryOptions
  4. 0586fb909 feat: report the full driver configuration to the cluster at connection time
  5. 088a03fe2 fix: keep the config report off the connection path when Jackson is absent

Why now: this repo rebase-merges rather than squashes — #982's three commits landed on scylla-3.x as three — so PR history becomes branch history. One commit carrying 3.4k lines across 15 files buried the only public-API behavior change in the PR (QueryOptions.setConsistencyLevel now rejecting null), which is now #3 on its own.

Ordering note: the schema ships first, matching #968. That isn't cosmetic — assertConformsToSchema is called from ~45 sites spread through the whole test file, so schema validation is how these tests assert rather than a separable block of them.

The ~3000-word body of the old commit is distributed to the commit each part explains; the cross-cutting material (the catalogue of omitted keys, the three documented schema gaps) belongs in the PR description and I'll move it there rather than duplicating it per commit.

The previous head ac44ace7f8 is still reachable from the force-push entry in this PR's timeline.

@dkropachev dkropachev left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. connection.node-preference ignores built-in filters

HostFilterPolicy.fromDCWhiteList, fromDCBlackList, and WhiteListPolicy affect which hosts receive pools but are not reported. Retain filter metadata and use the existing schema when restrictions resolve to exactly one DC: one known DC → {"type":"dc","local-dc":"dc1"}; auto DC → {"type":"dc-auto"}; multiple or opaque restrictions → omit.

  1. query.defaults.page.size reports disabled paging incorrectly

QueryOptions rejects <= 0; paging is disabled by Integer.MAX_VALUE, currently reported as 2147483647. Report the field only when fetchSize > 0 && fetchSize != Integer.MAX_VALUE, and add a disabled-paging test.

  1. query.load-balancing.node-preference.inferred-* never appears

The report is built before policy initialization and cached forever. Build or refresh it after inferred DC/rack becomes available. Account for the initial STARTUP lifecycle limitation and test runtime inference.

  1. query.load-balancing.policy.adaptive-ordering requires token awareness

LatencyAwarePolicy(RoundRobinPolicy) performs adaptive ordering but reports none. Emit the capability independently of token awareness and add coverage.

  1. query.load-balancing.policy.name loses outer wrappers

Any inner TokenAwarePolicy hides an outer WhiteListPolicy, HostFilterPolicy, ErrorAwarePolicy, or custom wrapper. Name up name of all the involved policies, like so: TokenAwarePolicy(HostFilterPolicy)

nikagra added a commit to nikagra/java-driver that referenced this pull request Aug 11, 2026
Motivation:

The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has
to be checked against the normative schema rather than against this driver's
own idea of the shape.

Modifications:

Vendors the schema block verbatim from the design doc, revision v5 -- whose
report version field is still 1 -- as a test resource. This is the same
resource the 3.x sibling PR scylladb#974 ships, so the two drivers are held to one
document.

Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x
line because it is the last minor line still targeting Java 8. The
conformance tests assert on this validator's exact ValidationMessage
wording, so a bump may need those strings updated; that is noted on the
version property in the parent pom.

Result:

The resource and the dependency are in place. Nothing consumes them yet --
the reporter and the conformance tests that validate against them follow in
later commits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nikagra and others added 3 commits August 11, 2026 19:37
Motivation:

The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has
to be checked against the normative schema rather than against this driver's
own idea of the shape. Nothing in 3.x could do that.

Modifications:

Vendors the schema block verbatim from the design doc, revision v5 -- whose
report version field is still 1 -- as a test resource. This is the same
resource the 4.x sibling PR scylladb#968 ships, so the two drivers are held to one
document.

Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x
line because it is the last minor line still targeting Java 8. The
conformance tests assert on this validator's exact ValidationMessage
wording, so a bump may need those strings updated; that is noted on the
version property in the parent pom.

Result:

The resource and the dependency are in place. Nothing consumes them yet --
the reporter and the conformance tests that validate against them follow in
later commits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation:

The DRIVER_CONFIG report has to describe the policies a session is actually
running with, but the built-in policies keep their configuration in private
fields with no accessors. A preferred datacenter, a replica ordering or a
speculative-execution budget could not be observed from outside the policy
at all.

Modifications:

Adds public getters for exactly what the schema has a slot for:

- DCAwareRoundRobinPolicy: the local datacenter, whether it was set
  explicitly, and used-hosts-per-remote-DC.
- RackAwareRoundRobinPolicy: the local datacenter and rack, and whether
  each was set explicitly.
- TokenAwarePolicy: the replica ordering.
- ConstantSpeculativeExecutionPolicy: max executions and the delay.
- PercentileSpeculativeExecutionPolicy: max executions and the percentile.

The two speculative-execution policies hold these in immutable fields whose
values land in the schema's ranges exactly, so the report can name them by
their built-in type rather than falling back to "custom".

Also makes PagingOptimizingLoadBalancingPolicy implement
ChainableLoadBalancingPolicy. Cluster.Manager wraps every session's policy
in that internal class, so without a getChildPolicy() a chain walk stops at
the wrapper and never reaches the policy the user configured.

Result:

Pure widening -- no behavior changes. Every accessor returns what the policy
was constructed with.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation:

QueryOptions.setConsistencyLevel accepted null, and a null there could never
work: SessionManager falls back to the default consistency level for every
request that does not set one of its own, and CBUtil.writeConsistencyLevel
then dereferences it to write the frame. Any statement without an explicit
level already failed against such a configuration.

Modifications:

The setter now rejects null, like the other QueryOptions setters that back a
value every query needs.

setSerialConsistencyLevel is deliberately left as it is. Serial consistency
is genuinely optional, so a null there is a configuration that works, and it
is reported as an omitted key rather than as a missing required one.

Result:

A behavior change to a public setter, and the only one in this series. It
narrows the accepted input to what the driver could actually use; the
rejected configuration had no working behavior to preserve.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from 088a03f to 7232ac6 Compare August 11, 2026 17:44
@nikagra

nikagra commented Aug 11, 2026

Copy link
Copy Markdown
Author

Review round addressed, head 7232ac6391. All five points implemented; the default report is unchanged, still 858 bytes byte-for-byte. Also resynced the vendored schema to the v6 revision, which retires the one violation every 3.x report carried.

1. connection.node-preference ignores built-in filters. Fixed. HostFilterPolicy.fromDCWhiteList now retains the datacenters it was given (getWhiteListedDatacenters()), so fromDCWhiteList(new RoundRobinPolicy(), ["dc1"]) — nothing in the chain preferring a datacenter of its own — reports {"type":"dc","local-dc":"dc1"} in both slots. Same shape gocql #987 took from DataCenterHostFilter. A location-aware policy still wins over a filter above it, since it is what builds the query plan. Everything else stays omitted, per your "multiple or opaque restrictions → omit": a blacklist names no preferred datacenter, several allowed ones name no single one, WhiteListPolicy filters on addresses, and a caller-supplied predicate is opaque. fromDCBlackList deliberately retains nothing.

2. query.defaults.page.size reports disabled paging. Fixed — fetchSize != Integer.MAX_VALUE guards it now, so the group goes when paging is disabled, matching the schema's "absent when page is not limited" and the call you made on csharp-driver#263. The class javadoc already claimed this; the guard was > 0, which setFetchSize makes unreachable. There was no page-size test at all before; there are three now.

3. inferred-* never appears. Fixed, by matching #968: the report is no longer cached. Connection.Factory decides once whether a report can be built at all (the Jackson guard, now canBuildDriverConfigReport), and each control connection builds its own inside its STARTUP frame assembly. The first one still reports dc-auto with no datacenter — Cluster.Manager.init() sends that STARTUP before it calls LoadBalancingPolicy#init, so nothing is inferred yet — and every reconnect carries what the policy has since inferred. That lifecycle limitation is now stated in the class javadoc rather than being an unexplained hole. Tested both ways: unit tests drive DCAwareRoundRobinPolicy/RackAwareRoundRobinPolicy through their real init() and assert the second report differs, and DriverConfigReportingCcmTest forces a control-connection reconnect against a live cluster and asserts the datacenter appears, byte-for-byte against a rebuild.

4. adaptive-ordering required token awareness. Fixed — it is now reported wherever LatencyAwarePolicy appears, so LatencyAwarePolicy(RoundRobinPolicy) carries {"signals":["latency"]} without claiming load-distribution or fallback-to-non-preferred-nodes, which it cannot state. Adaptive ordering is a property of the chain, not of token awareness.

5. policy.name loses outer wrappers. Fixed, with one shape decision worth flagging. Each chain element is classified as describable or not; a chain the group can describe end-to-end still reports token-aware (the default report is untouched), and any other reports type: "custom" with the composed name you asked for — WhiteListPolicy(TokenAwarePolicy(DCAwareRoundRobinPolicy)) — keeping load-distribution / fallback-to-non-preferred-nodes / adaptive-ordering alongside it, which the custom branch admits as additional properties.

The name lands under custom rather than beside token-aware because the built-in variant is additionalProperties: false with no name key, and v6 does not change that. This is the same call you made twice on csharp-driver#263 (IdempotenceAwareRetryPolicy, RetryLoadBalancingPolicy → report the non-transparent wrapper as custom). It also gives ErrorAwarePolicy its first home: it excludes hosts over an error-rate threshold, a restriction rather than a reordering, so adaptive-ordering would be the wrong place for it despite the inviting response-rate signal — the name is the only place it can appear. If you would rather keep the token-aware discriminator and name the wrappers next to it, that needs name (or a wrappers array) on the built-in branch in the document, and I will switch the moment it lands.

Schema resync to v6. connection.requests.orphaned is optional now, which is exactly the 3.x case — a request the driver stops waiting for keeps its stream id until the response arrives, with no bound and no connection replacement. ORPHANED_REQUESTS_GAP is gone, assertConformsToSchema means "validates cleanly" again, and the class javadoc is down to two known gaps. #968 needs the same resync so the two vendored copies stay byte-for-byte equal.

Still open for the document, none of which v6 picked up:

  • percentile should be minimum: 0, not exclusiveMinimum: 0 — your own thread above; PercentileSpeculativeExecutionPolicy validates >= 0.0 && < 100, so 0 is accepted configuration with no schema-valid form. It is now the only gap a 3.x report can carry;
  • in-flight.max needs the same escape hatch downward: PoolingOptions accepts 0 and positiveInteger rejects it;
  • name on the built-in load-balancing branch, per point 5 above.

On the red lane: Cassandra ITs (3-LATEST, 8) failed on the previous head with one failure in 905, DCAwareRoundRobinPolicyTest.should_not_use_remote_hosts_if_some_nodes_are_up_in_local_dc (QueryTracker "Query fail"). The policy commit is pure widening — added getters and one implements — so I do not believe it is this diff, but worth confirming against the base rather than taking my word for it.

Verified locally on JDK 11: the whole unit group (707 tests, 0 failures) and make check (the full-reactor mvn verify -DskipTests CI's Full verify runs, clirr and animal-sniffer included) across all 15 modules.

@nikagra

nikagra commented Aug 11, 2026

Copy link
Copy Markdown
Author

Correcting my hedge on the red Cassandra ITs (3-LATEST, 8) lane — it is provable, not just likely. ac44ace7f8 and 088a03fe2a are byte-identical trees (git diff between them is empty; I checked locally rather than taking the description for it), and CI ran both: the first passed, the second failed on DCAwareRoundRobinPolicyTest.should_not_use_remote_hosts_if_some_nodes_are_up_in_local_dc. Same tree, opposite results — the lane is flaky, independent of this PR.

…on time

Motivation:

Stage 1 wired up DRIVER_CONFIG and sent a {"version":1} placeholder. This
fills in the report itself, in the normative cross-driver schema shape, so an
operator can see from the server what a client is actually configured with.

Modifications:

The report is built once per Cluster as it initializes, from Configuration
and Policies, and hangs off three groups. connection carries the connect/read
timeouts, the per-connection request capacity, the pool, the socket options,
the reconnection policy and -- only when TLS is on -- tls. control-plane
carries the system-query and schema-agreement timeouts. query carries the
per-request defaults plus the three policies acting on a query: retry,
load-balancing (with the node preference beside it) and, when configured,
speculative-execution.

The schema reports the node preference in two places and 3.x fills both from
the same policy chain: query.load-balancing.node-preference for what a query
is routed by, connection.node-preference for the part of the cluster the
driver holds connections to. One LoadBalancingPolicy decides both, since
distance(Host) governs whether a host is pooled at all, so the connection key
carries the datacenter half alone. A rack-aware policy's distance() returns
REMOTE, never IGNORED, for a local-datacenter host in another rack, so the
rack scopes no pooling at all; the datacenter does, a host outside the
preferred one being IGNORED unless the policy is configured to use hosts
there, and an ignored host gets no pool.

token-aware is the only built-in load balancing shape the schema defines, so
every other built-in policy -- a bare DCAwareRoundRobinPolicy,
RoundRobinPolicy, WhiteListPolicy -- is reported as custom with its class
name, which identifies it but carries none of the normalized flags. A
token-aware chain reports load-distribution from its replica ordering
(RANDOM, the 3.x default, is "shuffle"; TOPOLOGICAL is "replica-set";
NEUTRAL keeps the child's plan order, so "round-robin"). A custom policy is
named after the policy the user configured, not after
PagingOptimizingLoadBalancingPolicy, which Cluster.Manager wraps every
session's policy in and which is the outermost element of the chain; an
anonymous class falls back to its binary name, having no simple name where
the schema requires a non-empty one.

fallback-to-non-preferred-nodes is true whenever the policy can reach a node
outside the preference reported beside it. For DCAwareRoundRobin that means
used-hosts-per-remote-DC, since the preference is the datacenter.
RackAwareRoundRobin reports a rack, and the other racks of its local
datacenter are outside that yet are the second tier of every query plan, so
it is always true there, remote datacenter hosts or not.

in-flight.max needs a fallback because PoolingOptions is still UNSET when the
report is built: the protocol version is only negotiated once the control
connection is up. The default row is resolved with the same walk
PoolingOptions.setProtocolVersion applies -- the highest DEFAULTS key not
above the version -- driven by the version the user pinned with
withProtocolVersion when they pinned one, and by v3 otherwise, that being the
lowest version ScyllaDB negotiates. DEFAULTS holds only v1 and v3 rows, so a
cluster pinned to v2 is sized from v1's 128 rather than v3's 1024, and
pinning is the one part of negotiation knowable at report time.

Three ways reporting could break a connection rather than merely fail to
report are closed off:

- The report is capped at 32KiB of UTF-8 (MAX_DRIVER_CONFIG_LENGTH), matching
  the 4.x sibling PR scylladb#968, gocql scylladb#964 and csharp-driver scylladb#262. Beyond
  cross-driver parity this is a correctness fix: CBUtil.writeString writes
  each STARTUP value with a 16-bit length prefix and no bounds check, so a
  value over 65535 bytes truncates the prefix modulo 65536 while still
  appending the whole body -- a corrupt frame and a failed handshake, and not
  something the fail-safe try/catch can contain since nothing throws. Parts
  of the report are user-supplied and unbounded (DC/rack names, consistency
  levels, custom policy class names). Over the limit means WARN and no
  DRIVER_CONFIG.
- The fail-safe catch also covers InternalError, since customPolicy() calls
  getClass().getSimpleName() on arbitrary user policy objects (a documented
  JDK edge case for certain synthetic classes). Not a bare Error, so
  OutOfMemoryError and StackOverflowError still surface.
- The load balancing chain walk is bounded at 16 policies and shared by both
  callers. It follows getChildPolicy() on arbitrary user policies, so a
  cyclic chain used to spin forever on the Cluster initialization path -- the
  one failure mode a try/catch cannot contain, because it hangs rather than
  throws.

Result:

Every group the reporter can emit is validated against the normative schema
shipped earlier in this series, covering each discriminated-union branch and
optional group, with a negative test proving additionalProperties=false is
enforced.

Where a configured value falls outside what the schema can express, an
optional key or group is omitted rather than emitted as a value the schema
rejects; where a required key has no accurate value to carry, it is left in
the one state that is accurate and the limitation is documented on the class.
The assertion is therefore that a report violates the schema in exactly the
documented ways and no other. The PR description catalogues each omission and
the three schema gaps recorded for the cross-driver document.

Both node preference slots are an approximation once a wrapper sits above the
policy they were read from. HostFilterPolicy.distance() -- and so
WhiteListPolicy's, which extends it -- returns IGNORED for any host failing
its predicate, including one inside the reported datacenter, and a custom
chainable policy computes distance() itself and need honor nothing below it.
The configured datacenter is reported anyway, on the grounds that hiding one
the operator really did set is worse. The asymmetry is deliberate: nothing is
inferred on a third party's behalf, but what was configured is passed
through. The restriction has nowhere to go, the built-in shape having no room
for a wrapper and fromDCWhiteList collapsing its datacenters into an opaque
Predicate<Host>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from 7232ac6 to e449073 Compare August 11, 2026 19:58
@nikagra

nikagra commented Aug 11, 2026

Copy link
Copy Markdown
Author

Self-review round on the last one, head e449073b2f. Two bugs in the chain classification your point 5 introduced, both folded into commit 4 rather than added on top — still five commits, and git diff 7232ac6391..e449073b2f touches only DefaultDriverConfigReporter and its test. The default report is unchanged, still 858 bytes byte-for-byte.

1. A datacenter filter stayed invisible behind a token-aware chain — the exact blind spot point 5 closed, surviving for one element type. Each chain element is classified as describable or not; a single-DC HostFilterPolicy was classified describable in isolation, without checking that the preference reported beside it is that filter's restriction. So

new TokenAwarePolicy(fromDCWhiteList(DCAware("dc1"), ["dc2"]))

reported plain token-aware, no name, node-preference {"type":"dc","local-dc":"dc1"} — with the dc2 whitelist, which distance() enforces as IGNORED and which is disjoint from dc1, leaving nothing reachable at all, absent from the report entirely. Drop the TokenAwarePolicy and the same chain reported correctly (should_prefer_a_location_aware_policy_over_the_filter_above_it): nesting-dependent again.

Fixed by testing describability against the resolved preference rather than the filter alone — a filter is described only when it is the policy the preference was read from. That also fixes a sibling: a describable filter over DCAware(usedHostsPerRemoteDc > 0) reported fallback-to-non-preferred-nodes: true while the filter made every remote-DC host IGNORED. should_name_a_datacenter_filter_that_disagrees_with_the_policy_below_it pins it, and should_keep_reporting_token_aware_for_a_fully_representable_chain still pins the case that must keep working (fromDCWhiteList(TokenAware(RoundRobin), ["dc1"])token-aware, no name).

2. A blank whitelisted datacenter emitted a schema-invalid local-dc: "". Nothing validates the strings fromDCWhiteList is handed, and local-dc is nonEmptyString and required beside type: "dc" — so fromDCWhiteList(new RoundRobinPolicy(), [""]) put an invalid value in both preference slots. Every other DC/rack name in the report is already guarded the same way (DCAwareRoundRobinPolicy.getLocalDc(), RackAwareRoundRobinPolicy.getLocalRack()); the filter path was the one that wasn't. Now the whole group is omitted, as a fifth row in should_omit_the_node_preference_for_restrictions_that_name_no_single_datacenter. A merely padded name is still reported verbatim — hiding the whitespace would hide the typo the report exists to expose.

Both tests fail on the previous head (token-aware vs custom; preference present vs absent), so they are regression guards rather than restatements. The two checks are now one helper, singleWhiteListedDatacenter, since the duplicated predicate is what let the two answers diverge in the first place. The "Non-obvious derivations" bullet on filters in the description is updated to match.

Verified on JDK 11: DefaultDriverConfigReporterTest 81 tests, the whole unit group (708, 1 skipped), and make check across all 15 modules. DriverConfigReportingCcmTest is untouched — neither finding changes the wire format of any configuration it exercises.

@nikagra
nikagra requested a review from dkropachev August 11, 2026 20:05
dkropachev pushed a commit that referenced this pull request Aug 12, 2026
Motivation:

The DRIVER_CONFIG report is consumed as a cross-driver contract, so it has
to be checked against the normative schema rather than against this driver's
own idea of the shape.

Modifications:

Vendors the schema block verbatim from the design doc, revision v5 -- whose
report version field is still 1 -- as a test resource. This is the same
resource the 3.x sibling PR #974 ships, so the two drivers are held to one
document.

Validation runs on com.networknt:json-schema-validator, pinned to the 1.5.x
line because it is the last minor line still targeting Java 8. The
conformance tests assert on this validator's exact ValidationMessage
wording, so a bump may need those strings updated; that is noted on the
version property in the parent pom.

Result:

The resource and the dependency are in place. Nothing consumes them yet --
the reporter and the conformance tests that validate against them follow in
later commits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from e449073 to b6a01b2 Compare August 12, 2026 11:34
@nikagra

nikagra commented Aug 12, 2026

Copy link
Copy Markdown
Author

Self-review round against the merged siblings, head b6a01b24.

The merge-order argument in the description is dead. gocql#987, csharp-driver#263 and #968 all merged ahead of this branch, so version: 1 is already fixed and this PR conforms rather than leads. Re-checked by hash: the schema here is byte-for-byte equal to the copies #968 and #987 merged with. Shapes agree wherever the drivers do — connection.socket is rule-for-rule identical with 4.x, and all four now fill both node-preference slots. adaptive-ordering.signals differs on purpose, each driver naming what its own reordering consults.

One defect, in DriverConfigReportingCcmTest. The reconnect test forced triggerReconnect() on the class-level shared cluster; the superseded control connection lingers in system.clients until the server reaps its row, so two rows carry a DRIVER_CONFIG — which should_store_session_id_on_all_connections_and_driver_config_on_control asserts against. TestNG orders methods alphabetically, which runs the mutating test first and the asserting one last. It now uses a Cluster of its own, and the poll that waits for the new report is scoped to that cluster's SESSION_ID (unscoped, it would have returned the class-level session's control connection instead).

Also filed as a follow-up: csharp-driver#263 merged carrying the v5 schema — orphaned still in required — while the other three are identical at v6. Nothing it emits is invalid under either revision, so the drift is in the vendored contract only.

Unit group 708 run / 1 skipped / 0 failures from a clean target.

nikagra and others added 2 commits August 13, 2026 14:04
… can be

Motivation:

Two problems with where the report was built, both in Connection.Factory.

Stage 1 made jackson-core and jackson-databind required compile-scope
dependencies of driver-core, and DefaultDriverConfigReporter holds an
ObjectMapper in a static field. Exclude jackson-databind and merely
initializing that class raises NoClassDefFoundError. That is an Error, raised
while initializing the class rather than thrown from any method it declares, so
neither the reporter's own fail-safe nor its caller could contain it -- and it
happens on the Cluster initialization path. A classpath that merely lacks an
optional serializer therefore went from "the report is skipped" to "no
connection can be established at all", the inverse of the invariant this class
is written around.

Separately, the report was built once as the Cluster initialized and the
resulting string reused for every control connection that factory ever opened.
But Cluster.Manager.init() builds the factory, and the first control connection
sends its STARTUP, before it calls LoadBalancingPolicy#init -- so a datacenter
or rack the policy infers from the node it reaches could never be reported at
all. The schema has keys for exactly that (dc-auto.local-dc,
rack-auto.inferred-local-dc, rack-auto.inferred-local-rack) and nothing could
ever fill them.

Modifications:

Connection.Factory decides once, as the Cluster initializes, whether a report
can be built at all: canBuildDriverConfigReport catches LinkageError and answers
false. LinkageError rather than NoClassDefFoundError alone, so a partially
present or version-mismatched Jackson -- which surfaces as
ExceptionInInitializerError out of the static initializer -- is covered too;
probing for one class name would pass and then still fail here. Choosing whether
to touch the class at all is the only place the check can live. The report it
builds is discarded; running it there is what loads Jackson on the
initialization thread rather than leaving a Netty event loop to be first.

Logged at WARN, and unconditionally: reporting ships enabled, so nobody opted
into it and nobody would think to look for a message saying it is off.

The report itself is then built inside each control connection's STARTUP frame
assembly, and never cached. Connection carries a reportConfig flag instead of a
blob, and asks the factory for a report when it needs one.

This is the same fallback SnappyCompressor already applies for its own optional
library. It does not contradict buildReport() deliberately not catching bare
Error: that is about report building never masking a real JVM-level failure,
while this is a call site tolerating a missing optional dependency.

Result:

Excluding Jackson costs the report and nothing else, and a report describes the
objects in force at the handshake that sends it rather than the configuration
the Cluster was constructed from. The first control connection still reports
{"type":"dc-auto"} with no datacenter -- the policy genuinely has not inferred
one yet -- and every reconnect carries the one it has since inferred. A CCM test
forces a control-connection reconnect against a live cluster and asserts both
halves, byte-for-byte against a rebuilt report.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation:

Every public member the configuration-reporting feature adds is new and none of
it has shipped: stage 1 merged as 8ee8349, after the 3.11.5.17 release, so the
whole surface is still unreleased on 3.11.5.18-SNAPSHOT. The convention this repo
already applies to recent additions -- TabletMap, Metadata#getReplicasList,
Metadata#getTabletMap, BatchStatement#getRoutingStatement -- is Guava's @beta,
declaring the shape provisional until it has proven out across releases. This
feature was the one recent addition not marked.

Modifications:

Marks the API in the two granularities the tree already uses: a whole new type at
type level, as TabletMap is, and a new method on a pre-existing type at method
level, as Metadata and BatchStatement are.

Type level, both new types:

- DriverConfigReporter, which covers buildReport().
- DefaultDriverConfigReporter, which covers DRIVER_CONFIG_KEY, the constructor,
  buildReport(), and the protected buildJson(), populateConfig(ObjectNode) and
  configuration extension points -- the class is public and non-final, so those
  are part of the surface a subclass compiles against.

Method level, on types that predate the feature:

- Cluster.Builder#withDriverConfigReporting and its Configuration counterparts,
  isDriverConfigReportingEnabled() and Configuration.Builder#withDriverConfig-
  Reporting.
- The thirteen policy accessors this stage added to feed the report:
  ConstantSpeculativeExecutionPolicy and PercentileSpeculativeExecutionPolicy
  (max executions, delay, percentile), DCAwareRoundRobinPolicy and
  RackAwareRoundRobinPolicy (local datacenter and rack, whether each is explicit,
  used hosts per remote DC), HostFilterPolicy#getWhiteListedDatacenters,
  TokenAwarePolicy#getReplicaOrdering.

PagingOptimizingLoadBalancingPolicy is marked on getChildPolicy() rather than on
the class: the class is public API from 2018 and is not provisional, while its
implementing ChainableLoadBalancingPolicy at all is what this work introduced.

Result:

Behaviour is unchanged -- @beta is CLASS-retention, so nothing observes it at
runtime, and @documented, so it renders in the javadoc a caller reads. Callers of
the reporting API now see that its shape may change before it settles, and clirr
is unaffected because no signature moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nikagra
nikagra force-pushed the feature/driver-config-reporting-3x-phase2 branch from b6a01b2 to 960ab14 Compare August 13, 2026 12:08
@nikagra

nikagra commented Aug 13, 2026

Copy link
Copy Markdown
Author

Head 960ab14f1c, six commits; git diff b6a01b24ac..960ab14f1c is +45/-4 across 12 files.

  • @Beta on all 19 new public members, not just the 14 your threads name — stage 1 merged after 3.11.5.17, so its API is equally unreleased. Rationale in the PR body; this adds Cluster.java and Configuration.java to the diff, one import and one annotation each.
  • DriverConfigReporter#buildReport's javadoc still said the report is built once per Cluster and reused — commit 5 inverted that.
  • CodeRabbit's three untriaged comments: CCM asInt()/has() tightened; the other two are N/A (assertion since deleted) and declined (the two chain loops have diverged).

Unit group 708/0. Scylla ITs (LATEST, 8) is red on #946, unrelated.

@dkropachev
dkropachev merged commit 4a0d32a into scylladb:scylla-3.x Aug 13, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants