Repository navigation
Conversation
…keeps it transport-free The API a JVM worker programs against, and the one nine other languages will mirror: ParallelConsumerClient, ClientOptions, RecordProcessor and its asynchronous form, InboundRecord, OutboundRecord, Outcome and the Outcomes bridge that turns a misbehaving user function into a verdict instead of a broken session. What a user gets: a way to receive Kafka records and answer per record - success, optionally carrying records for Parallel Consumer to produce, or failure carrying a reason that travels with the redelivery - without the transport underneath appearing anywhere in the shape. Two Java transports implement it, and neither is visible from here. THE SURFACE IS DEPENDENCY-FREE, AND THAT IS THE PRODUCT DECISION RATHER THAN TIDINESS. A ByteString or a ConsumerRecord on this surface would not be a Java problem, it would be a specification problem: the mirroring languages have no such type, so the shape stops being expressible and the fan-out diverges silently, one language at a time, with nothing going red. ClientSurfaceArchTest fails the build on the bytecode when a transport, engine or Kafka type is referenced; the direct sibling's bannedDependencies enforcer fails it on the dependency tree. They are complements - the enforcer fires when a jar arrives, ArchUnit when a type is used through a jar that was already legitimately there. THE MODULE WAS A LEAF AND IS NOW AN AGGREGATOR, which its own pom on master said this rung would do. The HelloFixture class it compiled - the polyglot scaffolding's one deterministic line - is DELETED rather than relocated: a module that compiles a client surface and its suites is a strictly stronger statement about the JDK than a class that prints a string. The eight non-JVM modules keep theirs and bin/foreign-client-step.sh is untouched. SpotBugs gates every module beneath this aggregator - `check` rather than `spotbugs`, so a finding fails the build instead of writing a report nobody reads. It is bound at process-test-classes, NOT at the process-classes this arrived from: the root pom sets includeTests=true, and at process-classes target/test-classes does not exist yet, so a clean build analysed main code only and passed while the next incremental build failed the same tree. A gate whose verdict depends on what a previous run left on disk is not a gate. process-test-classes is the first phase after test-compile and is still before `test`, so bin/ci-unit-test.sh still reaches it. Extracted from #293 as extraction A7 of docs/plans/2026-08-31-001-process-god-branch-decomposition-plan.md, taken by path-scoped checkout rather than rewritten. The transport-parameterised conformance suite that lives in this module's test tree on that branch is NOT here - it belongs to the conformance rung stacked above this one. A COLLISION WITH A RULE MASTER GAINED AFTER THAT BRANCH WAS CUT, resolved rather than absorbed. EveryModuleWiresUpArchUnitTest requires every module with test sources to wire the shared TestConventionRules - which ships in core's test-jar, which this module may not depend on in any scope. So the module is named as its one exemption, with the reason at the site, and the exemption is GUARDED: a second assertion beside it requires an exempt module to still run some @AnalyzeClasses wrapper of its own, so deleting ClientSurfaceArchTest turns the exemption back into a failure instead of into silence. Proven by deleting that test and watching the guard go red, with a control arm either side. The full unit suite is what found this; the module-scoped run was green.
…re in-process The degenerate transport: the shared surface bound straight to the engine that has always been on master, with no sidecar, no wire and no protocol underneath. The user's RecordProcessor runs on core's own worker threads and its Outcome maps onto core's success and retry paths, so ordering, retry and commit semantics are core's, untouched. For a user this is the cheapest way to program against the client surface - the same code that will later run against a sidecar, running in the same JVM today. For the project it is the control arm: a scenario that passes over the wire and fails here is a claim about the API rather than about a stream. "THE ENGINE" HERE IS parallel-consumer-core, NOT THE SIDECAR'S DISPATCH ENGINE, and the distinction is what let this land on a branch where the sidecar hosts nothing. Nothing in this module reads a line of parallel-consumer-proxy; its pom bans protobuf, gRPC and the protocol module from the classpath in every scope, and that ban passing is the shortest proof of the claim. Evidence, and it asserts the offset rather than the callback: the processor being invoked proves hand-out and nothing about whether the verdict was applied, and a transport that ran the user function and dropped its answer looks identical from the processor's side. So the success arm asserts core's committed offset and the producer's history, the failure arm asserts the redelivery carries the reason the previous attempt gave, and both were proven able to fail by breaking one term at a time - dropping the produce list on its way into core, and applying a failure Outcome as a success - with a control arm either side and the tree restored byte-identical. CommitHistory.forPartition comes from the same source branch and is why that evidence is a value rather than an assertion: the class's constructor wants a flat per-partition list, and until this existed the only code producing one lived inside a Truth Subject - so a fixture that needs a number to poll on with Awaitility hand-wrote the same reverse scan. Only the static factory is taken; the source branch's wider tidy-up of that class is left where it is. Two fb-contrib findings were real and were fixed rather than excluded - an identity lambda, and an overly concrete collection parameter. The third is declined in the module's own filter, because its remedy is forbidden here: CE_CLASS_ENVY on the record translation points at moving the method onto core's RecordContext, and core must not know a client API exists - the mirror of that rule is what ClientSurfaceArchTest enforces from the other side. Extracted from #293 (extraction A7). The cross-language conformance suite that also drives this transport arrives with the conformance rung above.
… a sidecar The same surface over the RPC hop: one bidirectional stream, Configure as its first message, and from Configured onward dispatched records handed to executors and their per-record reports sent back. It is the JVM reference implementation of the wire, and the shape Kotlin and Scala are meant to wrap rather than reimplement. What a user gets that the direct transport cannot give them: their processing running wherever they put it, against a Parallel Consumer that is somewhere else. What every later language gets: a session implementation that has already been debugged once. THE CLIENT STORES NOTHING PER RECORD. A dispatch's fencing token rides from the queue to the report on the executor's stack and is echoed byte for byte - no request map, no dedupe cache, no completion state - because a stateless client cannot have a state bug, and every language mirroring this inherits that rather than inventing its own bookkeeping. WireMappingTest pins the echo without a connection; SessionEndTest pins the two ways a session ends badly, both of which were red before the change that added them on the source branch: a mid-session stream error used to hang with every executor parked and nothing on the surface able to report it, and closing mid-processing used to transmit an interrupted user function's failure as a verdict the user never gave. IT DEPENDS ON parallel-consumer-proxy IN NO SCOPE, and that is load-bearing rather than tidy. A test-scope dependency is not on the classpath but IS an edge in the Maven reactor, so a client wrapping this one would build the sidecar - and bin/build.sh opens with `clean`, so the routine build of a wrapper would delete the sidecar jar other languages' tests spawn. Anything needing a running sidecar lives in the harness lane instead. Two ignored CountDownLatch.await results in SessionEndTest were a real hole and are fixed rather than excluded: a timed-out await returned the same success both tests assert, so the run could have gone green having measured nothing. Four findings are declined in the module's own filter with the argument for each, the two that generalise being that widening a parameter to its *OrBuilder supertype would let a caller obtain an API value for a message that was never on any wire - the same call #383 made for the protocol module's own bridges - and that making the negotiated in-flight ceiling static would share one session's number with every other client in the JVM. The gRPC graph's two pins - error_prone_annotations for requireUpperBoundDeps, gson forward off the version OSS Index matches for CVE-2025-53864 - are declared once on the Java aggregator rather than in this pom and the harness's. parallel-consumer-proxy's pom owns the reasoning and keeps it; what is new is only that this aggregator has two children needing the same pin and dependencyManagement does not flow between siblings. It stays out of the root pom, where ${error-prone.version} pins the Error Prone COMPILER for an unrelated and temporary reason. Extracted from #293 (extraction A7).
…ly a scripted one
The harness lane, and the one assertion no in-JVM fake can make for a client library: the gRPC
transport's own connect path, against a sidecar started through the sidecar's own entry point -
real bind, real authority allowlist, real admission slot, real session service.
Because that sidecar hosts no Parallel Consumer engine, the run ends where its honesty ends: the
client is told UNIMPLEMENTED, in the protocol's own words, and reports that to its caller from both
connect() and sessionEnd(). That is a smaller claim than "the client works", and it is written down
as the smaller claim rather than dressed up.
THE STATUS CODE IS THE ASSERTION, NOT "IT FAILED". The authority allowlist answers PERMISSION_DENIED
and the admission slot RESOURCE_EXHAUSTED, both from interceptors that run BEFORE the service
method, so only UNIMPLEMENTED can have come from the service - which is what distinguishes "the
handshake was delivered" from "the connection was turned away". A second test points the same client
at a port nothing is listening on and asserts the failure is NOT that one, so the first cannot pass
on any error at all.
Proven able to fail, one term at a time, with a control arm either side and the tree restored
byte-identical afterwards:
CONTROL untouched tree 2 tests pass
ARM 1 the client declares an :authority the allowlist does not
list (overrideAuthority on the channel) expected UNIMPLEMENTED
but was PERMISSION_DENIED
ARM 2 the no-engine service accepts the session silently
instead of refusing it no refusal in 30s;
TimeoutException
RESTORED control arm again 2 tests pass
Arm 1 is the one worth reading: it shows the assertion is about reaching the SERVICE rather than
about reaching the server, which is exactly what a green run cannot tell you.
IT IS A MODULE RATHER THAN A TEST SCOPE, and that is inherited from #293
rather than decided here: a test-scope dependency is not transitive on the classpath but is an edge
in the Maven reactor, so putting this test in the transport module would drag the sidecar into the
ordinary build of anything wrapping it - and bin/build.sh opens with `clean`, so that build would
delete the sidecar jar other languages' tests spawn. The measurement is a reactor list rather than
an assertion, and the pom says which command to run.
Its one SpotBugs finding is declined: the socket findsecbugs wants encrypted is opened purely so
the OS allocates a port number and is closed before anything connects to it.
The engine-backed conformance run, and the compile-scope harness classpath a JVM client declares to
spawn a sidecar as a child process, arrive with the conformance rung stacked on this one - this
module is where they land.
… it is now evidenced for The records that make the Java client's claims checkable by someone who did not write it, and correct the ones this change made false. docs/client-static-analysis.md is the policy the Java modules' poms and their exclusion filters all cite by name, and it did not exist on master: which tool, why that one, what it catches, what fails the build, and the exact local command with the three silent traps baked into it - naming the aggregator instead of the leaves analyses nothing and reports BUILD SUCCESS; dropping the quarantined exclusion makes you debug someone else's known flake; omitting -am fails at validate. It arrives trimmed to what exists here: Java filled in, the protocol row pointing at docs/ci.md which owns that gate, and the other ten languages named as arriving with their own clients rather than left as apparent gaps. The severity policy is the part worth arguing with - everything fails, nothing warns, dismissal at the finding with a reason - and it is deliberately stricter than the repo-wide SpotBugs lane, because these modules start at zero findings and that is the one moment a clean-slate gate is free. The maturity and testing-evidence records say in both directions what is and is not evidenced, and the limitation is the sentence that matters: NO RECORD HAS CROSSED THE WIRE. The direct transport is evidenced end to end because the engine it binds is ordinary; every gRPC assertion past the handshake is against a proxy the test itself scripts, which can prove how the transport reacts and cannot prove a real engine would ever behave that way. Two claims elsewhere became false with this change and are corrected rather than left to rot: the clients directory's README said none of its modules was a client, and AGENTS.md's module table said the whole tree was build scaffolding.
Dependency ReviewThe following issues were found:
License Issuesparallel-consumer-proxy-clients/parallel-consumer-proxy-client-java/parallel-consumer-proxy-client-java-direct/pom.xml
parallel-consumer-proxy-clients/parallel-consumer-proxy-client-java/parallel-consumer-proxy-client-java-grpc/pom.xml
parallel-consumer-proxy-clients/parallel-consumer-proxy-client-java/parallel-consumer-proxy-client-java-harness/pom.xml
OpenSSF ScorecardScorecard details
Scanned Files
|
✅ Duplicate Code ReportTwo engines run in parallel for cross-validation. Each has its own thresholds tuned to its baseline - the real safety net is the per-engine "max increase vs base" check. ✅ PMD CPD
No new clones introduced by this PR. ✅ jscpd (language-agnostic)
|
6 tasks done
🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its |
|
… wrote bin/check-pr-analysis-surfaces.sh 386 reported two findings on lines this branch wrote, both from SpotBugs and both in parallel-consumer-core's test tree - the two files this extraction touched outside the client modules. Nothing inherited was reported. One is fixed, one is declined with the argument at the site rather than in a PR body nobody re-reads. FIXED - the exemption registry is a Set, because membership is what it is for. EveryModuleWiresUpArchUnitTest's exempt-module list was a List used only with contains() and a for-each, which is fb-contrib's "defines List based fields but uses them like Sets". The detector is right and the type was simply the first one that came to hand: nothing about this registry is positional, and a Set says so. DECLINED - CommitHistory.forPartition's first parameter must stay a List, though a Collection would compile. Everything that class answers is positional: highestCommit() and getEncoding() read the LAST element, which is the most recent commit, so encounter order is the whole meaning of the input. A Collection parameter would accept a set and return an arbitrary commit while looking correct, which is worse than not compiling. The reason now sits in that method's javadoc, so the next reader meets it before the finding rather than after. Not a general position on the pattern: the same OCP_OVERLY_CONCRETE finding on DirectParallelConsumerClient.toProducerRecords was FIXED earlier on this branch, because that method only iterates and its parameter carries no ordering promise. The difference is whether order means anything, not whether the detector is fashionable.
…lves-for-itself miss
Integration Tests went red on this PR with ZERO tests run. The job built every module through
parallel-consumer-proxy, reached the Java client's SpotBugs gate, tried to fetch
com.mebigfatguy.fb-contrib:fb-contrib:7.7.4 from Maven Central, and hung until it gave up:
[ERROR] Could not transfer artifact com.mebigfatguy.fb-contrib:fb-contrib:jar:7.7.4
from/to central (https://repo1.maven.org/maven2/): Read timed out
WHAT IT IS, AND IT IS NOT NEW - it is the third instance of a class this workflow already names.
docs/solutions/build-errors/maven-central-timeout-azure-west-regions-2026-04-21.md records that
Maven Central's CDN routes badly from some Azure regions, that each attempt hangs exactly 240
seconds, and that re-running does not help because the region assignment is what differs. The
signature here matches it term for term: the module failed at 04:07, "Unit Tests" fetched the same
jar from another runner without trouble in the same run, and no test anywhere in the job failed.
WHY IT REACHED THE TEST MATRIX ONLY NOW, which is the part this PR owns. `dependency:go-offline`
resolves declared dependencies and plugin JARs, but not what a plugin resolves FOR ITSELF at
execution time - and SpotBugs' detector plugins are declared under <configuration><plugins>, so
go-offline never sees them. That cost nothing while SpotBugs ran only in its own `static: spotbugs`
job. Binding spotbugs:check into the DEFAULT reactor, which the Java proxy client does, makes every
lane of the `test` matrix resolve them, and a cold cache then enters the route lottery three times
over.
The prepare-deps job already warms two instances of exactly this class - protobuf-maven-plugin's
protoc executables, and scala-maven-plugin's compiler and zinc bridge - each with the same
reasoning written above it. This is the third, warmed the same way and with the same two guards:
the version comes from the pom property rather than a literal that would rot on the next bump, a
non-version answer from help:evaluate is a hard error rather than a warm of the wrong coordinate,
and the jar is asserted ON DISK afterwards, because the goal's exit code says it ran while only the
file says the cache got populated.
Verified by extracting the step from the workflow and running it: exit 0 with all four jars present.
Proven able to fail by renaming one property, which produced the intended error rather than a silent
no-op:
::error::fb-contrib.renamed did not resolve to a version from the root pom
(got 'null object or invalid expression') - the SpotBugs detector cache warm would be
a silent no-op
Nothing is skipped, disabled or retried to make the lane green: the gate still runs in every lane it
ran in before, and what changes is only that the artifacts it needs are already on disk.
5 tasks done
astubbs
marked this pull request as draft
August 31, 2026 12:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #242.
depends on #384
Description
The Java reference client: one shared surface, an in-process transport, a gRPC transport that
speaks the frozen v1 wire, and a lane that proves the second one reaches a real sidecar. One
canonical client before the foreign ones - the shape ten other languages mirror, decided once, in
the language where it can be tested hardest.
The claim this PR makes, and what to check it against. The direct transport carries a record end
to end and applies its verdict: the offset moves and the produce payload lands, because a transport
that runs the user function and drops its answer looks identical from the processor's side. The gRPC
transport speaks the wire and its handshake genuinely reaches a real sidecar's session service -
which is a narrower claim than "it works", and is written down as the narrower claim. Both were
verified by breaking one term at a time; five sabotage arms with control arms either side are below.
Where this came from
Extracted from #293, the language-proxy god PR, as extraction A7 of
docs/plans/2026-08-31-001-process-god-branch-decomposition-plan.md- the rung above#384, whose "deliberately NOT in this rung" list leaves exactly this. Taken
by path-scoped checkout rather than rewritten; every place it differs is named below.
The direct transport IS here, and that is the decision worth reading first
The dispatch order for this rung assumed the direct transport might need the sidecar's engine, and
would have to be left behind if it did. It does not.
DirectParallelConsumerClientcompilesagainst
parallel-consumer-core-ParallelEoSStreamProcessor,ParallelConsumerOptions,RecordContext- and against nothing inparallel-consumer-proxyat all. "The engine" it drives iscore, which has been on
mastersince before any of this existed; the sidecar's dispatch engine isa different thing and this module has no way to reach it.
The evidence is not a reading of the imports. Its pom's
bannedDependenciesrule refuses protobuf,gRPC and the protocol module on that classpath in every scope, and the module builds and its
tests pass with that ban in force. So the rung's claim does not have to shrink: both transports are
here, and the one that could not be finished without an engine - the engine-backed conformance run -
is the one that is left for A8.
What is in it
java-api-ParallelConsumerClient,ClientOptions,RecordProcessorand its asynchronousform,
InboundRecord,OutboundRecord,Outcome, and theOutcomesbridge that turns every waya user function can misbehave into a verdict rather than a broken session. Dependency-free, and
that is a product decision rather than tidiness: a
ByteStringor aConsumerRecordhere would notbe a Java problem but a specification problem, because the mirroring languages have no such type.
Two gates from opposite directions hold it -
ClientSurfaceArchTestreads the bytecode and fireswhen a type is referenced; the direct sibling's enforcer ban reads the dependency tree and fires
when a jar arrives.
java-direct- core bound in-process. The user's function runs on core's own worker threadsand its
Outcomemaps onto core's success and retry paths, so ordering, retry and commit semanticsare core's, untouched.
java-grpc- one bidirectional stream,Configurefirst, dispatched records to executors andtheir reports back. The client stores nothing per record: the fencing token rides from queue to
report on the executor's stack and is echoed byte for byte, so a stateless client cannot have a
state bug and every language mirroring it inherits that instead of inventing bookkeeping.
java-harness- not a product module. The one place on the JVM side that depends on thesidecar, so a client wrapping a transport does not drag it into the reactor.
Deliberately NOT in this rung
Named so neither side reads as an oversight: the conformance framework - the
transport-parameterised scenario suite, its fixture interface, the per-transport subclasses and core
as the control arm - is A8's, and A8 stacks on this branch. The engine-backed harness run and the
compile-scope harness classpath a JVM client declares to spawn a sidecar land in the harness module
when it arrives. The ten foreign clients are A9. Nothing here negotiates leases, heartbeats,
reconnect, worker-death reporting, terminal outcomes or the shutdown drain -
java-grpcdeclares["dispatch"]and nothing else, which is un-negotiated rather than half-built.Five sabotage arms, with a control on either side
Each arm changes one term, runs the test that should break, and restores from a pristine copy;
the restored file was diffed byte-for-byte against the copy each time.
Arm 1 is the one that matters most. It is what separates "the wire reached the service" from
"the wire reached the server", and a green run cannot tell you which you have. The authority
allowlist answers
PERMISSION_DENIEDand the admission slotRESOURCE_EXHAUSTED, both frominterceptors that run before the service method - so
UNIMPLEMENTEDis the only code the serviceitself can raise, and asserting the code rather than "it failed" is what makes the test mean
something. A permanent control arm sits beside it: the same client pointed at a port nothing is
listening on, asserted to fail differently.
Arm 2's shape is the finding, not its colour. Removing the refusal does not fail fast; it hangs,
and the assertion lands on its budget after 40 seconds. That is what a client author would hit
against a silently-accepting sidecar.
How it was verified
JDK 17, macOS, on this branch:
./mvnw cleanthen a full-reactor-Pci install- BUILD SUCCESS, every module including thefour new ones.
the asynchronous processor's whole failure contract; the direct transport's four; the gRPC
transport's wire mapping and its two session-end cases; the handshake pair.
bin/ci-unit-test.sh, the full unit suite - which is what found the collision below. Amodule-scoped run was green while the whole-tree run was not.
bin/check-all.shon its real exit code: 0. Two gates report CANNOT RUN -check-proto-breaking.shandcheck-proto-lint.sh, both wanting abufthis machine does nothave. Both belong to a base rung, neither is affected by this diff, and CI is where they run.
COPYRIGHT_CHECK_REQUIRE_FORK_POINT=1 bin/check-copyright-headers.sh- 0 violations against areal fork point.
Commits were made with
--no-verify, and the reason is recorded rather than assumed. Thepre-commit hook resolved the wrong project directory and gated this branch against a different
worktree's tree - it reported dangling citations in
bench/README.md, a file that does not exist on thisbranch at all.
bin/check-file-refs.shrun in this worktree reports zero, andbin/check-all.shexits 0. The harness fix is in flight on #382.
A rule master gained after the source branch was cut
EveryModuleWiresUpArchUnitTestrequires every module with test sources to wire the sharedTestConventionRules. That rule does not exist onfeats/proxy-requirements, so the api modulenever had to satisfy it - and it cannot: the rule library ships in core's test-jar, and that
module is dependency-free on core in any scope, which its pom states and the direct sibling's
enforcer ban exists to keep true.
Resolved by naming the module as that test's one exemption, with the reason at the site - and by
guarding the exemption, which is the part worth reviewing. A second assertion beside it requires
an exempt module to still run some
@AnalyzeClasseswrapper of its own, so deletingClientSurfaceArchTestturns the exemption back into a failure rather than into silence. Arm 5above is that guard going red. The alternative - putting core's test-jar on the api module's test
classpath - would have worked and is deliberately not taken, because the "dependency-free" invariant
is also what the module's SpotBugs filter argues from; if you would rather have the dependency than
the exemption, this is the place to say so.
Five places this differs from #293 on purpose
process-classestoprocess-test-classes.master's root pomsets
includeTests=true, which postdates that branch - and atprocess-classes,target/test-classesdoes not exist yet. A clean build analysed main code only and passed; thenext incremental build, with test classes left on disk, failed the same tree with two findings.
A gate whose verdict depends on what a previous run left behind is not a gate. The new phase is
still before
test, so every reason the original phase was chosen still holds.direct pom's and READMEs' descriptions of it are rewritten to say where it is rather than
describing a tree that does not exist.
CommitHistory.forPartitionis taken, and the rest of that class's rework is not. The staticfactory is what the direct transport's test needs and is the reason its evidence can be a value
polled on with Awaitility rather than an assertion; the source branch's wider tidy-up of the same
class belongs with whatever else wanted it.
modules that resolve them.
parallel-consumer-proxy's pom owns the reasoning and keeps it; whatis different here is only that this aggregator has two children needing the same pin and
dependencyManagementdoes not flow between siblings. It stays out of the root pom, where${error-prone.version}pins the Error Prone compiler for an unrelated and temporary reason.HelloFixtureis deleted rather than relocated. The scaffolding rung's own pom said this rungwould "convert this module and move this class out"; a module that compiles a client surface, two
transports and their suites is a strictly stronger statement about the JDK than a class that
prints a string. The eight non-JVM modules keep theirs and
bin/foreign-client-step.shisuntouched.
Two smaller ones: the protocol specification and client-authoring guide moved to the protocol module
in #383, so every README link that pointed into
parallel-consumer-proxy/docs/is repointed; and
docs/client-static-analysis.mdarrives trimmed to the rows that exist here, withthe remaining languages named as arriving with their own clients rather than left as apparent gaps.
Three things this PR found
A gate that disagreed with itself between a clean build and an incremental one - item 1 above.
The discovery order is what makes it worth naming: the clean run passed, so the natural conclusion
was that the incremental failure was noise.
Three fb-contrib findings in the direct transport, two of them real. An identity lambda and an
overly concrete collection parameter were fixed. The third is declined because its remedy is
forbidden here:
CE_CLASS_ENVYon the record translation points at moving the method onto core'sRecordContext, and core must not know a client API exists - the mirror of that rule is exactly whatClientSurfaceArchTestenforces from the other side.Two ignored
CountDownLatch.awaitresults inSessionEndTestwere a real hole. A timed-outawait returned the same success both tests assert, so the run could have gone green having measured
nothing. Fixed rather than excluded, by throwing when the release never comes - which lands as the
wrong verdict and fails the assertion loudly.
The declined findings, and where each argument lives
Every module carries its own
spotbugs-exclude.xml; every entry names a class and a pattern, most amethod or a field too, with the argument written beside it. The two that generalise beyond one line:
widening a parameter to its
*OrBuildersupertype would let a caller obtain an API value for amessage that was never on any wire (the same call #383 made for the protocol
module's bridges), and making the negotiated in-flight ceiling
staticwould share one session'snumber with every other client in the JVM.
One NullAway warning remains on a line this diff wrote:
OutboundRecord.of("responses", null, ...)passes a null key deliberately, which is the API'skeyless record and which
WireMappingTestasserts stays absent on the wire. NullAway is repo-wideadvisory and no file in this tree carries a suppression for it; starting that practice is not this
PR's call.
The duplication reports, read
Both engines pass. PMD CPD reports zero new clones and no movement at all in its percentage.
jscpd names four, and none of them is duplication this PR should remove:
commons-lang3dependency and the comment above it sayingwhy it is declared explicitly rather than leaned on transitively (core's test-jar reaches for it,
and test scope is not transitive). One of the pair in each case is
parallel-consumer-proxy/pom.xml, which is on the base branch: the repetition is the establishedanswer to a Maven fact, and the comment is what makes each copy readable on its own. Collapsing it
would mean a parent declaring a test dependency for children that may not want it.
GrpcParallelConsumerClientand the test that drives it -the same pair feat(proxy) astubbs#242: a sidecar giving non-JVM runtimes key-ordered concurrency, in eleven languages #293's own report names. A test importing the protocol types
its subject uses is not duplication with a fix.
The analysis surfaces, read
bin/check-pr-analysis-surfaces.sh 386reported two findings on lines this diff wrote and noneinherited, both from SpotBugs and both in the two
parallel-consumer-coretest files thisextraction touched outside the client modules. One fixed, one declined with the argument at the site:
EveryModuleWiresUpArchUnitTest's exempt-module registry was aListused only withcontains()and a for-each, which fb-contrib reports as "defines List based fields but uses themlike Sets". It is right: nothing about that registry is positional, and the type was simply the
first one to hand. It is a
Setnow.CommitHistory.forPartition's first parameter could be aCollection, and mustnot be. Everything that class answers is positional:
highestCommit()andgetEncoding()read theLAST element, which is the most recent commit, so encounter order is the whole meaning of the
input. A
Collectionparameter would accept a set and return an arbitrary commit while lookingcorrect - worse than not compiling. The reason is now in that method's javadoc, so the next reader
meets it before the finding. This is not a general position on the pattern: the same
OCP_OVERLY_CONCRETEfinding onDirectParallelConsumerClient.toProducerRecordswas fixedearlier on this branch, because that method only iterates and its parameter promises no order.
The module-scoped SpotBugs gate this PR adds is stricter than the repo-wide
static: spotbugslaneand is green. One NullAway warning on a line this diff wrote is named in the section above and
declined.
Re-read on the fixed head, once
static: spotbugshad run on it, and it says what a fix and adecline are supposed to say: the
EveryModuleWiresUpArchUnitTestfinding is gone, theCommitHistoryone is still reported, and nothing is inherited. That is the expected state, notan outstanding item - a decline does not silence a detector, it answers it, and the answer is in the
method's javadoc. Reviewers who disagree with the decline should say so there.
(An earlier re-read returned no annotations at all. That was not a clean bill of health, it was
SpotBugs not having run on that head yet - the distinction the script's own closing note insists on.)
Integration Testswent red, and it was not a testZero tests failed anywhere in that job. It built every module through
parallel-consumer-proxy,reached the Java client's SpotBugs gate, and hung fetching a detector jar from Maven Central:
It is the third instance of a class this workflow already names.
maven-central-timeout-azure-west-regions-2026-04-21.mdrecords that Central's CDN routes badly from some Azure regions, that each attempt hangs exactly
240 seconds, and that re-running does not help because the region assignment is the variable. The
signature matches term for term - 04:07, and
Unit Testsfetched the same jar from another runner inthe same run without trouble.
Why it reached the
testmatrix only now is the part this PR owns:dependency:go-offlinedoes notfetch what a plugin resolves for itself, and SpotBugs' detectors are declared under
<configuration><plugins>. That cost nothing while SpotBugs ran only in its ownstatic: spotbugsjob; binding
spotbugs:checkinto the default reactor makes every lane resolve them.prepare-depsalready warms two instances of exactly this class - protobuf'sprotocexecutablesand scala-maven-plugin's compiler and zinc bridge, each with its reasoning written above it. This
adds the third, the same way and with the same two guards: the version comes from the pom property
rather than a literal that rots on the next bump, a non-version answer from
help:evaluateis a harderror rather than a warm of the wrong coordinate, and the jars are asserted on disk afterwards.
Verified by extracting the step from the workflow and running it (exit 0, all four jars present), and
proven able to fail by renaming one property, which produced the intended
::error::rather than asilent no-op.
Nothing was skipped, disabled or retried to make the lane green - the gate still runs everywhere
it ran before; only the artifacts it needs are now already on disk.
The control arm: the same suite passes.
bin/ci-integration-test.shwas run locally against thisbranch, on a machine where Maven Central is reachable - same code, same commands, one term different -
and it is BUILD SUCCESS end to end. So the lane's red was the artifact fetch and nothing about the
diff, which is what a re-run alone could never have told you apart from luck.
Known-red, and not a fault
Check PR Dependenciesis red until feat(proxy) astubbs#242: the sidecar as a real process - binds, serves its lifecycle, and shuts down cleanly #384 and its own parents merge.That is the dependency gate doing its job on a stack rung.
claude-reviewis red until somebody asks for a review; every rung below is in the same state.Chaos Pain Suite,Unit Tests,Performance Testsand every other lane passed on the first run.The one real red,
Integration Tests, is diagnosed above and fixed at its cause.Everything else has run on this head and passed, self-hosted lanes included:
Unit Tests,Integration Testsin 9m18s - the lane that was red, now green with the cache warm in place,which is the confirmation that the fix above works rather than a hope that it does -
Performance Tests,Chaos Pain Suiteandstatic: spotbugs. Thecachejob carrying the fix passed too, withits
Warm SpotBugs' detector pluginsstep succeeding on a real runner and saving the warmed jars.Those lanes took a while to get there because the six
highcpurunners spent the day flapping in andout of
status=offline- a fleet condition affecting every open PR, not a signal about this one.roadmap-stage: N/A - the
language-proxy-sidecarentry names #293 as its carrier and sits atlimited-pocon the strength of that PR's content. Landing a client library on master makes no more advanced artifact exist - there is still no engine for it to talk to - so its stage is unchanged.Checklist
docs/client-static-analysis.md(new, and cited by name from four poms and anArchUnit test), the four module READMEs, the clients directory README and
AGENTS.md's moduletable - both of which claimed the whole tree was build scaffolding, which this change makes false -
and the module-maturity and testing-evidence records.
docs/features/- N/A - nothing here is auser-facing feature. Nothing is published to any registry (the clients tree skips deploy and
install), and there is no engine on
masterfor the gRPC transport to talk to; therelease-documentation records this does earn are the maturity and evidence rows, which are in this
PR and say in both directions what is and is not evidenced.
api and gRPC suites come with their modules. Five arms were proven able to fail by sabotage, with
control arms on either side, above.
ce-simplifyandce-code-reviewlocally - N/A - neither was run. Most of the diff is codetaken from feat(proxy) astubbs#242: a sidecar giving non-JVM runtimes key-ordered concurrency, in eleven languages #293 where it was already reviewed, and the changes this rung
made to it are named one by one above; the cheaper place to spend a review is
@claude review thison the PR, which is also the only route that opens inline threads.