Repository navigation
Conversation
…ve the real binary The sidecar shell compiles and runs as a native executable. bin/native-image-sidecar.sh builds parallel-consumer-proxy's Main into one and then drives that binary - not the code, the binary - through the whole spawning contract: it refuses arguments, binds an ephemeral loopback port, announces it on stdout line one, answers a real gRPC session with UNIMPLEMENTED naming the missing engine, exits 0 when its parent's write end closes, and leaves the socket released. WHAT A USER GETS: the artifact stops being a JVM artifact. The whole proposition of the sidecar is that a Go, Python or Ruby team is handed an executable rather than a JVM dependency, and this is the first place in the repository where that is a thing you can build and run rather than a claim. MEASURED, macOS/arm64, Oracle GraalVM 23: a 27MB Mach-O binary in ~40s of native-image time, all four lifecycle arms green, started with an empty environment (no JAVA_HOME, no PATH). THE RECIPE IS ONE FLAG, AND THE SHORTNESS IS THE FINDING. --no-fallback, no --initialize-at-build- time, no META-INF/native-image metadata at all. That is not this rung being clever; it is this rung having no engine. The same build on feats/native-image-sidecar needed logback initialised at build time and a vendored 1,049-line reachability capture, because that tree carries the Kafka client and a logging backend - the two things closed-world analysis cannot follow. Neither is on this module's runtime classpath, so neither obstacle exists here, and adding configuration for them anyway is not free: on that branch, ADDING reachability metadata broke a build that had already passed, by letting logback's XML configurator run during the build and strand a SAX object in the image heap. The script's header carries the recipe for the day it is needed, and docs/inflight/core-the-native-sidecar-is-easy-only-while-it-has-no-engine.md tells the engine rung what it inherits. NOTHING IN THE ORDINARY BUILD RUNS THIS. No Maven profile, no plugin, no reactor module - so ./mvnw install, bin/build.sh and every existing CI lane behave identically on a machine with no GraalVM. An absent toolchain prints a banner and exits 0, or exits 2 under PC_NATIVE_IMAGE_STRICT: the same three-way contract, and the same exit codes, as bin/foreign-client-step.sh, which the polyglot scaffolding rung established for exactly this question. THE VERIFIER IS A PROGRAM, NOT A JUNIT TEST, deliberately. Its subject does not exist until a GraalVM toolchain has run, so a test would have to skip itself on nearly every machine - and a skip is indistinguishable from a pass in a surefire summary, which this repository treats as the failure mode rather than the convenience. The decision about an absent toolchain is made once, out loud, in the script. SABOTAGE, WITH A CONTROL ON EITHER SIDE. Deleting the UNIMPLEMENTED refusal from NoEngineSessionService and rebuilding the image turns the exercise red (exit 1), and the SHAPE is the finding rather than the colour: it does not fail fast, it hangs, and the run lands on its bounded latch - which is what a client author would hit against a silently-accepting sidecar. Restored from a pristine copy, the control is green again. A second control pointed the exercise at /bin/cat, which fails at the first arm, so the exercise is not vacuous. A PREDICTION THAT WAS REFUTED, recorded because it changed what the code claims. The empty-environment arm was written believing it would catch a fallback image or a JVM in disguise. The control - the same exercise pointed at a shell wrapper that execs the JVM sidecar by absolute path - PASSES every arm, because a hard-coded path needs no environment either. So the arm's comment and the class javadoc now say what it does establish (the binary needs nothing from its environment) and name --no-fallback as the thing that actually keeps a JVM out, at build time. THE CLASSPATH TRAP, since it is silent and cost a build here: dependency:build-classpath takes -DincludeScope, not -Dmdep.includeScope, and the ignored spelling yields the TEST classpath. The first image built here contained JUnit, Mockito, ArchUnit and - worse - the core test-jar's logback.xml, so the shipped binary carried this repository's test logging configuration as its own. The build is green either way. Extraction A6 of docs/plans/2026-08-31-001-process-god-branch-decomposition-plan.md, re-cut against the no-engine shell. The source branch proved its build with a Python demo end to end; there is no engine and no demo here, so the proof became the lifecycle instead - which is the claim this rung can actually make. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A workflow with two rows - ubuntu-latest and macos-latest - that builds the sidecar as a native executable and drives it through its lifecycle. Each row installs its own GraalVM, so the result is a standing answer rather than one machine's. LINUX IS THE NEW INFORMATION. The branch this work was re-cut from recorded "untried: Linux" among its own gaps, and Linux is where a sidecar is actually deployed. macOS/arm64 is where the build was first proven by hand, so that row is a regression guard rather than a discovery. PC_NATIVE_IMAGE_STRICT IS SET ON BOTH ROWS, and that is what keeps the lenient default honest. On a developer box bin/native-image-sidecar.sh prints a banner and exits 0 when GraalVM is absent, because absence there is normal; on a row that installs the toolchain itself, absence means the setup step did not do what it claimed, and a green skip would be docs/solutions/workflow-issues/a-check-that-reports-success-without-having-run.md again. Same pairing, same reasoning, as clients.yml and PC_FOREIGN_CLIENTS_STRICT. TWO JDKS, AND THE ORDER IS LOAD-BEARING. GraalVM is installed first and captured by PATH into PC_NATIVE_IMAGE; the project's JDK 17 is installed second so it is the JAVA_HOME Maven builds on. A GraalVM JAVA_HOME exported for the whole build is how an unrelated module's delombok step starts failing, so the separation the script's header describes is enforced by the row rather than assumed. Its own workflow rather than a step on maven.yml: this is the only lane in the repository that needs a second JDK, and appending that to a lane already near its timeout is what pushes it over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… two gaps it leaves The module README gains the section that tells a reader how to build the executable and, more importantly, what it does and does not establish: the SHELL survives closed-world compilation - the transport, the generated stubs and protobuf all still work in the image - and nothing about an engine that is not in this module. It also stops listing "the native image" among what is not here, and names #340 as where the different artifact lives: the --shared C-ABI library that embeds the engine in the host process with no gRPC at all. The maturity and testing-evidence records say the same thing from the release-documentation side: the lifecycle is now evidenced twice, once in the JVM and once against a native executable, and the limitation names why the second one is easier than it will stay - an engine and a Kafka client are exactly the parts that make closed-world analysis hard, and neither is on this classpath. TWO NOTES, BOTH OPEN WORK RATHER THAN A WRITE-UP OF FINISHED WORK: core-the-native-sidecar-is-easy-only-while-it-has-no-engine.md is addressed to whoever lands the engine rung. Both of the things that make this build a one-flag affair stop being true in that one commit, and NEITHER failure appears at build time: the Kafka client instantiates serializers and partitioners by reflection from configuration strings, which fails at runtime inside connect-time configuration, and a logging backend brings back the logback analysis failure. It names the prior capture on feats/native-image-sidecar as a starting point and says why it is not an answer - it was traced over one unordered 20-record run with no failures, retries, rebalance or transactional commit, so every path it did not walk is still invisible. core-the-sidecar-ships-no-logging-backend-so-its-own-logs-go-nowhere.md is a defect found while building the image and belonging to neither: slf4j-api is on the module's runtime classpath and no provider is, so every line the sidecar logs - including the non-loopback warning and the session refusal - reaches a no-op logger. The JVM sidecar behaves identically, so it is not a native-image problem. It is left open rather than fixed because the fix is a packaging decision (a library must not bind a backend; an executable normally must, and this module is both), and nothing is blocked on it while there is no distributable and no way to reach a broker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dependency ReviewThe following issues were found:
License Issues.github/workflows/native-image.yml
OpenSSF Scorecard
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)
No new clones introduced by this PR. Powered by astubbs/duplicate-code-cross-check |
|
|
||
| /** Opens a session and requires it to terminate with {@code UNIMPLEMENTED} rather than hang or answer. */ | ||
| private static void assertSessionRefused(ManagedChannel channel) throws InterruptedException { | ||
| var terminated = new CountDownLatch(1); |
There was a problem hiding this comment.
parallel-consumer-proxy/src/test/java/bz/stub/parallelconsumer/proxy/MainTest.java:142
🧪🔒 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 |
… was hiding the resolve Both CI rows failed on their first run, identically and for a real reason: Could not resolve dependencies for parallel-consumer-proxy: the following artifacts could not be resolved: parallel-consumer-proxy-protocol:jar:0.6.0.0-SNAPSHOT, parallel-consumer-core:jar:tests:0.6.0.0-SNAPSHOT ... not found in central THE DIAGNOSIS, AND WHY IT PASSED LOCALLY. The classpath step runs on this module ALONE, and it has to: with -am, dependency:build-classpath runs on every module in the reactor and each overwrites the same output file. Alone, it resolves the sibling snapshots from the LOCAL REPOSITORY rather than from the reactor - and `test-compile` never puts them there. On a developer box they are already installed by some earlier build, so the weaker goal appears to work; on a clean runner nothing has installed them and the resolve goes to Central, which has never heard of a snapshot. `install` is what makes the two machines behave the same, and it reaches test-compile on the way past, which is what the lifecycle verifier still needs. This is the version of the bug that only a clean environment can show, which is the argument for the lane existing at all - the script had run green a dozen times on the machine that wrote it. ALSO: fetch-depth: 0 on the checkout. The Maven build this row runs includes the copyright header check, and on a shallow clone it cannot find the fork-point commit, so it prints a warning and skips itself - a check reporting success without having run, which is the class the workflow header already cites and copyright.yml already sets this flag for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
…instead of a third copy The duplicate-code check flagged a new clone this PR introduced: NativeSidecarLifecycle had hand-rolled the same StreamObserver - a synchronized reply list, an error reference and a termination latch - that RecordingProxyMessageObserver already is. Both engines saw it, and they were right to. RecordingProxyMessageObserver becomes public and is used from outside its package, which is the smaller change of the two available: the alternative was a third copy of nine lines that must all agree about how a session ends. Its javadoc says why it is public, so nobody narrows it back. MainTest keeps its own inline observer. That clone predates this PR - it is between MainTest and the recorder, on the base branch - and this repository's rule is that inherited duplication is not this PR's to fix. Re-run after the change: the module suite green, and the native build and all four lifecycle arms green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…han abandoning it SpotBugs on the PR: awaitAnnouncedPort "may fail to close stream". It was right, and the fix is the one the finding points at rather than a suppression - the method wrapped the process's stdout in a BufferedReader, read one line, and dropped it. The reader is now opened beside the process it belongs to and closed with it, in a try-with-resources around the whole arm, with the helper taking it as a parameter. That is also why it is not closed the moment the port line is parsed: closing it closes the process's stdout pipe, which must outlive the parse. The two remaining findings on these lines are UNENCRYPTED_SOCKET, and they are declined for the same reason #384 declines five of them: every one is a connection to this sidecar's own loopback listener, which is plaintext BY DESIGN - loopback-only plus one-connection admission is what this module offers instead of transport security. A probe that used TLS would be probing a listener that does not exist. Re-run: the native build and all four lifecycle arms green, the module suite green, bin/check-all.sh 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…evidence, explicitly The branch never had a PR and its work was re-proven as #385 on the decomposed stack, but nothing recorded that relationship - a reader finding the branch would take it for live work, and a cleanup pass might delete the reachability metadata that is the prior art for the engine-present native build, a question the stack merge into feats/proxy-requirements just made live again. Owner asked for the status to be explicit. The campaign note now points at the dedicated note, which names the delete-when condition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… its exemption Forward-merges `feats/polyglot-demos` (#331) into this branch, the third link of the descendant-chain half of the plan's retarget move - `docs/plans/2026-08-31-001-process-god-branch-decomposition-plan.md`. Ordinary merge: no history rewritten, nothing force-pushed, this PR's base unchanged. The pre-merge tip is preserved as `origin/backup/pre-forward-merge-340`. **Zero textual conflicts** - the FFI work and the ten per-language demos touch almost disjoint files. What the merge did surface is a guard doing exactly its job. ## The registry guard found the C probe, which is the point of it `LanguageRunnerRegistryTest.everyClientModuleOnDiskHasARunnerRegisteredAndTheReverse` arrives with the rung below. It reads the client modules **off disk** rather than from a list, precisely so that "an eleventh language fails this test on the day its directory appears, rather than on the day somebody remembers". This branch adds a twelfth directory - `parallel-consumer-proxy-client-c` - and the guard went red naming it. That is the guard working, not a collision. `c` genuinely has no conformance runner, and cannot have one: its own README calls it *a reach probe, not a supported client* - it links the engine in through GraalVM's shared library and speaks no gRPC at all, so there is no sidecar for a runner to spawn and nothing for the shared scenarios to drive. It has no `pom.xml` either. **The exemption was re-keyed rather than doubled.** The test already had one, `NOT_A_SPAWNED_RUNNER = "java"`, a bare `String`. The second case has a completely different justification, so a second constant would have recorded *that* two things are exempt and nothing about *why* either is - which is how an exemption list rots into a mute. It is now a `Map<String, String>` of language to reason, with both reasons written out, and the javadoc says that being on it is a claim about the module rather than a way to quiet the test. ## The `--shared` build, which is this rung's whole proposition, still works after the merge #385 predicted this rung would inherit both of the obstacles that make a native image hard - the Kafka client and a logging backend - and warned that **neither fails at build time**. Run rather than assumed, on the merged tree: ``` ==> GraalVM: native-image 23 2024-09-17 (Oracle GraalVM 23+37.1) ==> native-image --shared (libpc) Finished generating 'libpc' in 1m 34s. 76,802,760 bytes, libpc.dylib ``` and then the Go smoke against that library: ``` ok isolate created ok pc_session_open -> handle 1 ok a malformed frame is rejected as ERR_BAD_FRAME ok pc_send(Configure) accepted, broker=localhost:19092 topic=pc-ffi-smoke ok Configured: max_concurrency=4 executor_count=4 ok session closed PARALLEL CONSUMER RAN INSIDE THIS GO PROCESS - no sidecar, no gRPC, no JVM ``` **The obstacles are real and were already paid for on this branch, and the merge did not disturb the payment.** `build-shared-library.sh` carries `--initialize-at-build-time=ch.qos.logback,org.slf4j,...`, and the proxy module ships `META-INF/native-image/reachability-metadata.json`, which `native-image` finds on the classpath by itself. Both survived the merge - so the prediction holds as a description of the difficulty and is answered here, rather than arriving as a surprise. ## A defect of the class #385 named, found by looking for it That PR recorded a silent trap: `dependency:build-classpath` takes `-DincludeScope`, **not** `-Dmdep.includeScope`, and the ignored spelling yields the TEST classpath. Swept for other instances across this branch: the two demo `run.sh` files already use the correct spelling; **`ffi/build-shared-library.sh` is the one remaining instance**, and it is live - `build/proxy-classpath.txt` holds **18 test-scope artefacts out of 86**, including JUnit 4 and 5, Mockito, Testcontainers, ArchUnit and Truth, plus `parallel-consumer-core`'s test-jar. **Not fixed here, and the reason is that it is not this merge's to fix**: it is a one-word change to a file this merge does not touch, and it changes the classpath every measurement on this branch was taken against. Its worst named consequence does not land, which is worth recording so nobody re-derives it - that test-jar carries no `logback.xml`, so the shipped library is not picking up this repository's test logging config. What it costs today is a needlessly large closed-world analysis surface. Left for #340's author, with the evidence above. ## A flake sighting, and the day's tally `ParallelEoSStreamProcessorTest` went red on the second run of the merged tree - 5 failures in 543, `processInKeyOrder[1]` together with `inFlightMessagesCommittedIfProcessedDuringShutdown` and `executorThreadsInterruptedOnShutdownTimeout`, all on the same `[sanity check input data]` assertion. Appended to the existing ledger rather than written up again: `docs/inflight/test-processinkeyorder-sanity-check-races-the-first-poll.md`. The sighting is worth more than usual because the whole chain ran today on one box over three trees whose `parallel-consumer-core` is **byte-identical** at every link: green 5 of 5 two rungs down, red once then green one rung down, green then red here. Same code, same box, same afternoon, outcome flipping run to run - so the remaining variable is the run, not the tree. ## How it was verified JDK 17, macOS/arm64, toolchains through `mise`; GraalVM 23 for the shared library only, reached by path so the project's JDK 17 stays `JAVA_HOME`. - Full default reactor `./mvnw --fail-at-end test`, with the conformance suite over eleven of the thirteen bindings. - The `--shared` build and the Go smoke above. - `bin/check-all.sh` on its real exit code. `check-issue-refs` is red on **the PR body**, not the tree - #340's description carries three bare `#NNN` references, which the gate reads and correctly refuses; that is a body fix, not a code one. **Not run, and named rather than skipped quietly:** the `cpp` and `swift` conformance cells, whose runners are Linux ELF binaries on this host and whose container path this box's disk headroom does not permit exercising. They are deselected with `-Dpc.conformance.language`.
…ly at test scope `ffi/build-shared-library.sh` asked for `-Dmdep.includeScope=runtime` and got the test classpath. `dependency:build-classpath` honours the `mdep.` prefix on `outputFile` but NOT on `includeScope`, so the prefixed spelling is discarded without a word and the mojo falls back to its default scope, which is `test`. One word - the prefix - is the whole fix. This is the defect class #385 named on the native sidecar build, found again here. ## What was actually on the classpath Re-derived on this branch by running the same Maven command twice, once per spelling, and diffing the two outputs by exact artifact coordinates rather than by eye: -Dmdep.includeScope=runtime (before) 87 entries -DincludeScope=runtime (after) 37 entries dropped 50, added 0 All 44 artifacts the proxy module declares at `test` scope were on the before classpath, and none are on the after one. The other six dropped entries are `provided` scope - lombok, jabel-javac-plugin, byte-buddy, byte-buddy-agent, error_prone_annotations, jna - which `includeScope=runtime` correctly also excludes. The passengers were not harmless. JUnit, Mockito, ArchUnit, Testcontainers, docker-java, Truth, AssertJ, Awaitility, logback-classic and `parallel-consumer-core`'s **test jar** were all being compiled into a library whose entire purpose is to be linked into somebody else's process - and that test jar carries `logback-test.xml`, so the shipped library was carrying this repository's test logging configuration as its own. Exactly what #385 found in the sidecar image. `libpc.dylib` went from 76,802,760 to 67,872,424 bytes as a result. ## Verified, not assumed - `./build-shared-library.sh session` rebuilt the GraalVM `--shared` library green, 1m 28s for native-image, about two minutes wall clock. native-image raised no new warning; in particular it does not object to `--initialize-at-build-time=ch.qos.logback` now that logback is off the classpath. - The Go FFI smoke was re-run against the rebuilt library exactly as the forward merge did on this branch today - GraalVM 23 via sdkman, no container, broker on localhost:19092 - and passed on its **real exit code, 0**: isolate created, session opened, a malformed frame rejected as ERR_BAD_FRAME, Configure accepted, `Configured` returned with max_concurrency=4 executor_count=4, session closed, and "PARALLEL CONSUMER RAN INSIDE THIS GO PROCESS". - `bin/check-all.sh` exits 1 with the same three inherited failures as before this change - check-branch-self-reference, check-file-refs, check-inflight-tags - and the same two that cannot run for want of `buf`. Gate-by-gate verdicts are identical to the pre-change tip, so the diff is not-worse rather than merely red for a familiar reason. ## The figures on this branch predate this change - do not read them as current `docs/inflight/perf-embedding-the-engine-over-ffi.md` records throughput for the embedded arm measured on 2026-08-22. Every one of those numbers was taken against the wider, test-scope classpath - a different library from the one this commit builds. **Re-measurement is deferred by owner decision**, so nothing in that note is edited or re-run here. It is recorded in this commit so that nobody later mistakes a pre-fix figure for a measurement of the fixed library. ## Same defect elsewhere, reported rather than fixed `grep -rn "mdep.includeScope"` over the tree finds four more sites, none touched here: - `parallel-consumer-proxy-client-dotnet/demo/run.sh`, `demo/Dockerfile` and `SidecarCommand.cs` all ask for `-Dmdep.includeScope=runtime` and all silently get test scope. These are **demos**, and `docs/inflight/bug-sidecar-runtime-logging-and-address-leak.md` states plainly that moving a demo to runtime scope on its own is actively harmful until its steps 1, 2 and 4 land: the test jar is currently the only thing pinning `org.apache.kafka.common.config.AbstractConfig` above INFO, and dropping it uncovers a `bootstrap.servers` dump the demo contract forbids. That ordering is an inherited decision and this commit does not override it. The FFI library is not a demo, prints no demo output and is not read by `bin/ci-demo-conformance.sh`, which is why it is safe to fix ahead of them. - `parallel-consumer-proxy-client-go/internal/harness/harness.go` passes `-Dmdep.includeScope=test`. The spelling is equally ignored, but the value it asks for happens to be the default it gets, so that classpath is right today by coincidence. Latent, not live: the day somebody edits that value it will silently not take.
Part of #242.
depends on #384
Description
The sidecar shell compiles and runs as a native executable.
bin/native-image-sidecar.shbuilds
parallel-consumer-proxy'sMaininto a GraalVM native image and then drives that binarythrough the whole spawning contract #384 established in the JVM: it refuses
arguments, binds an ephemeral loopback port, announces it on stdout line one, answers a real gRPC
session with
UNIMPLEMENTEDnaming the missing engine, exits 0 when its parent's write end closes,and leaves the socket released.
Why that is worth its own rung. The whole proposition of the sidecar is that a Go, Python or Ruby
team is handed an executable rather than a JVM dependency. Until something builds one, that is a
claim. The JVM suite cannot substitute: closed-world analysis can drop a class the JVM would have
loaded, and the failure appears only in the binary - so the transport, the generated stubs and
protobuf surviving into an image is a distinct thing to evidence, and this evidences it.
Where this came from
Extraction A6 of
docs/plans/2026-08-31-001-process-god-branch-decomposition-plan.md, re-cutagainst the no-engine shell. The source is
feats/native-image-sidecar, a sibling of#340 stacked on the demo chain rather than a free-standing branch, which is
why the plan says it can only land on
masterafter the shell exists.Almost none of that branch's delta ported, and the reason is the finding below. Its two commits
are a vendored 1,049-line
reachability-metadata.jsonand an inflight note; its proof was a Pythondemo driving a sidecar with a real engine and a real broker. There is no engine and no demo here, so
the proof became the lifecycle instead - which is the claim this rung can actually make - and the
metadata turned out not to be needed at all.
It re-parents the story #340 tells
That PR currently has to make two arguments at once: "the sidecar can be a native image" and "the
engine can be embedded in the host process over a C ABI, with no gRPC anywhere". The first is now
this rung's, evidenced against
master-bound code. What #340 is leftasking is the sharper question: native distribution is settled, so now eliminate the process
boundary too. The module README says which artifact is which, so a reader arriving at either one
finds the other.
The recipe is one flag, and the shortness is the finding
No
--initialize-at-build-time, noMETA-INF/native-imageconfiguration of any kind. That is notthis rung being clever; it is this rung having no engine. The same build on
feats/native-image-sidecarneeded logback initialised at build time and the vendored reachabilitycapture, because that tree carries the Kafka client and a logging backend - the two things
closed-world analysis cannot follow. Neither is on this module's runtime classpath.
Reproduced rather than assumed: the plain build against a classpath with logback fails exactly as
that branch's table records it, on
Logger.namenot being available during analysis. The script'sheader carries the flag list for the day it is needed, and the inflight note tells the engine rung
what it inherits.
--no-fallbackis the load-bearing flag. Without it native-image quietly emits a fallback imagethat still requires a JVM: it builds green, runs fine on the build machine, and destroys the whole
proposition.
Measured
macOS/arm64, Oracle GraalVM 23, JDK 17 for the Maven half:
Whole script, including the Maven build: about a minute.
Sabotage, with a control on either side
The arm's shape is the finding, not its colour: removing the refusal does not fail fast, it
hangs, and the run lands on its bounded latch - the same shape #384 reported
for the equivalent JVM arm, now confirmed to survive into the image.
A prediction that was refuted
The empty-environment arm was written believing it would catch a fallback image or a JVM in disguise.
It does not. The control - the same exercise pointed at a shell wrapper that
execs the JVMsidecar by absolute path - passes every arm, because a hard-coded path needs no environment
either. So the claim was narrowed rather than kept: the arm establishes that the binary needs nothing
from its environment, and the class javadoc now names
--no-fallbackas the thing that actuallykeeps a JVM out, at build time. The script additionally refuses an output that starts
#!, which isthe failure a hand-rolled "native build" really has.
What CI added that the local runs could not
Both rows failed on their first push, for a real reason the developer box structurally cannot
show. The classpath step runs on this module alone - it has to, since
-amwould rundependency:build-classpathon every module and each would overwrite the same output file - so itresolves the sibling snapshots from the LOCAL REPOSITORY, and
test-compilenever puts them there.Every machine that has ever built this project has them already; a clean runner does not, and went
to Central looking for a snapshot. Fixed by installing rather than test-compiling, which makes the
two environments behave the same. The lane earned its keep before it had run green once.
fetch-depth: 0went on the checkout in the same change: the Maven build these rows run includesthe copyright header check, and on a shallow clone it cannot find the fork-point commit, so it warns
and skips itself - a check reporting success without having run.
Then both rows passed, and Linux is the result worth having:
Same one-flag recipe on linux/x64 as on macOS/arm64, GraalVM 23.0.2 on both.
The duplication reports, read
Both engines flagged one new clone this PR introduced -
NativeSidecarLifecyclehad hand-rolledthe same
StreamObserverthat the transport tests'RecordingProxyMessageObserveralready is - andthey were right. That class is now public and reused, rather than a third copy of nine lines that
must all agree about how a session ends. After the change PMD CPD and jscpd each report zero new
clones and duplication at or below the base branch's.
dups: similaritystill listsMainTest<->NativeSidecarLifecycleat ~45%, and that one isvocabulary rather than code. The two files talk about the same five things - a port line, a
session,
UNIMPLEMENTED, parent death, a released socket - because they assert the same claimsagainst two different subjects, which is the entire point of the second file existing. Both clone
detectors, which compare token sequences rather than word bags, find nothing between them. The
TestConventionsArchTestfamily moves +0.8 for the mechanical reason it always does: the proxymodule gained a file, and that wrapper is required by
EveryModuleWiresUpArchUnitTestand cannot beinherited - the same finding and the same answer as #380,
#383 and #384.
Optional by construction
No Maven profile, no plugin, no reactor module runs native-image.
./mvnw install,bin/build.shand every existing CI lane behave identically on a machine that has never heard of GraalVM. An absent
toolchain prints a banner and exits 0, or exits 2 under
PC_NATIVE_IMAGE_STRICT- the same three-waycontract and the same exit codes as
bin/foreign-client-step.sh, which#380 established for exactly this question, reused rather than reinvented.
.github/workflows/native-image.ymlsets strict on both rows, which is what keeps the lenient defaulthonest. Linux is the new information: the source branch recorded "untried: Linux" among its own
gaps, and Linux is where a sidecar is actually deployed. macOS/arm64 is the regression guard for the
build proven above. Two JDKs per row, GraalVM reached by path so the project's JDK 17 stays
JAVA_HOME.The verifier is a program, not a JUnit test
Its subject does not exist until a GraalVM toolchain has run, so a test would have to skip itself on
nearly every machine - and a skip is indistinguishable from a pass in a surefire summary, which this
repository treats as the failure mode rather than the convenience. The decision about an absent
toolchain is made once, out loud, in the script.
NativeSidecarLifecycleuses nothing but the JDK,gRPC and the generated stubs, so the module's test-classes plus the runtime classpath is enough to
run it.
A trap worth naming, because it is silent
dependency:build-classpathtakes-DincludeScope, not-Dmdep.includeScope; the ignoredspelling yields the TEST classpath. The first image built here contained JUnit, Mockito, ArchUnit
and - worse - the core test-jar's
logback.xml, so the shipped binary carried this repository's testlogging configuration as its own. The build is green either way.
The analysis surfaces, read
bin/check-pr-analysis-surfaces.sh 385reported three findings on lines this diff wrote and noneinherited, all SpotBugs, all in the lifecycle verifier. One was real and is fixed; two are
declined with a reason:
OBL_UNSATISFIED_OBLIGATION- "may fail to close stream". Correct, and fixed at the site ratherthan suppressed.
awaitAnnouncedPortwrapped the process's stdout in aBufferedReader, readone line and dropped it. The reader is now opened beside the process it belongs to and closed with
it, with the helper taking it as a parameter - which is also why it is not closed the moment the
port line is parsed: closing it closes that pipe, and the pipe must outlive the parse.
UNENCRYPTED_SOCKET(2). Both are connections to this sidecar's own loopback listener, whichis plaintext by design: loopback-only plus one-connection admission is what this module offers
instead of transport security, and
ProxyServer's javadoc and the module README carry thereasoning. A probe that used TLS would be probing a listener that does not exist. Same finding and
same answer as the five feat(proxy) astubbs#242: the sidecar as a real process - binds, serves its lifecycle, and shuts down cleanly #384 declines.
Two gaps recorded rather than glossed
docs/inflight/core-the-native-sidecar-is-easy-only-while-it-has-no-engine.md- addressed to theengine rung. Both things that make this build a one-flag affair stop being true in that one commit,
and neither failure appears at build time. It names the prior capture as a starting point and
says why it is not an answer: traced over one unordered 20-record run with no failures, retries,
rebalance or transactional commit.
docs/inflight/core-the-sidecar-ships-no-logging-backend-so-its-own-logs-go-nowhere.md- foundwhile building the image and belonging to neither.
slf4j-apiis on the runtime classpath and noprovider is, so every line the sidecar logs reaches a no-op logger. The JVM sidecar behaves
identically, so it is not a native-image problem; it is left open because the fix is a packaging
decision and nothing is blocked on it yet.
Deliberately NOT in this rung
Named so neither side reads as an oversight: the engine and everything downstream of it; the FFI
--sharedC-ABI embedding, which is a different artifact and stays #340's;the eleven language demos, including the Python one that drove the source branch's proof; the
conformance suite; and any distributed binary - nothing is published, and building one is not part of
the ordinary build.
How it was verified
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 a realfork point.
./mvnw -pl parallel-consumer-proxy -am test- BUILD SUCCESS, core and both proxy modulesgreen.
on macOS/arm64, and on both CI rows.
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 parents merge. Thatis the dependency gate doing its job on a stack rung.
claude-reviewis red until somebody asks for a review; the rungs below are in the same state.roadmap-stage: N/A -
language-proxy-sidecarsits atlimited-pocon the strength of#293's content, and its
done_whenis clients validated by the conformancesuite. A native executable of an engine-less shell moves neither, so the stage is unchanged.
Checklist
README.mdgains the build section and stops listing the nativeimage among what is not here;
docs/data/module-maturity.yamlanddocs/data/testing-evidence.yamlrecord what the second lifecycle arm evidences and what it doesnot; two
docs/inflight/notes carry the open gaps.docs/features/- N/A - nothing here is auser-facing feature. No binary is distributed, the module publishes to no registry, and the sidecar
still cannot be configured to reach a broker. The release-documentation records it does earn are
the maturity and evidence rows, which are in this PR.
sabotaging the code it asserts on, with control arms on either side. The module's existing suite is
untouched and green.
ce-simplifyandce-code-reviewlocally - N/A - neither was run. The diff is one script,one verification program and documentation, and the reasoning behind each decision is written at
the site; the cheaper place to spend a review is
@claude review thison the PR, which is also theonly route that opens inline threads.