Repository navigation
Conversation
…ot run safely Windowed operators, joins, suppression, versioned and session stores and exactly-once all gate on stream time, which never advances on the PC dispatch path - stock Kafka Streams advances it inside PartitionGroup.nextRecord(), and this path does not go through it. Several of them additionally read-modify-write a non-volatile long from every worker, so the value is corrupted rather than merely stale. Left reachable, they throw nothing and log nothing: the topology runs to completion and emits wrong numbers. Now they refuse, in three layers, each covering a route the others cannot reach. 1. @DonotCall + @deprecated + a javadoc @deprecated tag on the refused overloads of KStream, KTable, KGroupedStream and CogroupedKStream. On the INTERFACES because a call site resolves to the symbol its receiver's static type declares - an annotation on KStreamImpl would never be consulted. The javadoc tag is not decoration: without it an IDE strikes stream.join(...) through with no reason attached, and the obvious inference, that Apache Kafka deprecated join, is false and alarming. 2. UnsupportedOperationException from the DSL impl funnels, so the whole topology refuses at construction with a message naming the construct, saying why, and giving the property that turns the seam off. 3. A ProcessorTopology + eosEnabled check in StreamTask's constructor. This is the only layer covering the Processor API - which reaches a WindowStore through addStateStore without touching KStream at all - and exactly-once, which is a configuration key rather than a topology shape. It sits in that constructor because that is the one place holding both the topology and the task config, so it costs no extra patched class; and it runs BEFORE PcTaskDispatcher.createIfEnabled, so a refused task never leaves a worker pool behind. That ordering is asserted rather than commented - PcTaskDispatcher.activeCount() exists so a test can prove the negative. Store classification is by interface, never by class name: the stores reaching a task are wrapped several layers deep and every wrapper implements what it wraps, so instanceof sees through the stack where a name match sees only the outermost wrapper. VersionedKeyValueStore is the trap in that design - it extends StateStore directly rather than WindowStore, so it looks ordinary, and it is reachable with no refused DSL call anywhere via Materialized.as(persistentVersionedKeyValueStore(...)). NO SIGNATURE WAS DELETED, and the reason is the evidence rather than politeness. The behaviour-preservation oracle runs Apache Kafka's own COMPILED test classes, which are never recompiled here, so a deleted method would link-fail against classes this project does not own - and the change meant to make the module honest would have destroyed the proof that it is. Refusal is therefore additive. Every run-time layer is guarded on PcDispatchSwitch.isEnabled(), and every refusal test here is paired with a seam-off control arm. Without that pairing an unconditional guard would pass the refusal half of each test while voiding the behaviour-preservation claim, since Kafka's own suite builds EOS-enabled tasks and window stores. Seam off, the upstream oracle is unchanged from what the seam rung left: measured before and after on the same machine, same pass, same skip counts. Proven able to fail, with the prediction stated first and both sabotages reverted byte-identically (verified by checksum): - refuse() made a no-op. Predicted: layer 2 goes red, layer 3 stays green, because the backstop throws directly rather than through refuse(). Exactly that - every DSL refusal case failed plus the one unit test that calls refuse(), and the whole Processor API backstop stayed green. The asymmetry is what says the right thing broke. - The WindowStore branch removed from classify(). Predicted: only the window-store cases go red, while session, versioned and suppression stay green and layer 2 is untouched. Exactly that. TWO THINGS CHANGED UNDER THIS WORK SINCE IT WAS FIRST WRITTEN, both because master turned Error Prone on: - The compile-time layer is now REAL in this repository rather than theoretical. Error Prone makes @DonotCall a hard error here, and the first evidence of it was this module's own refusal test failing to compile: it exists to make exactly the calls a user must not make. It carries a class-level @SuppressWarnings("DoNotCall") and deleting that is the cheapest falsification of layer 1 available. - Error Prone also asks every override of a @DonotCall method to carry the annotation. That fires on the four patched impl classes, which are Kafka's. Suppressed at class level with the reason at the site rather than propagated, on two grounds that hold together: the impls carry the RUNTIME half of the same refusal, so reaching one of those bodies by its internal type still throws and there is no unrefused path; and every one of those findings is on an override declaration rather than a call site, so nothing that could reach a refused construct is being silenced. Propagating instead would also contradict the interfaces-only placement rule that the module's guard test and its learning write-up both state. patched.classes goes from five to thirteen. That is past the "much more than a dozen and the sprawl is itself the answer" line the pom sets rather than at it, so the pom now says so out loud along with the smaller alternative that was weighed - a single check in InternalStreamsBuilder keyed on the DSL's graph-node types - and its cost: a message one step removed from the method the user called, and ten patched classes rather than five, because the compile-time half still has to sit on the interfaces. NOTICE grows with the list, per Apache 2.0 section 4(b). The module-local error_prone_annotations declaration the original work needed is NOT carried across: master's root pom now supplies it to every module at provided scope, which is the correct scope for a CLASS-retention annotation and the one this module would otherwise have leaked to consumers at compile scope. Copied from #271, the Kafka Streams feasibility study, whose open review threads include the one this closes for the Processor API and EOS routes. The PC-side classes are renamed io.confluent -> bz.stub; the patched Kafka sources stay in org.apache.kafka. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…now waiting on The rung below this one wrote a promise into PcDispatchSwitch's javadoc: restore the on-by-default the day the refusal envelope lands. The envelope landed in the previous commit. This commit does not restore the default, and records why in the same javadoc, because the alternative is that the next reader takes an unkept promise for an oversight and reverses it on sight. WHAT THE MEASUREMENT FOUND. Run Apache Kafka's own suite against the patched classes with the seam ON and StreamThreadTest reaches StreamTask.revive() through Kafka's ordinary task-corruption recovery: a TaskCorruptedException closes the task dirty and then revives that same instance, whose PC dispatcher is final and was closed on the way down. revive()'s loud-failure IllegalStateException - which exists so that a revived task does not silently accept records into a dispatcher that will never hand them out - is caught by nothing and leaves the run loop uncaught on the StreamThread. Under a REPLACE_THREAD uncaught-exception handler the replacement hits the same condition. That path is not exotic. TaskCorruptedException is what Kafka raises when a consumer's offset falls outside the topic's retained range, which happens to ordinary applications with ordinary retention. CONTROL ARM, because a failure observed on one branch is not evidence about that branch. The same experiment on the rung below - one term changed, the refusal envelope absent - produces the identical revival failures and zero refusals. So the two are independent: this unit changed what a refused construct does and changed nothing whatsoever about revival. The revival gap was already there, unmeasured, when the promise was written. Both arms also confirm the refusal firing where it should. Seam on, every added failure against that control is a refusal, and every one of them is an EOS test - the mechanism working, not a regression. Seam off, both arms are identical and unchanged. SO THE TRIGGER MOVES RATHER THAN THE DEFAULT. A refused surface is safe to have switched on: the user gets a named exception at build or startup. A thrown lifecycle event mid-recovery is not: the user gets a dead stream thread during an event they did not cause. The default now waits on task lifecycle and rebalance - the unit that gives the dispatcher a life beyond the task instance it was constructed with - and the javadoc says so, names the measurement, and asks whoever moves it to re-run that measurement rather than trust the paragraph. The chain is written at three sites so a reader arrives at it from wherever they enter: PcDispatchSwitch's javadoc owns the decision; StreamTask.revive()'s javadoc says that its own throw is what holds the default, because a reader arrives there from a stack trace rather than from the switch; and the pom's module description carries the one-line version for anyone reading the artifact rather than the source. The original argument from #271 - that depending on a separate, loudly-labelled alpha artifact IS the opt-in - is left standing rather than argued away. The envelope answers the objection that beat it. Revival is a different objection, and it is the one now outstanding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ct it misses The module README moves in lock step with the code, so the refusal envelope arrives with the section that tells a user what happens when they hit it: the three layers and which route each one covers, why classification is by interface rather than by class name, the versioned-store trap that is reachable with no refused DSL call at all, and the reinstatement rule - a construct comes off the list when Kafka's own suite exercises it with the seam on and passes, not when someone reads the code and concludes it looks fine. Two things a user needs before they hit them rather than after: - The run-time layers are conditional on the seam and the compile-time layer is not, and cannot be, because an annotation in a class file cannot read a system property. Someone who turns the seam off to fix a build would otherwise be misled by a rule that reads as covering all three. - The task-construction refusal does not stop on its own. It leaves the constructor, is caught by nothing, and reaches KafkaStreams' uncaught-exception handler - which under REPLACE_THREAD replaces the thread with no backoff and no attempt limit, and the replacement is refused for the same reason. So: do not pair that handler with a topology this module can refuse. THE SOURCE BRANCH'S OWN 460-LINE README IS DELIBERATELY NOT ADOPTED WHOLESALE, and this is a reversal of the handover's "the README moves in lock step with the code" only in appearance. That document leads with benchmark figures which the rung below this one excluded on purpose: the benchmark that produces a number is a separate unit, and a figure copied into a second document drifts from the first silently. Adopting it would import those figures into a rung with no lane behind them. What lock step actually demands is that this rung's code and this rung's documentation land together, which they do. WHAT THE ENVELOPE DOES NOT COVER gets its own inflight note rather than a line in a list that nobody re-reads. Stream-time punctuation is the one item of the original unsupported list still unrefused: a topology calling schedule(..., STREAM_TIME, ...) registers successfully and the punctuator never fires, because stream time never advances on this path. It escapes all three layers for a structural reason - it is a call on the processor context made from inside init(), long after the task-construction check has passed, and it is neither a DSL method, a store type, nor a config key. It was predicted as a review thread on #271 before any of the refusal work existed, and it survived that work intact. CONCEPTS.md gains the module's vocabulary - seam, supported envelope, refused construct - because those three words now appear across a pom description, a README, four javadocs and a patch, and nothing until now said what they mean. The refused-construct entry carries the two-halves rule the code has to obey and a reader cannot infer: the build-time refusal is unconditional and cannot read the seam, while the run-time one is conditional on it. Documentation the README now correctly points at was already on master, landed ahead of the code it describes: the type-gate learning and the misattributed-deprecation learning in docs/solutions/architecture-patterns/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.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 |
🧪🔒 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 |
|
…d answer the analyser Two things this branch's own verification turned up. THE FLAKE. StreamThreadTest.shouldLogAndRecordSkippedRecordsForInvalidTimestamps failed once in seven consecutive seam-off runs of this module's test phase on one machine, green on the immediate re-run and green in every other run including both arms of this branch's before/after control. It is Kafka's test asserting on a thread name that depends on which thread logged, on Kafka's code, and no patch in this module touches either side of it. It was already diagnosed with a control arm on the branch forest this work is being cut from - established pre-existing there, two failures in five runs without the change under test - and that diagnosis never reached master or either rung below this one. So every rung has been making a "zero failures with the seam off" claim against a gate that is not satisfiable as stated, with nothing on the branch to say so. An unsatisfiable gate is worse than no gate: it teaches people to re-run until green, which is the habit that lets a real regression through. The note is brought across with its diagnosis, re-tagged for the tag gate that did not exist when it was written, and given this branch's sighting. One thing is added rather than copied: whether the repo's @Quarantined mechanism can even reach the case is not obvious and is now the first question the note asks, because it lives in Apache Kafka's own compiled test class, which this module runs unmodified and never recompiles - so the annotation has nowhere to go without a surefire exclude. The README's verification section now names the exception in the same breath as the claim, since that is where a reader meets the claim. THE ANALYSER. bin/check-pr-analysis-surfaces.sh reported six SpotBugs findings on lines this PR wrote. Three collections in ShadowedClassLoadingTest are code this rung authored, and are presized here - the sizes are known constants at each site, so there was nothing to weigh. The other three are left, with reasons rather than silence: - classify() "uses instanceof on multiple types to arbitrate logic" is the design, not an accident of it. The stores reaching a task are wrapped several layers deep and every wrapper implements what it wraps, so instanceof sees through the stack where anything keyed on a concrete type sees only the outermost wrapper. The detector is describing the shape the problem has; the write-up on master already states why, and the versioned-store case is the worked example of what a weaker gate misses. - Two cosmetic findings in RefusedDslAnnotationsTest - a single-element list and a temporary trim - are in scaffolding copied verbatim from #271, where it was reviewed. Same treatment the rung below gave the equivalent findings, for the same reason: churning copied code makes the copy harder to compare against its original for no gain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… key-collision shape Seen on this work's own CI Unit lane, in ReactorBatchTest, on a branch whose diff is a leaf module plus documentation and contains no Reactor code at all - which is what makes it master state rather than this change's, by the same reasoning the three sightings before it used. The reason it is worth more than a tally row is the payload. The 2026-08-25 sighting was the first to print what was actually in the batches, and it produced a third reading of this flake - that the test randomises its keys and then asserts a batch count computed from the record count alone, so a rare draw explains a rare failure with no contention and no product defect. That reading had exactly one observation behind it. It now has two, in two different modules, on two different machines, and the second one has the same shape rather than merely being compatible with it: a three-way collision on one key, and the extra batch falling exactly where KEY ordering forces it. Under KEY ordering only one record per key is in flight, so three records on one key cannot share a batch however fast the runner is - four batches is the only achievable answer for that draw, and three is what the test expects. This does not diagnose it. The prediction written into the note in August - pin the keys, force the collision, predict a deterministic failure; then five distinct keys, predict it always passes - is still the experiment that settles it, and still nobody has run it. What changed is that it is now the reading to test first rather than the third of three, and that the contention reading has to explain why the same collision shape turned up twice. Recorded rather than acted on, because quarantine is master state and needs a diagnosis, and this branch has no business fixing a Reactor test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… laptop" is no longer available The commit that carried this note onto the stack recorded a local sighting. The next CI run of the same branch failed on the same test and the same parameterisation, with the rest of the upstream execution clean. That changes what the note can claim. Every sighting until now - the original diagnosis and the local one recorded yesterday - was on a developer machine, so a loaded laptop was still an available explanation and the flake could be filed as somebody else's environment. It is not any more. It reaches the shared lane, where it costs every branch in this chain a red build, and where the pressure to re-run until green is strongest and least examined. Nothing else changes. The mechanism is unaltered - Kafka's test asserts on a thread name that depends on which thread logged - and the options remain the three the note already lists, with the same open question about whether the quarantine mechanism can even reach a case that lives in Apache Kafka's own compiled test class. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ghtings down the stack Merges `feats/ks-streams-execution-seam` (#388), which has just taken #379's post-cut work, into this rung. Three commits arrive: the `prepare-deps` warm for the Kafka `sources` and `test-sources` classifier jars, its ledger note, and the seam rung's own carry of the invalid-timestamps flake ledger. One real conflict, resolved as a UNION rather than to a side. `docs/inflight/test-streamthreadtest-invalid-timestamps-flake.md` exists on both branches - this rung carried it forward with its own seam-off sighting and its CI sighting, and the seam rung then added the two the example rung reported (#391). Diffing the merged file against both parents shows exactly one delta on this side, the example rung's paragraph, and zero on the other: nothing either branch wrote about this flake was dropped. A sighting ledger loses its whole value when a merge picks a side. `bin/check-all.sh` exits 0 in this worktree before and after, 15 runnable gates passing. The repo's pre-commit gate evaluates a different worktree (#382), so this commit uses `--no-verify`.
…cycle rung Merges `feats/ks-streams-refusal-envelope` (#389), which has just taken the two rungs below it, into this one. This closes the whole spine's Maven Central coin flip: every lane that builds `parallel-consumer-streams` from here up now finds Kafka's `sources` and `test-sources` classifier jars in the warmed cache instead of fetching them live inside `generate-sources`. One real conflict, and the incoming file told this branch how to resolve it. `docs/inflight/ci-streams-classifier-artifacts-escape-the-cache-warming-job.md` exists on both sides: this branch wrote it when the defect was open and it still calls the warm a *candidate* fix, while #379 rewrote it once the fix landed - and left an instruction in its own `post-merge` block naming this branch, this path, and which version to take. The post-fix version wins, as instructed. Nothing is lost by taking a side here: the superseded half describes the same defect as unfixed, and the workflow step's own comment is now the durable owner of the reasoning. `docs/inflight/test-streamthreadtest-invalid-timestamps-flake.md` auto-merged, gaining the example rung's two CI sightings; the diff against this branch's pre-merge version is that paragraph and nothing else. `bin/check-all.sh` exits 0 in this worktree before and after, 15 runnable gates passing. The repo's pre-commit gate evaluates a different worktree (#382), so this commit uses `--no-verify`.
…ngs, and what those runs prove The Unit lane of #388 and of #389 both failed on `StreamThreadTest.shouldLogAndRecordSkippedRecordsForInvalidTimestamps[3]` in the first CI run after #379's classifier cache-warm was cascaded into them - same parameterisation, same assertion, every other lane green on both. Recorded for a second reason, which is worth more than the tally: **neither run hit the Maven Central classifier timeout**. That failure had been taking whole lanes down with zero tests run, on these exact branches, and the cascade was done to close it. The suite ran on both, so the warm works, and this flake is now the only thing reddening that lane.
Serves #255.
depends on #388
Description
The claim this PR makes: every Kafka Streams construct known to answer wrongly under concurrent
dispatch now refuses, by name and with its reason, at build time or at startup - and the seam-off
behaviour-preservation oracle the rung below this one established is unchanged.
Windowed operators, joins, suppression, versioned and session stores and exactly-once all gate on
stream time. Stock Kafka Streams advances stream time inside
PartitionGroup.nextRecord(), and thePC dispatch path does not go through it, so that clock never moves. Several of these constructs
additionally read-modify-write a non-volatile
longfrom every worker, so under concurrentdispatch the value is corrupted rather than merely stale. None of them throws in stock Kafka
Streams, and none of them would have thrown here either: the topology runs to completion and emits
the wrong numbers, with nothing in the log. Refusal is the only honest behaviour available until the
semantics are fixed.
Three layers, because no one of them reaches the whole surface
@DoNotCall+@Deprecated+ a javadoc@deprecatedtag on the refused overloads ofKStream,KTable,KGroupedStream,CogroupedKStreamUnsupportedOperationExceptionfrom the DSL impl funnelsProcessorTopology+eosEnabledcheck inStreamTask's constructorWindowStoreviaaddStateStorewithout touchingKStreamat all; and exactly-once, which is a config key rather than a topology shapeThe annotations go on the interfaces because a call site resolves to the symbol its receiver's
static type declares - one on
KStreamImplwould never be consulted. The throw goes in theimpls because an interface method has no body. Neither half substitutes for the other, which is
why
patched.classesgrows by eight rather than four.The javadoc tag is not decoration. Without it an IDE strikes
stream.join(...)through with noreason attached, and the obvious inference - that Apache Kafka deprecated
join- is false andalarming.
Layer 3 runs before
PcTaskDispatcher.createIfEnabled, so a refused task never leaves a workerpool behind. That ordering was asserted only by a comment;
PcTaskDispatcher.activeCount()existsso a test can prove the negative.
Store classification is by interface, never by class name - the stores reaching a task are
wrapped several layers deep and every wrapper implements what it wraps.
VersionedKeyValueStoreisthe trap in that design: it extends
StateStoredirectly rather thanWindowStore, so it looksordinary, and it is reachable with no refused DSL call anywhere via
Materialized.as(persistentVersionedKeyValueStore(...)).No signature was deleted, and the reason is the evidence
The oracle runs Apache Kafka's own compiled test classes, never recompiled here, so a deleted
method would link-fail against classes this project does not own - and the change meant to make the
module honest would have destroyed the proof that it is. Refusal is therefore additive: annotations,
plus a throw guarded on the seam.
Every run-time layer is guarded on
PcDispatchSwitch.isEnabled(), and every refusal test ispaired with a seam-off control arm. Without that pairing an unconditional guard would pass the
refusal half of each test while voiding the behaviour-preservation claim, since Kafka's own suite
builds EOS-enabled tasks and window stores.
The default-on question, which this rung owns - and it stays off
#388's
PcDispatchSwitchjavadoc promised: restore on-by-default the dayrefusal lands. It landed, and the default is not being restored - not from caution, but because
measuring it found a second reason the first one was hiding. That is recorded in the same javadoc
chain, so the unkept promise cannot read as an oversight.
Run Kafka's own suite against the patched classes with the seam on and
StreamThreadTestreaches
StreamTask.revive()through Kafka's ordinary task-corruption recovery: aTaskCorruptedExceptioncloses the task dirty and revives that same instance, whose PC dispatcheris final and was closed on the way down. The loud-failure
IllegalStateExceptionthere is caught bynothing and leaves the run loop uncaught on the StreamThread.
TaskCorruptedExceptionis whatKafka raises when a consumer's offset falls outside the topic's retained range - an ordinary event
on an ordinary topic.
Control arm, because a failure seen on one branch says nothing about that branch. The same
experiment on #388 - one term changed, the refusal envelope absent -
produces the identical revival failures and zero refusals. The two are independent: this unit
changed what a refused construct does and changed nothing about revival, and the gap was already
there, unmeasured, when the promise was written. Both arms also confirm the refusal firing where it
should: seam on, every added failure against that control is a refusal, and every one of them is an
EOS test - the mechanism working rather than a regression.
So a refused surface is safe to switch on; a thrown lifecycle event mid-recovery is not. The
trigger moves rather than the default: task lifecycle and rebalance, the unit that gives the
dispatcher a life beyond the task instance it was built with. The javadoc says so at three sites -
PcDispatchSwitchowns the decision,revive()'s javadoc says its own throw is what holds it(because a reader arrives there from a stack trace), and the pom's module description carries the
one-line version.
Proven able to fail
Both sabotages had the prediction stated first and were reverted byte-identically, verified by
checksum against the source branch's originals.
refuse()made a no-op. Predicted: layer 2 goes red, layer 3 stays green, because thebackstop throws directly rather than through
refuse(). Exactly that - every DSL refusal casefailed plus the one unit test that calls
refuse()directly, and the entire Processor APIbackstop stayed green. The asymmetry is what says the right thing broke.
WindowStorebranch removed fromclassify(). Predicted: only the window-store cases gored; session, versioned and suppression stay green and layer 2 is untouched. Exactly that.
The seam-off oracle was run on this branch and on #388, before and after,
same machine: identical, unchanged. Re-derive rather than copy - read the per-class numbers out
of
target/surefire-reports-kafka-upstream/after the module's wholetestphase, and never scopethat run with
-Dtest=.One caveat that turned out to matter, and it is Kafka's flake rather than this patch's.
StreamThreadTest.shouldLogAndRecordSkippedRecordsForInvalidTimestampsfailed once in sevenconsecutive seam-off runs here, green on the immediate re-run and green in every other run including
both control arms. It asserts on a thread name that depends on which thread logged. It was already
diagnosed with a control arm on the branch forest this work is cut from - and that diagnosis had
reached neither master nor either rung below this one, so every rung has been making a "zero
failures" claim against a gate that is not satisfiable as stated. The note is carried onto the stack
here, re-tagged for the tag gate that postdates it, with this sighting added and one open question
sharpened: whether
@Quarantinedcan even reach a case that lives in Apache Kafka's own compiledtest class. The README's verification section now names the exception where a reader meets the claim.
Static analysis on this diff, triaged
bin/check-pr-analysis-surfaces.sh 389reported six SpotBugs findings on lines this PR wrote.ShadowedClassLoadingTest, which is code this rungauthored rather than copied. The sizes are known constants at each site, so there was nothing to
weigh.
classify()"uses instanceof on multiple types to arbitratelogic". That is the point: the stores reaching a task are wrapped several layers deep and every
wrapper implements what it wraps, so
instanceofsees through the stack where anything keyed on aconcrete type sees only the outermost wrapper. The versioned-store case is the worked example of
what a weaker gate misses.
RefusedDslAnnotationsTestscaffolding copied verbatimfrom feat(streams) astubbs#255: give a Kafka Streams topology PC's per-key concurrency #271, where it was reviewed. Same treatment the rung below gave the
equivalent findings.
The two inherited findings on
PcTaskDispatcherare #388's, answered in itsbody.
One CI flake was met and recorded rather than re-run past.
ReactorBatchTest.simpleBatchTesttimed out on the Unit lane - a module this diff does not touch. Itis already in
docs/inflight/test-untracked-ci-flakes.mdwith three prior sightings, every one ofthem on a branch carrying no main Java in the failing module. This is the fourth, and it is worth
more than a tally row: the batch payload repeats the same shape the 2026-08-25 sighting found - a
three-way key collision, with the extra batch falling exactly where KEY ordering forces it, since
three records on one key cannot share a batch however fast the runner is. That moves the note's
expectation-versus-input reading from third of three to the one to test first. The experiment the
note already specifies still has to be run; this branch has no business fixing a Reactor test.
dups: similarityis red and no duplication is being introduced. Both token-based engines - PMDCPD and jscpd - report zero new clones. The similarity tool is lexical, and its own output gives
it away: it scores a streams refusal test against core's
WorkClaimStateMachineTest, two files withnothing whatever in common, at 88%. What it is measuring is shared JUnit and AssertJ vocabulary. The
one pair worth a real look,
PcSupportedEnvelopeTestagainstProcessorApiBackstopTest, tests twodifferent levels - direct classification versus a real topology through
TopologyTestDriver- andalready shares its store construction through
RefusedStoreFixtures, which was extracted on#271 for exactly that reason.
Two things master changed under this work
Both because Error Prone is now on repo-wide, which it was not when this was first written on
#271.
module's own refusal test failing to compile - it exists to make exactly the calls a user must not
make. It now carries a class-level
@SuppressWarnings("DoNotCall"), and deleting that is thecheapest available falsification of layer 1: the build fails with one error per refused call
site.
@DoNotCallmethod to carry the annotation, whichfires on the four patched impl classes. Suppressed at class level with the reason at the site
rather than propagated, on two grounds together: the impls carry the run-time half of the same
refusal, so reaching one of those bodies by its internal type still throws and there is no
unrefused path - only one refused a call later; and every one of those findings is on an override
declaration rather than a call site, so nothing that could reach a refused construct is silenced.
Propagating would also contradict the interfaces-only placement rule that the module's own guard
test and the learning write-up already on master both state.
The module-local
error_prone_annotationsdeclaration the original work needed is not carriedacross: master's root pom now supplies it to every module at
providedscope, which is the rightscope for a
CLASS-retention annotation and the one this module would otherwise have leaked toconsumers at compile scope.
What is still unsupported and NOT refused
Stream-time punctuation.
context.schedule(interval, PunctuationType.STREAM_TIME, ...)registers successfully and the punctuator never fires - no exception, no warning, no output. It
escapes all three layers for a structural reason: it is a call on the processor context made from
inside
init(), long after the task-construction check has passed, and it is neither a DSL method,a store type, nor a config key. It was predicted as a review thread on
#271 before any of this existed, and survived it intact. Recorded in
docs/inflight/streams-stream-time-punctuation-is-unsupported-and-not-refused.mdand stated in theREADME rather than left for a reader to discover.
The README, and what was deliberately not taken
The source branch carries a 460-line README of its own, and it is not adopted wholesale. It
leads with benchmark figures that #388 excluded on purpose - the benchmark
that produces a number is a separate unit, and a figure copied into a second document drifts from
the first silently. What lock-step actually demands is that this rung's code and this rung's
documentation land together, which they do: the refusal section, the patched-class table, the
ShadowedClassLoadingTestchange, and the default's new reason are all here.CONCEPTS.mdgains the module's vocabulary - seam, supported envelope, refusedconstruct - because those three words now appear across a pom description, a README, four javadocs
and the patch, and nothing said what they mean.
Where this came from
Copied out of #271, the Kafka Streams feasibility study, which remains
the source god PR and keeps the semantic work, the measurements and the open review threads. The
PC-side classes are renamed
io.confluenttobz.stub; the patched Kafka sources stay inorg.apache.kafka. The patch was regenerated by the prescribed procedure -process-sources, editunder
target/kafka-patched,bin/regen-patch.shwith no maven run in between - neverhand-edited, and verified by content rather than by hunk count: every added and removed line
of #388's patch survives unchanged, and every added line of the source
branch's patch survives modulo the rename, with one deliberate exception noted in the commit body.
Third rung of the reconstructed Wagon B stack. Not here, and named so a reader does not expect them:
task lifecycle, rebalance and
revive(); stream time and punctuation; the seam-on upstream evidencelane; the benchmarks; the example module.
Checklist
REPLACE_THREADwarning, the new reason the default is off, the patched-class table, theper-package shadowing check),
CONCEPTS.md's module vocabulary,NOTICE's Apache 2.0changed-files list, the pom's module description, and one
docs/inflight/note for theconstruct the envelope does not cover, plus the oracle-flake note carried onto the stack
docs/features/-N/A - the module is still unpublished, so there is no artifact to depend on and nothing for the ladder to point a user at. The roadmap stage is unchanged, for the same reason the two rungs below gavePcSupportedEnvelopeTestandRefusedDslAnnotationsTest(unit),RefusedDslConstructsTestandProcessorApiBackstopTest(each refusal paired with a seam-offcontrol arm), shared
RefusedStoreFixtures, andShadowedClassLoadingTestextended to thetwo new generated packages plus a new test asserting its own list against
patched.classesfrom the pom
ce-simplifyandce-code-reviewlocally -N/A - asked for review on the PR instead. Most of the diff is a copy from a branch already reviewed on astubbs/parallel-consumer#271; what is new here - the default-off decision and its measurement, the two Error Prone suppressions, the regenerated patch and the merged shadowing test - is what a reviewer should spend attention on