Skip to content

feat(streams) astubbs#255: reconcile the sibling rungs, and decide the dispatch default against the merged whole - #454

Draft
astubbs wants to merge 45 commits into
feats/ks-streams-task-lifecyclefrom
feats/ks-streams-reconciled
Draft

astubbs wants to merge 45 commits into
feats/ks-streams-task-lifecyclefrom
feats/ks-streams-reconciled

Conversation

@astubbs

@astubbs astubbs commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Serves #255.

depends on #395
depends on #396
depends on #398
depends on #391

Description

This is the reconciliation rung of the Kafka Streams stack: the four sibling rungs merged together, verified as one tree, and the dispatch default decided against a measurement of the whole rather than of any one rung. It sits on the task-lifecycle rung (#394) and contains, by ordinary merge commits, error surfacing (#395), stream time and punctuation (#396), the evidence suite (#398) and the example module (#391). The diff against the base is therefore mostly theirs; what is new here is small and is the point:

  • The reconciled seam-on measurement, and the default stays OFF (8f7d4c884). Every sibling rung declined to flip PcDispatchSwitch on its own evidence and reserved the decision for the merged module, because the seam-on numbers move with each of them. Merged, Kafka's own suite was run through the evidence lane twice - seam off as the control arm, seam on as the measurement - and two punctuation cases still diverge: StreamTaskTest.shouldPunctuateOnceStreamTimeAfterGap produces six of stock's seven punctuations, and shouldRespectPunctuateCancellationStreamTime fails a different assertion than it did before stream time worked at all. That is the low-water mark doing its job - it never passes a record still in flight - and it is a divergence in the direction the documentation did not state: the README and CONCEPTS.md pinned the mark overtaking stock and said nothing about it lagging. A default flipped on while half of a two-directional divergence is undocumented ships a config whose docs are half true, so it became the fourth named trigger. PcDispatchSwitch's javadoc owns the chain of reasons end to end; docs/inflight/core-streams-punctuation-diverges-in-three-measured-ways.md is where the current position lives, and the divergence triage note gained the stream-time-lags-in-flight-work class so the lane names it rather than re-diagnosing it.
  • The two review threads no rung answers (5f4e6de0f). Of the eleven open threads on feat(streams) astubbs#255: give a Kafka Streams topology PC's per-key concurrency #271, nine close against a named rung or are answered as a documented contract. Two survive all of it and are written down in docs/inflight/core-streams-two-commit-signals-the-pc-path-cannot-see.md: an explicit context().commit() sets a volatile commitRequested that nothing on the PC path reads, and a poll batch of nothing but control markers advances the consumer position without any signal reaching commitNeeded(). Both were read against the reconciled tree, not the state the threads were written on. Neither is a one-line fix - the || commitRequested shape is the same one the stream-time rung measured and rejected.
  • The reconciled tree verified, and its flakes ledgered (a7855e2ea, 234e9cd24, d9d56acad). The whole module's test and verify phases were run on the merged tree; the invalid-timestamps flake was met on both cascaded rungs and is recorded with what those runs prove, a load-tightness sighting is recorded, and PostCommitCheckpointGapTest's post-commit arm is recorded as passing only beside its siblings - which is why it is red on feat(streams) astubbs#255: advance stream time on the PC path, and decide what to do about punctuation #396 alone and is expected to be.

Why this rung exists as a PR at all. #271 was retargeted onto this branch as its base, so this branch is on the merge path to master and nothing else reviews the three decisions above. Without a PR here, the four leaves merge down through #394 and this branch - the one carrying the default decision - never merges anywhere.

Not in this PR

Verification

  • Diff against the base: 74 files, +16,270 / -376, of which the reconcile commits themselves touch three code files - README.md, pom.xml, PcDispatchSwitch.java (+60 / -20) - plus thirteen docs/inflight/ notes.
  • The seam-on evidence lane, seam off then seam on, on the merged tree; the report is parallel-consumer-streams/target/seam-on-evidence-report.txt after bin/ci-streams-seam-on-evidence.sh, re-derived rather than quoted, per the triage note.
  • Every CI lane on the four leaves is either green, the ledgered StreamThreadTest.shouldLogAndRecordSkippedRecordsForInvalidTimestamps[3] flake, or a Maven Central read timeout that the cache-warm commit below this rung fixes.

Checklist

  • Docs updated - parallel-consumer-streams/README.md (stream time lags as well as overtakes; the default's fourth trigger), PcDispatchSwitch javadoc, and the docs/inflight/ notes named above
  • User-facing feature documentation data added under docs/features/ - N/A - the module is not published and the seam defaults off; there is no user-facing feature to document until a rung makes it publishable
  • Tests added/updated - N/A - the merged-in tests belong to the sibling rungs; this rung adds no test, it runs the whole set and records the outcome
  • docs/inflight/ working note (pr-/branch-) started at the PR's first commit - docs/inflight/branch-ks-streams-workstream.md is the workstream's note and was updated on this branch; the reconciliation's own state lives in core-streams-punctuation-diverges-in-three-measured-ways.md and test-streams-seam-on-divergence-triage.md, both edited at the reconcile commit
  • Title & body reflect the final content of this PR
  • Ran ce-simplify and ce-code-review locally - N/A - the rung's own code change is 60 lines of javadoc, README and pom; asking for @claude review this on the PR instead

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkRtYPnDuAPLEmHFYSnAiJ

astubbs and others added 30 commits September 1, 2026 00:49
…ith the controls that keep it honest

Adds parallel-consumer-example-streams-pc: a main() that starts its own broker in Docker
and runs one workload twice - stock dispatch, then PC dispatch - varying only
PcDispatchSwitch, then prints both latency distributions side by side. The topology is
ordinary Kafka Streams with no Parallel Consumer API in it; taking the dependency is the
whole integration.

The effect it shows is head-of-line blocking: one 1500ms record at the head of a single
partition with 24 fast 25ms records behind it on other keys. Under stock dispatch every
fast record waits for it; under PC dispatch they do not. Measured on this machine at 13.3x
on the median fast record and 1.51x on the whole-batch drain, with the median reading
between 13.3x and 19.0x across five consecutive runs. No figure from the branch this was
copied from is repeated: those were measured without wake-on-work and do not describe this
build.

The switch is turned on EXPLICITLY, in both arms. It defaults off here, so an arm that
merely omitted the call would silently be a second stock arm and the comparison would be of
one thing against itself. That default is also the demo's subject: unsupported topology
shapes are not yet refused, so an opt-in preview is the honest configuration, and this
module is exactly the artifact that opts in.

WHAT THE DEMO REFUSES TO LEAVE TO TRUST

- ClasspathGuard. The patched Kafka classes take effect only by winning classpath order,
  and when they lose nothing throws - the run would print plausible numbers meaning nothing
  at all. It resolves each patched class first, prints where it loaded from, and aborts
  loudly if the stock jar won. It then asks the loaded StreamTask for the pcDispatcher
  field the patch adds and prints its declared TYPE, because provenance and content are
  different questions with the same happy answer. Its unpatched control is now
  PartitionGroup: not in this branch's patch set, and the exact thing the seam routes
  around, so seeing it come from the stock jar says the seam replaced dispatch and left
  Kafka's own queueing alone.
- PcDispatchCounters, printed per arm. dispatchedToPool is incremented at exactly one place
  in the codebase, so 0 in the stock arm and 25 in the PC arm is evidence rather than
  assertion. splitPollWaits and wakesOnWork are printed beside them, separately, because
  wake-on-work is a second mechanism that arrives on the same rung and would otherwise be
  credited to the seam.

THE NEGATIVE CONTROL MOVED, AND SO DID THE CHECK ON IT

The same workload with every record on ONE key, where PC's key ordering leaves no
concurrency to exploit. On the branch this was copied from that arm LOST, at 0.69x, and the
demo's prose explained the loss and said the fix was a later change. That fix - the split
poll wait - is present here, and the arm now lands on parity: 0.99x, 0.99x, 0.98x, 1.01x
across four runs, plus one reading 1.19x.

That one reading is why the section's check was rewritten. The inherited check asked which
DIRECTION the median went, which is a sound test of an arm that loses and a coin toss for
an arm at parity - it fired on the 1.19x run and would fire on roughly half of all healthy
runs. It now tests a band around parity, in either direction, because a PC WIN here would
void the headline rather than strengthen it.

A NOISE FLOOR, MEASURED INSIDE THE RUN

The 1.19x run's two STOCK arms differed by 1.20x. They do byte-identical work - stock Kafka
Streams ignores keys, and the per-record cost here is chosen by value - so that difference
is pure variance, and the control had measured nothing. The summary now prints it as a
NOISE FLOOR row and tells the reader to read every ratio against it. Without it on screen
that run looks like a finding.

PROVEN ABLE TO FAIL, AND THE PREDICTION THAT WAS REFUTED

The dispatch switch was neutered so the PC arms ran stock, with six predictions stated
first. Five held: dispatchedToPool read 0 and the per-arm warning fired, splitPollWaits
read 0 and its warning fired, the summary printed its DO NOT QUOTE banner, the negative
control stayed green, and ClasspathGuard still passed - localising the loss to the switch
rather than to the build.

The sixth was refuted, and it was mine. The headline check asked only whether PC came out
ahead at all; the sabotaged run measured 1.01x, satisfied it, and printed "a fast record no
longer waits for an unrelated slow one" over a run in which every fast record had waited. A
check a broken run can satisfy is not a check. It now requires a margin far outside any
measured noise - 2.0x, sitting in the empty space between a sabotaged 1.01x and a real
13x-19x - and re-running the same sabotage fired it, with the negative control still green.
That asymmetry is what says the right thing broke.

LatencyScenarioTest pins that case in the ordinary build, because a passing demo run never
enters the contradiction branch and the demo itself is behind -Pdemo, so nothing in CI would
otherwise reach it. Verified red by relaxing the threshold to 1.0: exactly that one case
failed.

TWO GATES THIS MODULE MET THAT ITS SOURCE BRANCH DID NOT

Neither is a copy defect; both fire on our own code and are fixed rather than excluded, as
the machinery rung said would happen.

- Error Prone ReturnValueIgnored on the guard's reflective getDeclaredField. Fixed by using
  the result - printing the field's declared type - which is better evidence than the bare
  presence check it replaced.
- forbiddenapis on Console's locale-dependent String.format. Fixed with Locale.ROOT, which
  the report wanted anyway: it is a fixed-width table of grouped numbers, and a locale that
  groups differently would misalign the columns a reader compares down.

The demo is gated behind -Pdemo and bound to process-classes, so an ordinary build never
starts a broker. parallel-consumer-example-streams is untouched: it is the
provably-unpatched baseline arm, and a PC dependency there would destroy that evidence. The
new module is added to both Maven Central deploy exclusion lists, so a release cannot
publish a demo.

Copied from #271's feats/ks-streams-pc-example branch and adapted:
the Java packages renamed io.confluent to bz.stub, StreamThread moved from the guard's
unpatched control into its patched set, and the cross-references to a
HeadOfLineBlockingBenchmarkTest that does not exist on this stack removed rather than left
pointing at nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Um4ssjUsPh8ng73U7iKvdE
…ams seam

The workstream signpost listed "the example module" among the things still off master.
It is not, now, and the correction is worth more than accuracy: the note's whole complaint
is that an agent reading it finds sideways references to the work and no way in. A runnable
demo that prints its own evidence is that way in, and leaving it in the not-yet list would
send the next reader to the branch forest for something master already has.

Says what the demo shows rather than only that it exists - both arms, the dispatch counters
as evidence, the negative control and the within-run noise floor - because "there is an
example module" does not tell a reader whether it answers their question.
… dragging two CVEs onto the runtime classpath

Three findings from the module's first CI run, all of them things a new module in this repo has to
do and none of them visible until it existed.

WIRE UP ARCHUNIT

EveryModuleWiresUpArchUnitTest failed the Unit Tests lane: every module with test sources needs its
own TestConventionsArchTest, because ArchUnit imports from the classpath of the module it runs in,
and without one it silently does not run there at all. A sibling in
parallel-consumer-example-streams names the package bz.stub.parallelconsumer.examples.streams, which
looks as though it would cover this module's .pc sub-package - it does not, and the file says so, so
the next reader does not delete it as a duplicate. The core test-jar comes with it, for the one class
that holds the shared rules, at test scope so it cannot reach the demo's runtime classpath and
disturb the ordering the whole mechanism depends on.

NO SLF4J BINDING, AND THE CLAIM THAT SAID THERE HAD TO BE ONE IS WRONG

deps: whole-tree CVE scan flagged logback-classic 1.6.1 (CVE-2026-19880, CVSS 6.3) in this module and
nowhere else. That is not a coincidence: the scan audits the RUNTIME scope, logback is test scope
everywhere else in this repo and therefore invisible to it, and this module's compile-scope
declaration was the first thing in the tree to put it there.

The inherited pom comment defended that declaration on the grounds that without a binding, main()
warns about missing providers and Kafka's INFO logging then buries the report. The first half is
true; the second half is not, and it was worth measuring rather than believing. SLF4J 2.x with no
provider is a NO-OP, not a fallback to console. Measured: a run without logback emits three warning
lines at startup and then ZERO log lines between the classpath check and the summary. Removing the
dependency made the report cleaner, removed the finding at source, and cost the module two
dependencies and a config file it did not need.

The pom now records that as a decision rather than an omission, with the measurement, so re-adding a
binding is a change somebody has to argue for.

PIN COMMONS-COMPRESS PAST ITS TWO ADVISORIES

Same gate, same cause, different dependency: testcontainers has to be compile scope here because
DemoBroker lives in src/main, and it resolves commons-compress 1.24.0, which carries CVE-2024-25710
and CVE-2024-26308. Both are denial-of-service on malformed archives, both first fixed in 1.26.0, and
neither is reachable - testcontainers uses this to build Docker contexts and copy files in, never to
parse an archive this demo did not create. A fixed version exists, so it is pinned rather than argued
about in an exclusion entry, and verified by running the demo end to end against a real broker on
1.28.0 rather than by assuming the API held. Module-local, for the reason example-streams gives for
its own jackson pin: this is the only module putting testcontainers on a non-test classpath, so a
root pin would change what every other module's tests run against to fix a problem only this one has.

NULLAWAY ON THE TWO METHODS THIS BRANCH ADDED

Both LatencyScenario.contradiction and stockMedianMillis were designed to return null, and NullAway
said so. There is no nullability annotation library on this classpath; the codebase's idiom is
Optional, so they return Optional<String> and OptionalLong. The remaining NullAway and Error Prone
warnings on these files are inherited from the branch this module was copied from and are triaged in
the PR body rather than silently left.

Verified: the module's tests pass, and the demo runs end to end - 19.32x on the headline median, 1.00x
on the negative control, 1.00x noise floor, no warnings. Two parallel-consumer-core tests
(processInKeyOrder, JStreamParallelEoSStreamProcessorTest.testConsumeAndProduce) failed once in two
local runs and passed on the second with nothing changed, both with the "expected N, got 0" shortfall
that docs/inflight/bug-857-family.md already records as this box being saturated rather than a
regression. This branch touches no core code.
…iff that are worth answering

bin/check-pr-analysis-surfaces.sh reported seven findings on lines this PR wrote, none inherited.
Two of them are right and are fixed; the other five are one detector making the same objection to the
same deliberate shape, and are left with the reason recorded here rather than in five suppressions.

Fixed:

- PSC_PRESIZE_COLLECTIONS - DemoBroker.createTopics allocated its list unsized when the exact count is
  the varargs length it already has.
- OCP_OVERLY_CONCRETE_PARAMETER - printSummary took List where it only iterates, twice. Iterable says
  what the method actually needs.

Not changed, and it is the same finding five times: CE_CLASS_ENVY on printIntroduction, printClosing,
printSummary, ClasspathGuard.failLoudly and verifyDispatchSeamIsPresent - each "excessively uses
methods of another class", that class being Console. Every one of them is a report section, and
Console is the report writer, so calling it repeatedly is what they are for. The refactor the detector
is asking for would move this demo's explanatory prose inside the formatting helper, which is where a
reader would never look for it. The `static: spotbugs` gate passes either way; this is the advisory
surface, answered rather than left.
…rgence nobody has named

The seam-off execution has always been the behaviour-preservation gate: the patch changes nothing
while the switch is off. This adds its opposite - what Apache Kafka's own suite says about the path
this module actually ships - and, more usefully, a verdict on it.

THE PROBLEM WITH A SEAM-ON RUN, AND THE SHAPE THAT SOLVES IT

Seam-on failures are expected. The refusal envelope throws where stock would have run a construct;
the committed offset is Parallel Consumer's frontier rather than Streams' consumedOffsets; a record
handed to a worker has not been processed by the time process() returns. A gate that goes red every
build for those reasons is a gate people stop reading, which is why the branch forest's version of
this run recorded its failures and gated on nothing.

So the run and the verdict are SEPARATE surefire executions. kafka-upstream-tests-seam-on carries
testFailureIgnore and never reddens a build; seam-on-evidence-classification reads what it wrote and
fails when a divergence matches no named mechanism. That split is the only arrangement in which
"these failures are expected" and "a new divergence fails the build" are both true.

THE SEAM-OFF RUN IS THE CONTROL ARM, AND IT IS DIFFERENCED CASE BY CASE

A divergence is a case that PASSES with the switch off and FAILS with it on. Nothing else is
attributable to the dispatch path: a case failing in both arms is failing for a reason the seam had
nothing to do with, and counting it would credit a change that did not cause it. The classifier
therefore reports four disjoint sets - divergences, failing-in-both, passing-only-seam-on, and the
control arm's own failures - rather than one number.

MECHANISMS ARE DERIVED FROM THE CODE WHERE THAT IS POSSIBLE

RefusedConstruct takes its marker as the longest common prefix of every message PcUnsupportedConstruct
can produce, and attributes a failure to whichever constant the message names. No refusal string is
copied, so the refusal envelope - which belongs to another rung and moves independently - can gain or
lose a construct with no edit in this lane at all.

CommitFrontierEncoding recognises an assertion that renders an OffsetAndMetadata, which is an
assertion about the commit encoding whatever the case is called. Its limitation is stated rather than
hidden: an offset REGRESSION inside a Kafka unit test renders identically and would be explained by
this mechanism too. Nothing in the assertion text separates the two, so the lane does not pretend to -
what defends offset correctness is a broker-backed law, not this.

WHAT CANNOT BE DERIVED IS ATTRIBUTED IN THE LEDGER, NOT HERE

"This assertion fails because Kafka's test drives the task synchronously" is a judgement about a test,
not a fact recoverable from `expected: <5> but was: <0>`. Those attributions live in
docs/inflight/test-streams-seam-on-divergence-triage.md, keyed by method rather than by
parameterisation, and the lane reads them. Three consequences, all deliberate: the reasoning is
reviewed as prose by whoever changes the semantics under it; the PR that closes a mechanism deletes
its entries in the same change; and an entry that stops matching is reported as stale rather than
rotting silently.

THE FLAKE IS ABSORBED BY MECHANISM RATHER THAN BY A RETRY

StreamThreadTest.shouldLogAndRecordSkippedRecordsForInvalidTimestamps has been failing this module's
oracle intermittently for weeks, and its note says so. Because the oracle fails fast, one sighting used
to abort the lane before the measured arm ran at all - a full re-run of Kafka's suite twice, for a
failure already diagnosed. The lane now relaxes THAT ONE EXECUTION and makes the verdict itself: every
control-arm failure must carry a flaky-case marker in docs/inflight/, or the lane stops and says there
is nothing trustworthy to difference against. The ordinary build's oracle is untouched and still fatal.

The flake registry is consulted FIRST on the control arm and LAST among the divergence mechanisms, and
that ordering is load-bearing - see the triage note's own finding.

PROVEN ABLE TO FAIL, THREE WAYS, WITH THE PREDICTION STATED FIRST

Sabotage, end to end. Removing the close-time commit-state publication from PcTaskDispatcher.close()
was predicted to make exactly one case, shouldClearCommitStatusesInCloseDirty, fail seam-on while
staying green seam-off, with no mechanism recognising a bare commitNeeded assertion. Exactly that: one
unexplained divergence, named, with its recorded failure and stack; every other divergence still
explained by the same six mechanisms; the script exited 1. Reverted and verified byte-identical by
checksum. That run also demonstrated the control-arm path unplanned - the invalid-timestamps flake
fired seam-off, and the lane named it from its ledger note and carried on instead of aborting.

A stale report directory. Running the classification execution alone against report directories from
an earlier run failed on the freshness check, naming the file, when it was written and when the build
started. maven.build.timestamp is passed in for exactly this, and is required rather than optional: a
check that turns itself off when its input is missing is the failure mode this lane exists to avoid.

A missing report directory. The same execution against deleted directories failed with "That is an
ERROR and not an empty result", rather than reading an empty glob as zero failures.

SeamOnDivergenceClassifierTest pins all three at unit level on synthetic reports, plus the cases the
end-to-end runs cannot cheaply reach: an arm whose pin did not arrive (both arms recording
dispatch=false differences the control against itself and reports a flawless zero), arms covering
different classes, an attribution to an undeclared class, and a triaged attribution beating a flake
marker on the same case.

NO COUNT APPEARS IN THE MACHINERY OR IN THE LEDGER. The per-arm totals are printed for a reader and
the verdict does not consult them; which cases diverge moves with the branch, the Kafka version and
the seam, and the command that re-derives them is in the note and in the README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ot a local-only claim

A lane that only ever runs on the machine of whoever wrote it is half a deliverable: the first person
to change the dispatch path's semantics learns nothing, and the ledger of attributions rots with
nobody to notice.

RED HERE IS NOT EXPECTED, WHICH IS WHAT MAKES IT DIFFERENT FROM THE QUARANTINE LANE

That lane runs tests known to be failing, so its verdict lives in the step summary and its run step
carries continue-on-error. This one asserts that every seam-on divergence is EXPLAINED, and green is
the ordinary state. Red means one of exactly two things, both wanting a human rather than a re-run: a
divergence nobody has attributed - something changed the dispatch path's semantics - or an attribution
in docs/inflight/ that went stale because a rung fixed the thing it described. So the job gates.

PATH-SCOPED, AND THE LEDGER IS IN THE TRIGGER SET

The run costs Kafka's whole suite twice, and nothing outside the streams module, the lane script and
docs/inflight/ can change its answer. The ledger belongs in that set on purpose: an edit to an
attribution changes the verdict exactly as much as an edit to the code does, and a triage note edited
without re-running is how a lane starts lying.

The whole measurement goes to the step summary and is kept as an artifact with both arms' surefire
reports, so a red run can be read without re-running it.

A red on the dependency fetch is a download failure rather than a divergence, and the step says so:
this module pulls Kafka's sources and test-sources CLASSIFIER artifacts by explicit artifactItems,
which dependency:go-offline does not resolve, so the cache-warming job does not cover them. That gap
is already recorded in docs/inflight/, and this workflow points at it rather than restating it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Kafka Streams' own control paths

Two things Kafka Streams does for itself stopped working the moment records went to
a worker pool, and both failed silently. This rung restores them.

FAILURES REACHED THE TaskManager WITHOUT THE TYPE IT DISPATCHES ON.
TaskCorruptedException and TaskMigratedException are not errors; they are Kafka's
control-flow signals - close this task dirty, revive it, re-initialise its state.
StreamThread catches them BY TYPE. Every worker failure came back out of process()
wrapped in a StreamsException, so recovery never fired and an application stock
Streams would have recovered shut its client down instead. ProcessorNode.process
already rethrows those two unwrapped, so mirroring stock's own catch ladder is the
whole of the fix: a StreamsException passes straight through.

AND IT REACHED IT TOO LATE FOR THAT TYPE TO BE USEFUL. Even unwrapped, the failure
arrived at the top of a later pump - possibly in a later runOnce. Kafka's own
StreamThreadTest.shouldReinitializeRevivedTasksInAnyState asserts the stock shape
directly: one runOnce, one throw. A pump that has run out of work now waits,
bounded, for the outcome of what it already dispatched, which is enough because
Kafka's loop calls process() again while it reports progress: the pump that
dispatched the record returns "progress", the loop comes back, and the second pump
waits and throws.

The unwrap alone was declined once and recorded rather than taken, and this is why:
with the timing untouched no test can distinguish it from its absence, and a change
no test holds in place is one the next refactor removes for free.

NOTHING COMMITS PAST A FAILURE (#271's open thread). Kafka's
loop is process, punctuate, commit - so a failure landing in that window let a
scheduled commit make another key's completed offsets durable for a task about to be
closed dirty and rewound. For a TaskCorruptedException that is worse than a
duplicate: recovery wipes the state stores those offsets claim to cover. A pending
failure therefore fences commit-data collection. It deliberately does NOT fence
hasUncommittedWork(), which is the opposite question - that one is what validateClean
turns into a TaskMigratedException so the task closes dirty, and fencing it would
make a failed task look clean to close. Two tests pin the pair.

A failure also stops further dispatch, which bounds what runs after a known failure to
what was already in flight rather than to a whole poll budget. In-flight records are
left to finish rather than interrupted: a worker cancelled mid-chain leaves a
half-forwarded record.

ONE DELIBERATE DIVERGENCE FROM STOCK, AND IT IS THE INTERESTING ONE. Stock rethrows a
TimeoutException raw, which TaskExecutor reads as retriable - keep the task running,
come back to it - and that is safe there only because the record is still in the queue
and will be re-selected. Here retries are disabled and the failure bar has closed
dispatch, so honouring it would mean process() returning false for ever,
task.timeout.ms never tripping and the partition staying paused: zero throughput, no
exception, nothing logged, from an ordinary broker timeout. Matching the exception
TYPE would have broken the exception CONTRACT, so it is wrapped.

NOTHING BOUNDED INFLOW BUT HEAP. Stock pauses a partition once its RecordQueue passes
buffered.records.per.partition. On this path PC holds the records,
partitionGroup.numBuffered() answers zero, and the pause never fired. It is applied
now from the same threshold with the occupancy read from PC, and the resume mirrors
the pause exactly - only partitions this path paused are ever handed back, because
Kafka pauses partitions for reasons unrelated to buffering and a resume computed from
the whole assignment would restart fetches on those. numBuffered() and the
active-buffer-count metric follow.

THE OCCUPANCY IS DERIVED FROM PC'S INCOMPLETE SET, NOT COUNTED ALONGSIDE IT, and this
overrides a decision recorded on the branch this work reconstructs. That branch kept a
standalone count raised by predicting, per record, what PC would accept - explicitly
because getNumberOfIncompleteOffsets() counts a failed record for ever and would pause
that partition permanently. The objection is real and is answered rather than ignored:
a failed record is never subtracted back out of the handed-out tally, so it cancels
itself out of the derivation. What the prediction could not answer is the defect
ranked second on this workstream's handover: PC accepts an offset into its incomplete
set while ProcessingShard.addWorkContainer DROPS the arriving container when a live one
for that offset is resident - which is what happens to a re-delivered offset after a
seek, an offset reset or corruption recovery. That record is never handed out, nothing
decrements it back, and the partition is paused for good with no symptom but a topology
that has gone quiet. Deriving is immune to it, because the incomplete map's insert is
idempotent per offset. A test drives exactly that shape and asserts the count comes
back to zero, which a permanent residue cannot do.

A negative occupancy is clamped, COUNTED AND LOGGED rather than swallowed - that is the
drift direction which removes the memory bound with nothing to say so, and a bare clamp
is what hid a conditional-decrement bug in the engine's own intake gate.

THE SETTLE WAIT IS GATED ON RUNNING OUT OF WORK RATHER THAN OUT OF CAPACITY, and a
measurement forced that. Unconditional, it also fired in the saturated case - pool
full, plenty of work, nowhere to put it - where the StreamThread's next act would have
been to poll. With the pause switched off so nothing else bounded inflow, peak
occupancy over a 600-record backlog measured 36 with the wait and 596 without it: a
sixteen-fold intake throttle, and worse, a second undesigned memory bound sitting
inside the CONTROL ARM of the memory-bound proof. The residual the gate leaves is
stated rather than glossed: at poolSize 1 a single in-flight record fills the pool, so
the same-runOnce guarantee is a guarantee about a pool with a spare slot.

EVIDENCE. Seam-off oracle run before and after on this machine, whole test phase, per
class out of target/surefire-reports-kafka-upstream/: identical and unchanged, same
assumeTrue/assumeFalse skips. Seam on, same experiment with one term changed:
shouldReinitializeRevivedTasksInAnyState goes green on both parameter combinations this
module supports and shouldRecordBufferedRecords goes green with the backpressure work,
with ZERO regressions. Its third combination stays red under Kafka's private
processing-threads config, where DefaultTaskExecutor calls task.process from its own
thread - out of scope since the seam landed and named as such in PcTaskDispatcher's
threading contract, not a residue of this defect.

PROVEN ABLE TO FAIL. Three sabotages, each with its prediction stated first, each
reverted and verified byte-identical by checksum.

- The type passthrough removed: predicted exactly those two upstream cases red and
  nothing else moving. Exactly that, 65 failing to 67.
- The settle wait removed: same prediction, same result - and our own unit suite stayed
  entirely green, which is the honest reading that no unit test covers pump-level
  delivery timing.
- The commit fence removed: predicted one unit case red, on the fence, with its control
  arm (the same shape with no failure, where the frontier IS offered) staying green.
  Exactly one failure, and it was that one.

The memory bound has a control arm rather than a sabotage, which is stronger: two arms
differing in exactly one term on the same broker, topology, data, cost and JVM, with
occupancy sampled from a watcher thread because the interesting quantity is a peak and
a peak is invisible to any assertion made after the run. Over a 600-record backlog it
measured 596 held with the pause off against 30 with it on - the fixed arm landing
exactly on its derived bound, which is what says the derivation is right rather than
merely generous.

The patch was regenerated by the prescribed procedure - process-sources, edit under
target/kafka-patched, bin/regen-patch.sh with no maven run in between - never
hand-edited, and verified BY CONTENT rather than by hunk count: every line the previous
patch added is still added except the sixteen this rung deliberately rewrites, and no
removed line was lost.

Copied out of #271, which keeps the source, the measurements
and the open review threads. PC-side classes are renamed io.confluent to bz.stub; the
patched Kafka sources stay in org.apache.kafka.
… settles, and what it reserves

The dispatch seam has now had three reasons for defaulting OFF, and each was closed by
a measurement that named the next one. The third - a control-flow exception raised
inside a processor never reaching Kafka's recovery - is closed by the commit before
this one, and this time the measurement named nothing. That is a more delicate state to
leave behind than an open defect, because "no reason left" reads as "flip it", and the
flip is not this rung's to make: sibling rungs of #255 are in flight against the
same base and every one of them moves the seam-on numbers.

So the three sites that carry the decision are re-pointed rather than left stale -
PcDispatchSwitch's javadoc, which owns it, the module README, and the pom's module
description - and they now say the same thing: the trigger is closed, the numbers that
close it, and that the flip waits on a fresh seam-on measurement taken on the reconciled
module. docs/inflight/streams-dispatch-default-flip-is-reserved-until-the-rungs-reconcile.md
carries the reservation itself, so a reader arriving from the inflight index finds it
rather than inferring it.

The README also gains what a field tester needs to have changed under them: the third
switch and what turning it off costs, a backpressure section carrying the two-arm
measurement rather than a claim, and an error-surfacing section covering the type, the
timing, the commit fence and the one place this path deliberately diverges from stock
(a TimeoutException is wrapped rather than rethrown raw, because matching stock's TYPE
would have broken stock's CONTRACT). The unsupported-and-not-refused list loses the
typed-exception entry and gains Kafka's private processing-threads mode - which was
always out of scope but was only ever recorded in a javadoc, so a seam-on run showing
that parameter red had nothing to recognise it by.

Two write-ups land in docs/solutions/ rather than in the notes, because both outlive
this work:

- An async seam owes a control-flow exception BOTH its type and its timing, and fixing
  one leaves an unverifiable change rather than a partial one. It also carries the
  structural argument this rung got wrong: "the pump handed out nothing" conflated an
  idle pool with a full one, and waiting in the second case throttled intake
  sixteen-fold - which showed up not as a slow benchmark but as a contaminated CONTROL
  ARM, where every assertion still passed.
- Predicting what a collaborator will accept drifts from what it accepted; derive the
  count from the collaborator's own state. Three defects go together rather than one at
  a time, and the objection that had blocked deriving for a release turned out to be an
  objection to one particular derivation.

The typed-exception note is deleted by the commit that closes it, its content having
moved to the first of those. The settle-wait note is new and is NOT a "we should
benchmark this some day": it records the residual the gate leaves (at poolSize 1 a
single in-flight record fills the pool, so the same-runOnce guarantee is a guarantee
about a pool with a spare slot) and the one regime the sixteen-fold figure does not
cover.

No changelog entry, deliberately - the file is generated at release time from these
bodies.
One asterisk pair in the settle wait's javadoc renders literally rather than as
emphasis. Caught by reading the file back rather than by any check - there is no
javadoc linter here.
PartitionGroup.nextRecord() advances streamTime at selection, and the PC dispatch
path never calls it - so partitionGroup.streamTime() sat at UNKNOWN for the life
of every PC-dispatched task. Two things followed, both silently: STREAM_TIME
punctuators never fired, and the PUBLIC ProcessorContext.currentStreamTimeMs()
returned a constant -1. The second is the more likely of the two for a user to
hit, because it is reachable from any Processor with no refused DSL call in
sight.

Stream time now comes from the dispatcher: the lowest stream timestamp still in
flight, or the highest dispatched when nothing is, clamped monotone. The
timestamp crosses the seam in the WorkPreparer's return value rather than being
read off the ConsumerRecord, because the two are different numbers - a
payload-time topology's TimestampExtractor points at a field inside the value.

WHAT IT GUARANTEES IS NARROWER THAN A WATERMARK, and the class javadoc says so
rather than being read as one. The mark never passes a record CURRENTLY IN
FLIGHT, so a punctuation cannot close a window over work inside the chain. It is
NOT an order boundary: the monotone clamp means a later record can carry a lower
timestamp, and dispatchesBehindStreamTime counts exactly those. And it can sit
ABOVE what stock would hold - three records on one partition with three distinct
keys give [0, 2, 2] where stock reports [0, 1, 2], because PC hands records out
per KEY shard. That is pinned as a characterisation test rather than described,
because an earlier version of this work claimed "never ahead of stock", narrowed
the only test that measured the counter-example so it would pass, and shipped a
counter that could not have been non-zero if the claim were true.

THREE HOLES THE SAME WORK RECORDED AGAINST ITSELF AND LEFT OPEN ARE CLOSED HERE,
because each is a property of the mechanism this commit introduces rather than a
separable fix:

- close()'s drain could advance the mark over work a forced shutdown killed.
  awaitTermination expires, shutdownNow() interrupts the chains, each throws into
  recordFailure, and the very next statement is drainCompletions() - which
  released those holds and republished. A streamTimeFrozen flag is now raised at
  the top of BOTH close paths and checked in publishStreamTime, so the mark
  freezes at the last honest value it had. Pinned by
  aCleanCloseThatHasToForceThePoolDoesNotAdvanceTheMarkOverTheWorkItKilled, which
  is also the first coverage the clean-close-with-forced-pool branch has had.
- A RejectedExecutionException from workerPool.execute leaked a hold. The hold
  and the inFlight increment both happen before the submit, and abortAllActive()
  races a pump by design - so an uncompensated hold pinned the mark at that
  record's timestamp forever, turning a wrong counter into a stopped punctuation
  clock. Compensated now, in the drain's own order.
- PreparedRecord validated neither field. A preparer returning a null Runnable
  produced an NPE on a worker, which runOnWorker classifies as a PROCESSING
  failure - so with retries disabled it blocked that key's shard for the life of
  the task and reported a topology error. This is the module's public extension
  seam; it fails at its constructor now, naming the alternative.

The seed also reaches pcRecordQueues, not only the dispatcher. RecordQueue passes
its own partitionTime to timestampExtractor.extract(record, partitionTime), and
the PC path's queues are created lazily at UNKNOWN - so a topology using Kafka's
shipped UsePartitionTimeOnInvalidTimestamp threw on the first record after a
restart with an invalid embedded timestamp, where stock recovered. That branch
has no automated gate on this rung, because the only execution running Kafka's
shouldReadCommittedStreamTime* cases pins the seam off; it is verified by a
seam-on run recorded in the PR, and the gate belongs to the seam-on evidence lane.

The refusal reasons that argued from stream time NOT advancing are corrected in
the same pass - 28 @DonotCall and @deprecated strings a user reads as a compile
error, plus the enum they mirror. Leaving them would have made this commit ship a
config lie: the reason is no longer that the clock is stuck, it is that the clock
is a low-water mark over concurrently dispatched work, so which records fall
inside a window and which version of a table a record sees move with dispatch
order. The windowedBy strings argue from observedStreamTime, a different field
this work did not touch, and are left alone apart from one clause that read as if
it named the task's value.

Patch regenerated by the prescribed procedure - process-sources, edit under
target/kafka-patched, bin/regen-patch.sh with no maven run in between - never
hand-edited, and verified BY CONTENT rather than by hunk count: every added and
removed line of the parent's patch survives except the ones named above, and no
removal was lost.

Seam-off oracle run before and after on this machine, whole test phase, per-class
numbers read out of target/surefire-reports-kafka-upstream/ rather than copied:
identical, unchanged, with the same skips.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…m already claimed it did

The SLF4J-binding exclusion that keeps our own tests on logback and Kafka's fork
on reload4j sits on the SUREFIRE plugin's configuration. Failsafe does not
inherit it - so this module's own integration tests have been binding reload4j
the whole time, while the comment beside the dependency says "our own tests keep
logback (and logback-test.xml)" and src/test/resources/logback-test.xml sat
unused in that lane.

Nothing went red for it, because nothing in the integration lane asserted on a
log line until PunctuatorWarningTest did. What that test got was a
ClassCastException casting an org.slf4j.reload4j adapter to a logback Logger -
which is the loud version of a failure that would otherwise have been an empty
capture list and a wrong conclusion about a warning that had in fact fired.

The Kafka-fork execution is surefire's and keeps reload4j, so
LogCaptureAppender - the reason reload4j is a dependency at all - still works
where it is used.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… say why the one-liner was not taken

THE HANDOVER'S TOP-RANKED DEFECT, decided rather than deferred. It reads:
"WALL_CLOCK_TIME punctuators fire today with no warning, and their effects never
become commit-covered", with hasUncommittedWork() || commitNeeded named as a
one-line candidate that "changes commit cadence for every PC-path caller and
needs its own evidence".

THE CODE HALF OF THAT DECISION SHIPPED TWO COMMITS AGO, inside the regenerated
pc-streams.patch: both punctuation types now warn once per task at registration,
and WALL_CLOCK_TIME is the new one. This commit is the half that belongs at the
site which owns the default's reasons, because that is where the next person
deciding to flip it will read.

THE EVIDENCE SHRANK THE SECOND HALF OF THE DEFECT TO ALMOST NOTHING, and the
three classes that did it arrive in the next commit:

- Effects are not lost. PunctuatorEffectSurvivalTest reads the output topic and
  the store's changelog BEFORE close(), with a five-minute-linger negative
  control arm that must find nothing and does - [] and 0, against 13 forwards and
  13 changelog records in the measured arms. Punctuator forwards and store writes
  reach the broker on the producer's own schedule, before and independently of
  any commit. Non-EOS, which is the whole supported envelope here.
- postCommit DOES run. PostCommitCheckpointGapTest was written to confirm "the PC
  path never checkpoints" and refuted it - 12,000 records checkpointed at
  changelog 11,526 against stock's 11,999 on this machine. What is left is an
  idle-window tail, bounded by the final commit round, with clean close
  checkpointing regardless.
- Commit cadence cannot evidence the candidate at all. PunctuatorCommitCoverageTest
  shows commit-total pinned at zero on an IDLE PC task by TaskExecutor's second
  loop re-asking commitNeeded() after onCommitSuccess has just cleared it.

SO THE ONE-LINER IS NOT TAKEN. What it still buys is a little less changelog
replay after a crash landing in an idle window. What it costs is a commit-cadence
change for every PC-path caller, and a TaskMigratedException on a clean close
after a punctuate-only interval, because validateClean() reads the same answer.
That is not a trade this evidence supports. Loud degradation is.

Refusing punctuation outright was considered and rejected for a different reason:
both types fire and produce correct output for the common shapes, so a refusal
would break topologies that run today - and a punctuator is a call on the
processor context rather than a shape any of the three refusal layers can
inspect. Silence was the one outcome the handover forbade, and it is the one this
removes.

THE DEFAULT DOES NOT MOVE. This rung closed one of the three named triggers -
PcDispatchSwitch's javadoc used to defer punctuation to docs/inflight/ as "a
separate outstanding item, already priced in", and that sentence is replaced by
what is now true: implemented, loud, and with two measured gaps. The
exception-surfacing trigger is still open and lives on a sibling branch. A
default moved on one rung's evidence while another rung holds an unclosed reason
is a default nobody measured, so the flip is stated as a decision for after those
rungs reconcile rather than left for a reader to infer.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ms that refuted three claims

Five broker-backed classes, four of them reconstructed from a branch forest whose
whole value is that its claims changed under measurement. They are carried with
their retractions intact rather than tidied into results, because the retractions
are what stop the next reader re-proposing what was already refuted.

STREAM_TIME PUNCTUATORS RE-FIRE OVER COVERED EVENT TIME AFTER A RESTART.
StreamTimePunctuatorRefireOnRestartTest. Run 1 climbs event time and closes
cleanly; run 2 restarts the same application id with records BELOW run 1's base.
Stock punctuates at the restored mark; the PC path punctuates 60s and 56s below
run 1's BASE - measured here as run 2 firing at 1788212164856/1788212168856
against a base of 1788212224856. Asserted below the base and not merely below the
highest, because a replayed run-1 record would satisfy the looser band and PC's
own docs say completed records beyond the frontier are replayed rather than
skipped. Cause is named in the patch's own seedStreamTime comment: a PC-written
group's commit metadata is PC's frontier payload, not Streams'
TopicPartitionMetadata, so the decode yields UNKNOWN and there is nothing to seed
from. The PC arm asserts the defective behaviour, so the suite stays green and
the defect stays visible; its message says how to invert it on fix.

The control was built and run FIRST, and earned it twice. It failed once because
run 2's "late" records were stamped 2,000ms after the epoch - Kafka's time-based
retention keys on the record's own timestamp, so they were ~56 years old on
arrival and eligible for deletion immediately. It failed again because the arm
expected SILENCE from run 2, and a restored mark still punctuates once, at the
restored value. Both failures were the fixture, and both would have become a
wrong conclusion without a control that ran first.

PUNCTUATOR EFFECTS REACH THE BROKER INDEPENDENTLY OF ANY COMMIT.
PunctuatorEffectSurvivalTest, and the interesting part is what it stopped
claiming. Its first version punctuated, aborted, CLOSED the client, and then read
the topics - and streams.close() runs a clean shutdown whose producer.close()
blocks until every buffered record is sent, so close delivered the effects and
the test reported survival regardless. Settled by probe: linger.ms at five
minutes and the old shape still passed. The read now happens BEFORE close and the
linger probe is promoted to a permanent negative control that must find nothing -
[] and 0 here, against 13 forwards and 13 changelog records in the measured arms.
The javadoc states plainly what it is NOT: abortAllActive() is
workerPool.shutdownNow() and nothing more, so it is not a process-death test.

THE PC PATH CHECKPOINTS UNDER LOAD. PostCommitCheckpointGapTest was written to
confirm that it never does and refuted it: 12,000 records, stock checkpoints at
changelog 11,999 and the PC arm at 11,526. The step the reasoning missed is that
hasUncommittedWork() goes true AGAIN between the commit and TaskExecutor's second
loop whenever more records finish in that window, which under continuous load is
almost always. What survives is a bounded idle-tail gap.

THE COMMIT SENSOR IS BLIND, AND ONLY ON AN IDLE TASK.
PunctuatorCommitCoverageTest. Stock 8 -> 19 over 30 punctuations; stock without a
punctuator flat at 1; both PC arms committed at offset 5 with commit-total 0. The
PC arms assert that CONJUNCTION rather than a lone zero, because a zero alone was
unfalsifiable - and an earlier revision claimed the sensor was structurally
incapable of rising on the PC path at all, which the checkpoint class above
refuted. The claim is scoped to an idle task here, which is the condition all
four arms construct.

THE WARNING ITSELF IS GATED. PunctuatorWarningTest is new: two STREAM_TIME
punctuators and one WALL_CLOCK_TIME punctuator on one task, asserting ONE warning
of each kind - so the warn-once claim is tested rather than asserted - and a
seam-off control asserting a stock task says nothing at all. That log line is the
only channel a user has for a construct no refusal layer can see, and until now it
had no coverage; deleting the wall-clock assertion puts the handover's ranked
defect 1 back into silence.

Every arm asserts its own dispatch-path membership rather than logging it: PC
arms assert the seam dispatched, stock arms assert it did not. Without that, an
arm that silently fell back to the other path still satisfies a zero-valued
headline assertion and the whole comparison becomes two identical runs.

BrokerStreamsIntegrationTest gains committedOffsetOrNull (a group reader that
never subscribes, so it cannot rebalance the topology under test) and
sleepThrough, and closes a leak on the startup-timeout path: the client is
start()ed before the await, so a timeout never returns and no caller-side guard
can close it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… wherever it was written down

Three documents asserted the thing this rung falsified, and each said it in its
own words - so none of them would have been found by grepping for the others.

THE MODULE README's "What is still unsupported and NOT refused" said the
punctuator "simply never fires: no exception, no warning, no output". Replaced by
what is now true, split by punctuation type because the divergences differ, plus
the concurrency clause that applies to both and is the one that can corrupt user
state. A new "Stream time" section describes the low-water mark and - at greater
length than the mechanism - what it does NOT guarantee, because reading it as a
watermark is exactly what makes a windowed operator look safe behind it.

CONCEPTS.md's "Refused construct" argued that most refusals share one underlying
reason: "that clock does not advance on this project's dispatch path". It does
now, so the shared reason is restated as the one that is actually true - stream
time here is a different quantity, so which records fall inside a window and which
version of a table a record reads move with dispatch order. Two entries are added:
"In-flight low-water mark", which spends most of its words on what it is not, and
"Punctuator", which is the construct no refusal layer can see. Both keep the
directory's rule of standing alone - no file paths, no class names.

The docs/inflight/ note "streams-stream-time-punctuation-is-unsupported-and-not-refused"
is retired rather than rewritten into a FIXED narrative, which this directory's own
rules forbid. Its durable half - why a punctuator escapes all three refusal layers -
moves to the README, which owns the refusal surface. What is still open becomes a
new note, because what remains is a different item: three measured divergences
(the restart re-fire, the punctuate/process overlap, and skipped rather than merely
delayed punctuations), two divergence measurements the evidence lane owes, and one
claim about observedStreamTime that this work deliberately did not verify because it
names a different field.

Nothing cited the retired note by name, checked before removing it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… actually observe

The name and the assertions claimed the stream-time hold; the assertions read the
failure count and the in-flight count, neither of which the hold touches. Worse,
the claim is not provable on this code at all: both close paths freeze the mark,
and a fixed pool with an unbounded queue rejects nothing until it is shut down, so
there is no reachable state where a leaked hold moves the mark.

What IS observable, and is what the compensation is for, is the record left in
limbo: before it, RejectedExecutionException left dispatchAvailable - and so the
patched process() - on the StreamThread with the in-flight count already
incremented for a record that would never run and never report. PC holds it
forever, isQuiescent() cannot become true, and every close pays its full
termination wait. Renamed and re-asserted to that, with the hold's unobservability
stated rather than glossed.

Found by writing the sabotage: with the compensation deleted, the old assertions
still passed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…fier fetch timing out

The Unit lane went red on this rung with the streams module FAILURE and every other
module SUCCESS, at maven-dependency-plugin:unpack, on a Maven Central read timeout for
kafka-streams:jar:sources:3.9.2. No test ran - the module dies in generate-sources -
which is exactly the misdirection the note describes, and exactly the failure the rung
below this one diagnosed and left recorded rather than fixed.

Worth one line rather than nothing, because what the note needs and did not have is a
second, independent occurrence. One red lane on one branch is that branch's problem;
the same signature on two branches stacked at different points is the module's, and
that is the difference between "re-run it" and "the warming job does not cover this
artifact".

Deliberately not fixed here. The candidate fix is a change to prepare-deps in
maven.yml with its own verification - the unpack executions logging a cache hit rather
than a Downloading from central line - and that is a workflow unit, not a line in a
feature branch.
…ry was writing a plain int off-thread

Self-review finding, and it is this module's own recorded defect class rather than a
tidy-up. getBufferedRecordCount() is documented and used as callable from ANY thread -
the memory-bound proof's sampler reads it while the owner mutates - and it raises the
underflow counter on the clamped path. That counter was a plain int documented as
owner-thread-only, so the sampler wrote it unsynchronised, and the log-once guard
(bufferedUnderflows++ == 0) could fire twice when two threads both read zero.

Now an AtomicInteger with getAndIncrement, and the threading contract says so rather
than claiming an owner-thread-only field that three call sites contradict.

The rule this brushes against is the one this module already has a write-up for: a
question must never mutate. It still mutates, and that is admissible only because what
it touches is a diagnostic counter no answer depends on - it reaches neither the
WorkManager nor the state the buffered count is computed from. Said at the field rather
than left for a reader to re-derive, because the same reasoning applied loosely is how
the original cross-thread drain got written.

No behaviour change: the count is diagnostic, and the memory bound is unaffected. The
dispatcher suite is green.
…ually fail

The sabotage refuted its own prediction, which is the result worth keeping.
Deleting the freeze at the top of close() left this test GREEN.

Why: close() forces the pool with shutdownNow(), and the interrupted chains do not
reliably enqueue their failures before the drain two statements later has already
polled an empty mailbox. The mark then stays put for a reason that has nothing to
do with the flag. The advance the flag prevents is real - with two killed records,
three of the four orderings empty the hold map and publish the highest timestamp
ever dispatched over work that never completed - but it is a race, and a race is
not something a unit test pins by hoping.

So the test now asserts the field's actual claim rather than one scenario in which
the claim happens to hold: a closed dispatcher's mark is frozen against ANYTHING
that reaches it, and seedStreamTime() is the one route that publishes without a
drain. That assertion goes red the moment the freeze is removed. The original
assertion is kept, because the scenario is the one the defect was reported from,
and is labelled with exactly what it is worth on its own.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tcher into the next one

The low-water sabotage produced one red this rung did not predict -
rebindingMovesTheWakeSignalWithTheOwner, whose precondition counts how many
dispatchers are registered on the current thread. It was not a flake and not
related to stream time: the sabotage made aCleanClose...OverTheWorkItKilled fail
at its PRECONDITION, before the close() that would have deregistered its local
dispatcher, and this class runs SAME_THREAD - so the next test read 2.

A test that closes its own dispatcher has to do it in a finally, or a failure in
it becomes a failure somewhere else. Reported, because a cascade that only appears
under sabotage is exactly the kind of thing that gets filed as a flake later.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ueue seed

Measured with a control arm rather than asserted: removing ONLY the pcRecordQueues
half of the restart seed and re-running Kafka's own shouldReadCommittedStreamTime*
cases with the seam on leaves them green, case for case. The dispatcher half and
pcAwareStreamTime()'s PC branch ARE exercised by that run - the seam is verifiably
on, two dispatchers constructed - but the line that lets a
UsePartitionTimeOnInvalidTimestamp extractor recover after a restart is not, and
Kafka's suite has no case for that extractor to borrow.

Worth writing down because the run looks like coverage. Whoever builds the seam-on
lane needs to add a case rather than assume the existing ones reach it.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…, and it takes the integration lane too

The note predicted that re-running would not reliably help, on the grounds that the
runner is often reassigned to the same region. That prediction has now been tested
rather than quoted: a fresh push reproduced the identical failure, at the same
execution, on the same artifact - and this time the Integration lane went down with the
Unit lane on the same run.

Two things change with that. The exposure is not "one lane occasionally", it is every
lane that builds this module; and re-running has stopped being a reasonable response,
which matters because it is the response an agent reaches for by reflex when a lane
goes red on an artifact download.

So the note now says what a blocked PR should do instead: name this note in the PR and
let the module's own lane stand as the evidence the code is healthy. A local run has
the artifacts in ~/.m2 already, which is why it never reproduces there - and why "green
locally" is a real signal about the code and no signal whatsoever about this.

Still not fixed here. The candidate fix is prepare-deps in maven.yml learning to warm
the classifier artifacts, verified by the unpack executions logging a cache hit rather
than a Downloading from central line. That is a workflow unit with its own evidence,
and smuggling it into a feature branch would leave it unverified in both places.
… way to state

Apache Kafka's own suite is the oracle for behaviour PRESERVATION. It cannot state the properties this
module exists for, because stock Kafka Streams has no arm in which two records of one partition run at
once - so no stock test asserts what happens when they do. These are the arms that do.

WHAT IS NOW ASSERTED AGAINST A BROKER, AND WITH WHAT CONTROL

PcDrivenStreamsProofTest - the PC path's output is identical, record for record, to what stock Kafka
Streams produced. Two controls, and they answer different questions: a same-harness seam-off arm,
which separates "the patch changed the output" from "concurrency changed the output"; and the stock
baseline itself, which is generated OUTSIDE this module.

PcDrivenStatefulProofTest - the same for a stateful topology, which is where Kafka's own store
wrappers read the per-task record context ambiently. The stateless arm's only ambient reader is the
probe this module wrote; this one makes Kafka's own run. The module's existing envelope test already
said this proof was the reason non-windowed aggregation is not refused - and the proof was not on this
stack, so that sentence was pointing at nothing.

CommitFrontierCrashRestartTest - nothing is lost or double-counted across a crash. The committed
frontier is a point below which every record has been processed, not the offset of the last one handed
out, and under concurrent dispatch those are different records.

ShadowedStreamsControlTest - stock, single-threaded Kafka Streams running our generated classes. It is
itself a control: a concurrency change that quietly breaks the serial path is not a fix.

THE BASELINE IS GENERATED IN ANOTHER MODULE, AND THAT IS THE WHOLE POINT

This module compiles patched copies of Kafka's classes into its own build output, which precedes the
kafka-streams jar on the classpath, so EVERY KafkaStreams instance in this JVM runs the patch. A "stock
arm" written here would share every defect the patch introduced and the comparison would prove nothing.
The baselines come from parallel-consumer-example-streams, which does not depend on this module and
asserts its own classpath is stock before recording a row.

The fixture carries the INPUTS as well as the outputs, and the arms here replay those inputs rather
than rebuilding them from a second copy of the generation rules - the two modules cannot share code in
this reactor order, so replaying is what stops the arms drifting apart in what they were fed.

Re-verified here rather than trusted: both fixture tests were run on this branch and the tracked
fixtures still match what genuinely-stock Kafka Streams produces, so the baselines describe this Kafka
version and not the branch they were recorded on.

THE LANE ABOVE DEFERS TO ONE OF THESE, AND SAYS SO

The seam-on evidence lane's commit-frontier-encoding mechanism cannot tell a deliberate encoding
divergence from an offset regression: both render an OffsetAndMetadata in a Kafka unit test's assertion
and nothing in that text separates them. Its javadoc names CommitFrontierCrashRestartTest as what
actually defends offset correctness, because that arm asserts on records that arrived rather than on a
base64 metadata string. An acknowledged blind spot with a named owner is worth more than a predicate
pretending to cover it.

WHERE THIS CAME FROM, AND WHAT WAS DELIBERATELY LEFT

Copied out of #271's branch forest, package-renamed io.confluent to bz.stub,
compiled and run unchanged otherwise. HeadOfLineBlockingBenchmarkTest is NOT carried across: it
isolates head-of-line blocking on a fixture whose own construction bounds its multiplier, and the
settled decision is to lead with backlog catch-up instead. The two files that referenced it now say so
rather than pointing at a class that is not here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… that says when to ignore it

A card issuer screens every authorisation against a fraud-scoring service. The screener has been down,
authorisations have queued, and it has just come back. That is the workload: the canonical Kafka
Streams enrichment shape, keyed the way a real deployment would have to key it, with a skewed keyspace
and a heavy-tailed service cost - the two things that cost this seam the most.

Three arms, not two, and the third is the point of this change.

THE NOISE FLOOR, MEASURED INSIDE THE RUN THAT QUOTES IT

The extra arm is a second STOCK arm, given the same records. Whatever it differs from the first by is
this machine in this state, and that is an upper bound on how far a ratio can move with nothing
changed at all. Without it on screen a run taken on a busy machine looks exactly like a finding - and
that is not hypothetical, it is what #391 found on the example rung, where a
run reading 1.19x on its control had two stock arms differing by 1.20x. The control had measured
nothing and nothing said so.

So the headline assertion no longer asks whether the ratio beat one. It asks whether the ratio beat
the floor by a margin. A direction check is a sound test of an arm that loses and a coin toss for an
arm at parity, and - the reason this rule exists - it is satisfied by a run in which the mechanism did
nothing at all.

PROVEN ABLE TO FAIL, AND WITH A DOCUMENTED FLAG RATHER THAN A CODE EDIT

Prediction, stated first: with a worker pool of one, PC dispatch has no concurrency to exploit, so the
seam-on arm should land inside the noise floor and the headline assertion should fail while the run
stays otherwise healthy.

Exactly that. `bin/streams-benchmark.sh --pool 1` failed one case - the headline - and its message
carried the whole diagnosis: measured 0.99x against a floor of 1.00x, threshold 1.51x, with both stock
arms and the PC arm printed beside it. The steady-state case, which asserts on sample size rather than
on a ratio, stayed green. That asymmetry is what says the right thing broke.

`--pool 1` reaching the harness at all is a fix in this commit. The script has always documented
`--pool N`, and the harness hardcoded four - so a reader could ask for a configuration, be told in
every log line that they had got it, and be measuring something else. The same class of failure the
script's own "an unknown option is an error" rule was written against, arriving by the other route.

The margin rule itself is pinned by NoiseFloorTest, which runs in the ordinary build because it is a
pure function of two numbers and should not need a broker or a `performance` tag to be exercised. Its
first case is the one that matters: a 1.01x observation does not beat even the tightest possible
floor. That is the shape of a sabotaged run, and a direction check passes it.

THE SCRIPT RAN NOTHING ON THIS LAYOUT, AND WOULD HAVE EXITED 0

Carried across unchanged, `bin/streams-benchmark.sh` drove FAILSAFE with `-Dit.test` and `-DskipUTs`.
These benchmarks live in `src/test/java` and this module has no `src/test-integration`, so that
selects nothing, runs nothing, and succeeds. It now drives surefire - and because
`failIfNoSpecifiedTests` has to be off for the Kafka upstream executions' sake, maven can no longer
report an empty selection, so the script asserts a surefire report exists for every class it selected
and exits 2 when one does not. A skip is never a pass.

NO FIGURE FROM AN EARLIER RUN SURVIVES IN THIS DIFF

The forest's numbers were measured on another machine and describe it, so the headline multipliers,
the wake-on-work ablation percentage and the CPU-bound refutation are all removed from the javadoc or
attributed to the branch they were taken on. `DEMO.md` shows the report's SHAPE with the numbers
elided, and says why: a figure written into documentation quietly stops describing the reader's
machine. The run prints its own, and prints the floor beside them.

Two method write-ups come with the suite because its citations were pointing at them - choosing the
statistic that states the claim, and control arms varying exactly one term. Both are records of work
done on the branch forest, so their citations into that forest's plan and benchmark carry paragraph
markers rather than being repointed at files that would make the record describe a run that never
happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ifier timeout - a third run went green

I wrote "it cannot be cleared from inside one" into this note after it took both the
Unit and the Integration lane on one run. The very next run passed every lane. That
sentence was false when I wrote it and would have been inherited by whoever hit this
next, from a note filed under misdirection - which is the wrong file to leave a stale
absolute in.

What the evidence actually supports is the strength the upstream write-up already used,
and I strengthened it past what it could carry: "does not reliably help" means a re-run
is a COIN FLIP, not that it never works. Two consecutive runs failed and the third
passed, which is exactly a per-run condition - the lanes share the runner's route to
Central, so they fail together and they recover together.

Three things the note now says that it could not say before, and they are worth more
than the sentence they replace:

  * The exposure is every lane that builds this module, not one lane occasionally.
  * It is per-run rather than per-branch, so a green run is luck and says nothing about
    the next one.
  * A green lane is NOT evidence this is closed. Closed is the unpack executions no
    longer reaching Central at all, which is what "Delete when" already asks for.

The guidance section changes shape with it. Re-running is legitimate here and is worth
saying so explicitly, because it is not the retry-into-green this project bans: no test
outcome is being hidden, since no test ran. What it must not do is talk anyone out of
leaving the record behind, which is precisely what a lucky green invites.

Recording the correction rather than quietly editing the claim away, because the
mistake is the useful part: I turned "unreliable" into "impossible" in the space of one
commit, on the strength of two data points.
… it pulled Docker into the unit lane

Naming the seam-on classifier in this module's `default-test` execution silently dropped the two
excludes the root pom sets on surefire - `**/integrationTest*/**/*.java` and `**/*IT.java` - because
an `<excludes>` list REPLACES the inherited one rather than adding to it.

The effect was invisible in the only way that matters: everything passed. Every broker-backed arm in
this module lives in an `integrationTests` package, so they were quietly promoted out of failsafe and
into the surefire `test` phase, where they ran, needed Docker, and went green. A unit lane that has
started requiring Docker does not announce it; it just takes longer until the day it runs somewhere
without a daemon.

Caught by reading the run rather than the tick: the module's `test` phase reported far more cases than
its unit suite has, and the class names in the list were the integration ones. Fixed by repeating the
root's two entries beside the new one - `combine.children="append"` would express the intent, but a
literal list is what a reader can check against the root pom without knowing that attribute exists.

Verified both directions: the `test` phase now runs the unit suite alone with the seam-off oracle
still at its usual figures, and `verify` runs every broker-backed arm including the ones this stack
adds.

The generated stock-baseline fixtures are classified in the copyright scanner as regenerated-by-a-
generator, which is the first of the four reasons that table accepts and the same one `*.patch`
carries. They are rewritten wholesale by `StockBaselineFixtureSupport`, so a header would be
destroyed on the next run. Two explicit paths rather than a blanket `*.tsv`: the next `.tsv` to
arrive should be classified on its own merits, not inherit an exemption written for these.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… timeout, which settles the attribution

The run after the green one carried a documentation-only change - no Java, no pom, no
workflow - and reproduced the identical failure, same execution, same artifact, on both
the Unit and the Integration lane.

That is a control arm rather than another sighting, and it is the piece this note was
missing. Everything before it was consistent with "something about that branch": a
markdown diff is not. There is now no reading in which the change under review is
implicated, which matters because the note is filed under misdirection and the
misdirection it guards against is precisely an author reading a red lane named "Unit
Tests" as something they broke.

Worth the churn of another push to record, on the reasoning this note itself now
carries: a lucky green must not talk anyone out of leaving the record behind, and that
applies to me as much as to the next reader.
…a developer's local repo

`-pl .,parallel-consumer-streams` builds neither `parallel-consumer-core` nor its tests jar, and this
module needs both. On a developer box they are already installed, so the lane ran; the first CI run
failed in three seconds on an unresolved `0.6.0.0-SNAPSHOT`, which is the useful direction for that
mistake to fail in - it is the same class as a check that passes because of something only the
author's machine has.

`-am` instead, which is what `bin/chaos-test.sh` already does for the same reason, and which also
brings the parent in so the enforcer's reactor-convergence rule is satisfied without naming the root.
Core's own tests are filtered out by the `-Dincluded.groups` the lane already passes, so the extra
module costs a compile rather than a suite.

Re-run locally after the change: both arms produced, every divergence explained, exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…f-master list

The workstream signpost lists what master lacks so an agent listing this directory can see the shape
of the work. This rung removes its own entry rather than leaving a reader to infer it from a PR
number, which is the same discipline the rungs below used.

What is left on that list is now all semantics - refusal is already down, lifecycle is the parent,
and what remains is stream time, error surfacing and the example module. The distinction is worth
stating rather than implying: master will lack the behaviour, not the means of measuring it, so the
next rung arrives with a lane that can already tell it whether it changed anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…m that did not reproduce it

`PcDrivenStatefulProofTest.pcDrivenAggregationMatchesTheStockBaseline` timed out on repetition 2 of 3
in the Integration Tests lane - the first run that put that class on CI. One sighting, written down
rather than re-run past.

WHAT RULES OUT THE COMFORTABLE READING

The StreamThread's own progress line, logged as the drain gave up, reports 1207 polls against 14
completions in two minutes. A thread short of CPU polls LESS; this one went round its loop briskly and
finished almost nothing, which is the shape of work dispatched and not completed rather than of a
starved machine. The ambient probe was clean - no rebalance dwell, no lag stagnation, no frozen
partitions - so the group progressed at the broker while the task did not progress at the processor.
That is why "the runner was busy" is not available here, however busy it was, and why nothing has been
loosened, shortened or serialised.

THE CONTROL ARM, AND THE TERM IT COULD NOT VARY

Run rather than guessed: the whole streams integration suite locally at the lane's own settings,
forkCount=4 with reuseForks=true, alongside core's integration suite - green, including this class.
Uncontended at forkCount=1, green in three separate runs.

The instrumentation is confirmed rather than assumed. Failsafe pins no <forkCount> of its own, unlike
surefire where the root pom's <forkCount>${surefire.forkCount}</forkCount> makes a bare -DforkCount a
silent no-op, so the property does arrive - and the autopsy dump from the CI failure shows the lane
running with the same two values.

So contention is NOT established and is not being claimed. The one term the local arm cannot vary is
core count: four JVMs and four brokers on a developer machine is not four JVMs and four brokers on
two. The note names the experiment that would settle it - the same runner at forkCount=1, which holds
the machine fixed and varies only the load - and records the two exception lines that would explain
the rest immediately if they belong to this fork, unattributed because four forks share one
interleaved log and a class that interrupts workers by design was running in the same window.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…PR has landed

`bin/check-branch-self-reference.sh` caught the new note citing the pull request that is carrying it.
The citation is right and stays - a merged PR is a permanent link, and it is the shortest way to say
which change first ran that class on CI - so the fix is to word it as it will read afterwards and
confirm that with the `post-merge: checked` markers, the same treatment the invalid-timestamps flake
note already carries.

One sentence did need rewriting rather than confirming: "the lane re-runs on every push to that
branch" describes something that stops existing at the merge. It now names the triggers that outlive
it - every push and every merge to master - and says why a second sighting is the cheap next step: one
occurrence cannot tell a rare race from a one-off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
astubbs and others added 15 commits September 1, 2026 14:01
…t read differently are not comparable

The duplicate-code check flagged a genuine clone on this PR - `consumeByKey` plus `flatten`, shared
between the seam-on dispatch arm and the stock control arm, byte-identical apart from the deadline.
Both engines agreed on it, which is the tell that it is real rather than jscpd counting a shared
import block.

It is worth fixing rather than explaining away for a reason specific to this module: these two are a
matched experiment. The whole method here is comparing arms that differ in exactly one term, and a
copy of the reading logic in each is a second term waiting to appear the first time somebody tunes
one of them. Drifted topic-reading logic is already a named source of flaky CI in this repository.

Now on `BrokerStreamsIntegrationTest`, which both extend and which already holds precisely this kind
of thing - the same broker, the same serdes, the same starting offset. The DEADLINE stays a parameter
at each call site, because how long an arm waits is a property of the workload it runs and that class
deliberately carries only what the arms must agree on. Its javadoc says which two arms produced the
clone, so the next reader knows why it is shared rather than inlined.

Verified rather than assumed: the whole streams integration suite green afterwards, both sides of the
clone included.

WHAT WAS LOOKED AT AND DELIBERATELY LEFT

The other clone both engines agree on is the fixture FORMAT - the writer in
`StockBaselineFixtureSupport` against the reader in `BaselineFixture`. Those two live in modules that
cannot share code, and that is the point rather than an oversight: the example module must not depend
on the streams module, or the baseline it generates would run the patched classes and stop being a
baseline. Extracting it would need a third module whose only purpose is to reconnect the two things
this design keeps apart.

The remaining jscpd hits are import blocks and javadoc between sibling benchmark classes - jscpd is
lexical and counts comments, which is why PMD CPD, which is token-based, reports neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… and this is what it caught

`StreamThreadTest.shouldChangeStateAtStartClose[1]` failed in the SEAM-OFF arm on a GitHub-hosted
runner - "Condition not met within timeout 10000. Thread never shut down." No prior art anywhere in
this repository: not in the ledger, not in the solutions, not on the tracker, open or closed.

THE LANE DID THE RIGHT THING AND IT HAD NEVER DONE IT BEFORE

The control-arm integrity check had only ever been exercised on synthetic reports. On this run it
named the ledgered invalid-timestamps flake beside this failure, refused to produce a verdict on the
divergence set while an UNdiagnosed control-arm failure stood, and said so in a verdict that outranks
the classification - which was itself clean, no unexplained divergences at all. That is the fourth
way this lane has now been shown to fail, and the only one that arrived by itself.

THE DIAGNOSIS, AND IT IS ONE SIGHTING

The same case PASSED in the seam-ON arm of the same run. That is close to a control arm handed over
for free: two arms, same patched classes, same runner, minutes apart, differing only in the dispatch
switch. A defect the patch introduced cannot be REPAIRED by turning the seam on - seam-on is strictly
more machinery on top of the same patched code. What varies with load, and only with load, is whether
a thread finishes shutting down inside a fixed ten seconds.

Recorded with a flaky-case marker, and the note is explicit that this rests on one sighting plus that
asymmetry, that a failure in BOTH arms of one run would void it, and that quarantine is not the lever
here - it is one sighting on one PR, and the case lives inside Apache Kafka's own compiled test class
where the annotation has nowhere to go.

WHY THE MARKER IS NOT A SILENCER, SINCE IT DOES GREEN THIS LANE

The lane still prints the failure, still names the note beside it, and still refuses to attribute it
to the seam. What the marker buys is that one timing bound in Kafka's own test stops withholding the
verdict on the divergence set - otherwise the lane is permanently red for a reason that is not a
divergence, and a permanently red lane is one nobody reads, which is the failure this whole design
was built against.

ONE MORE THING THE RUN TAUGHT THE TRIAGE NOTE

The lane reported a divergence attribution as having matched nothing, which normally means a rung
fixed it. There is a benign second cause, now written down: a case failing in the CONTROL arm falls
out of the divergence set entirely, because a divergence is green-off-and-red-on. The
invalid-timestamps case does exactly that whenever its flake fires seam-off. So check which list a
case is in before deleting an entry the lane called stale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…so it is intermittent

The same lane, the same code, a later push on the same PR: the whole streams integration suite passed
including the class that stalled. Recorded because a green re-run is a data point about the rate and
nothing at all about the cause, and this note exists so the next green one is not mistaken for a
diagnosis.

What it does change is the priority of the experiment the note already names. A standing break would
be reproducible on demand; an intermittent one is exactly the case where re-running until green is
tempting and useless, so the core-count arm - the same runner at forkCount=1, holding the machine
fixed and varying only the load - is now the only thing that will settle it.

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

Merges `feats/ks-streams-error-surfacing` (#395) into
`feats/ks-streams-reconciled`, the branch on which the sibling rungs of #255 are brought
together and the dispatch default is decided against one measurement of the whole.

Everything semantic auto-merged: the typed control-flow passthrough and the settle wait, the commit
fence on a pending failure, the derived-occupancy memory bound, `PcDispatchSwitch`'s new
`BACKPRESSURE_PROPERTY`, the regenerated patch, the README's switch table, the pom's module
description, two `docs/solutions/` write-ups and the reserved-flip note. Verified rather than
assumed - `PcDispatchSwitch.java`, `README.md` and `pc-streams.patch` are byte-identical to that
branch's versions after the merge.

One conflict, on `docs/inflight/ci-streams-classifier-artifacts-escape-the-cache-warming-job.md`,
and neither side was simply taken. This branch carries the post-fix version, because
#379's warm step is now in the workflow here; the sibling carries the
pre-fix version enriched with what its own CI runs measured. The resolution keeps the post-fix
framing and adds the three durable findings the sibling established, which outlive the fix and
describe the shape of this defect class wherever it appears again: it takes down every lane that
builds the module rather than one, it is per-run rather than per-branch, and the attribution was
settled by a markdown-only commit reproducing it rather than by argument. The workflow's own warm
step is still present and is still the durable owner of the mechanism.

Ordinary merge. No history rewritten, and the sibling branch is untouched.
…ion branch

Merges `feats/ks-streams-stream-time-punctuation` (#396) into
`feats/ks-streams-reconciled`, beside the error-surfacing rung it was cut in parallel with. This is
the merge the two siblings were written to make possible, and it is where their shared files meet.

## The patch was RECONCILED AT THE SOURCE, never hand-edited

`bin/regen-patch.sh`'s own header prescribes this and it was followed exactly. The patch showed 36
conflict blocks, almost all of them `@@` offset arithmetic; the Java underneath had **one**.

- `process-sources` produced a pristine tree, and each side's patch was applied to its own fresh copy
  of it - #394's as the base, #395's as ours, #396's as theirs.
- `diff -rq` against the base named the files each side actually edited: `StreamTask.java` on both,
  `KStream.java` on the stream-time side alone. The second was taken wholesale; the first was merged
  with `git merge-file --diff3`, which reported a single conflict - two adjacent field declarations,
  `pcPausedPartitions` and `pcPunctuatorWarnings`, resolved as the union they are.
- The merged tree was copied over `target/kafka-patched` and `bin/regen-patch.sh` run with no maven
  invocation in between, which is the foot-gun that silently reverts the tree.
- **Verified by content, not by hunk count.** No line either side's patch added is missing from the
  regenerated one except where the other side deliberately rewrote it, and every removal on both
  sides survives. 117 hunks and 121 hunks in, 121 out.

The deliberate rewrites, checked one at a time rather than accepted as noise: the stream-time rung
replaced `pcPrepare`'s `Runnable` return with `PreparedRecord` so the extracted timestamp travels
with the work, and rewrote the `@DoNotCall` join strings because the reason is no longer "the clock
is stuck"; the error-surfacing rung replaced the `StreamsException` wrap with the typed passthrough
and `pollFailure()`'s `Failure` type. The merged `StreamTask` carries both - `pcClassifyFailure`
and `PcTaskDispatcher.Failure` from one, `PreparedRecord` preparation from the other.

## The Java, and one duplication removed rather than left

`PcTaskDispatcher` merged as six unions, each verified: the imports, the pump's two new locals, the
per-record occupancy count now sitting beside a `PreparedRecord` rather than a `Runnable`, the
completion drain releasing the stream-time hold and reading the partition before handing the
container over, the two blocks of new query methods, and the close path doing both its clears. No
line either parent added is absent from the result.

`PcTaskDispatcherTest` needed real work rather than a merge: the error-surfacing rung's new cases
were written against the `Runnable`-returning preparer, so thirteen of them are re-expressed through
`PreparedRecords.prepared`, the shared helper the stream-time rung added for exactly this. The merge
also produced two no-op preparers differing only in the case of one letter; the newer one survives
and the four call sites of the other are re-pointed, because a merge that leaves two spellings of
one helper is how the next reader adds a third.

## The trigger chain, reconciled into one statement

Three rungs wrote into `PcDispatchSwitch`'s javadoc, each describing the chain as it stood when it
was written, and merging them naively would have left a class documenting two contradictory
presents. It now states the history once: refusal closed by #389, revival by
#394, exception surfacing by #395 - each with a
seam-on measurement and a control arm - and punctuation closed beside them by
#396, which was never one of the triggers and is recorded as such. What
remains is named and explicitly not a fourth trigger: Kafka's private processing-threads parameter
combination, out of scope since the seam landed.

**The default is NOT moved by this commit.** Every rung declined to move it on its own evidence and
every one was right to; the flip is a decision for the reconciled tree measured as a whole, which is
what this branch exists to do.

The streams module compiles, main and test, on JDK 17. Ordinary merge, no history rewritten, and the
sibling branch is untouched.
Merges `test/ks-streams-evidence-suite` (#398) into
`feats/ks-streams-reconciled` - the seam-on divergence lane and its classifier, the broker-backed
laws with their controls, and the realistic-domain benchmark with its noise floor.

This is the rung whose whole purpose is to be run against the reconciled tree. It deliberately
touched no main Java and no patch, precisely so that the two sibling rungs could change dispatch
semantics underneath it, and that held: the merge conflicted on one file only.

`BrokerStreamsIntegrationTest`, the shared broker base class, gained members from both sides at the
same two points - the error-surfacing rung's `sleep` cost simulator, and the evidence rung's
`consumeByKey` and `flatten` readers, the pair the duplicate-code check told it to hoist. Every
conflict is a pure addition on both sides and every one is resolved as the union; the import block
was re-sorted and its one genuinely duplicated entry collapsed rather than left doubled.

`README.md`, `pom.xml` and the invalid-timestamps flake ledger auto-merged.

The streams module and the example module compile, main and test, on JDK 17. Ordinary merge, no
history rewritten, and the sibling branch is untouched.

The lane's own ledger entries are NOT edited here - that is the next commit's job, because deleting
an attribution and re-running the lane is the acceptance test for whether this merge preserved the
fixes it claims to.
…n branch

Merges `feats/ks-streams-example` (#391) into
`feats/ks-streams-reconciled`. That branch sits on the seam rung rather than the lifecycle one, so
this merge is where the demo meets refusal, lifecycle, error surfacing, stream time and the evidence
suite for the first time.

Everything auto-merged except the workstream signpost, `docs/inflight/branch-ks-streams-workstream.md`,
where two rungs each struck their own entry from the same "still off master" list - the evidence
suite removed itself, and the example module recorded its arrival. Resolved as the union: the list
now names only what genuinely remains, and both rungs' paragraphs stand.

The whole reactor compiles, main and test, on JDK 17. Ordinary merge, no history rewritten, and the
sibling branch is untouched.

**The example's framing is deliberately NOT changed here.** Its README and its run both say the demo
sets the dispatch flag explicitly because the default is off, and that is still true at this commit -
the flip is a measurement this branch has not yet taken. If the measurement moves the default, the
framing moves with it, in the commit that moves it.
…lt stays OFF

The decision every rung of #255 deferred, taken here, on the branch it was deferred to. The
answer is NO FLIP, and the measurement is the point rather than the answer.

## What was run

`bin/ci-streams-seam-on-evidence.sh` on the reconciled tree: Apache Kafka's own suite twice against
the patched classes, seam off as the control arm and seam on as the measurement, report directories
deleted first so a previous run's XML could not read as this one's.

The control arm was **clean** - no failures at all, so no flake had to be named and the differencing
had something trustworthy to difference against. Counts are deliberately not written here; they move
with the branch, the Kafka version and the seam, and the lane re-derives them.

## The acceptance test, and it ran red first, which is what makes it a test

The two attributions the sibling rungs owned - `stream-time-never-advances` and
`exception-type-lost-in-the-worker` - were **deleted before the run**, so a fix the merge had lost
would match no mechanism and be reported as unexplained rather than keep its old label by inertia.
The first run exited 1 with three unexplained divergences. That is the lane doing its job.

## The control arm that told a lost fix from a mechanism that only partly closed

A failure seen on one branch says nothing about that branch, so the same seam-on suite was run at
#396's own tip, in a **detached** worktree - the sibling branch itself is
not touched. That tip carries the stream-time work and not the error-surfacing work, so the single
term that differs from the reconciled tree is the reconciliation.

**Nothing was lost.** `shouldReinitializeRevivedTasksInAnyState` fails on all three parameters
there and passes on two here; `shouldRecordBufferedRecords` fails there and passes here; the two
`StreamThreadTest` punctuation cases pass in both. Every case is the same or better than at the
sibling's own tip, so both rungs' fixes survived the merge.

**And the residue changed mechanism rather than surviving unchanged.**
`shouldPunctuateOnceStreamTimeAfterGap` moves from zero punctuations to six of stock's seven, and
`shouldRespectPunctuateCancellationStreamTime` fails a different assertion than it did. Reading
those as "still `stream-time-never-advances`" would have hidden the whole effect of the stream-time
rung. They are re-attributed to a new class, `stream-time-lags-in-flight-work`, and
`shouldReinitializeRevivedTasksInAnyState` to `processing-threads-mode`, which is what its remaining
parameter has always been. The lane then exits 0 with every divergence explained and no stale entry.

## Why that is a fourth named trigger and not a shrug

The lag IS the guarantee: the low-water mark never passes a record still in flight, which is what
stops a punctuation closing a window over work still inside the processor chain. There is no code
fix here and the design would be worse without it.

What is wrong is the record. The README, `CONCEPTS.md` and the punctuation note all pin the mark
**overtaking** stock as the known divergence and say nothing about it lagging - and lagging is what
Kafka's own suite has now measured. Flipping the default while half of a two-directional divergence
is undocumented would ship a config whose docs are half true. So the default stays off, and what
closes the trigger is the lagging direction being recorded to the same standard as the overtaking
one: a bound on how far behind the mark can be, or an honest statement that none is known.

This is the fourth time the measurement has named the next reason, and the first time it did so from
the reconciled whole rather than from one rung.

## Where it is written down

The three sites that move together, re-pointed rather than left stale: `PcDispatchSwitch`'s javadoc,
which owns the decision and now carries the whole chain in one statement; the module README's
"Turning it on, and why it is off"; and the pom's module description.
`docs/inflight/core-streams-punctuation-diverges-in-three-measured-ways.md` gains the measurement -
it already listed this lag as something the divergence work owed, so the finding lands in the note
that predicted it rather than in a second one. The seam-on ledger gains the two new classes, the
control-arm table, and the deletion of `shouldRecordBufferedRecords` on that evidence rather than on
one stale-report line.

`docs/inflight/streams-dispatch-default-flip-is-reserved-until-the-rungs-reconcile.md` is **deleted**:
its own "delete when" was the default being re-decided against a fresh seam-on measurement on the
reconciled branch, whether or not it moved. It has been, so the reservation is discharged and the
position it was holding open now lives in the chain.
…akes it met

No code changes. Records what running the reconciled module actually produced, so the next reader
inherits the sightings rather than re-diagnosing them.

**What is green.** The module's own unit suite; the seam-off upstream oracle; all 35 broker-backed
integration arms run alone against a real broker - the rebalance arm, both memory-bound arms, the
five punctuation arms, the two proof arms, the commit-frontier crash-restart law, the shadowed stock
control and the wake-on-work shutdown arm. `bin/check-all.sh` exits 0, every runnable gate passing.

**Two flakes met and neither chased.**

- `StreamThreadTest.shouldLogAndRecordSkippedRecordsForInvalidTimestamps[3]` - the ledgered
  thread-name flake, failing once in three runs of the upstream execution on this tree while the
  other two, taken as the evidence lane's control arm, were entirely clean. A pass/fail pair on an
  unchanged tree in one session, which is the strongest shape that ledger collects.
- Seven `TransactionAndCommitModeTest` timeouts and one
  `PartitionStateCommittedOffsetIT.committedOffsetRemoved[3]` `RebalanceInProgressException`, in
  `parallel-consumer-core`, from a `-am` build that stood core's broker containers up alongside the
  streams module's. Attributed to the load-tightness family rather than to this branch, on a check
  rather than an assertion: this branch changes zero files under `parallel-consumer-core`, and
  re-running the streams module alone with core installed was green in every arm. The
  `committedOffsetRemoved[3]` sighting is the same case and the same exception as a row already in
  that ledger.

Nothing was loosened, shortened, serialised or re-run into green.
#271 carries eleven unresolved review threads. Nine of them either close
against a named rung of this stack or are answered as a documented contract. **Two do not**, and a
thread is the worst place for a live defect to sit: it disappears with the PR.

Both are the same shape. The patched `commitNeeded()` short-circuits to `pcAwareCommitNeeded()`
whenever a dispatcher is present, and that helper asks one question - does PC hold uncommitted work.
Stock asks two more, and both of them are real: an explicit `context().commit()` sets
`commitRequested`, which nothing on this path reads, and a poll batch of nothing but control markers
advances the consumer position through a sweep this path cannot run because `consumedOffsets` is
gone by design.

**Read against the reconciled tree rather than against the state the threads were written on**, which
matters because four rungs have changed this area since - the refusal envelope, the task lifecycle,
error surfacing and stream time. `grep -n 'commitRequested' parallel-consumer-streams/src/main/patch/pc-streams.patch`
is the check that the first one still stands: it finds the declaration and the javadoc calling it
"the one sanctioned cross-thread commit-state write", and no read.

The note also says what settling them is NOT: a one-line `|| commitRequested` is the same shape as
the `hasUncommittedWork() || commitNeeded` candidate the stream-time rung measured and rejected,
because `validateClean()` reads the same answer and would start throwing on a clean close after a
punctuate-only interval.
The self-reference gate flags any mention of the current branch. This one names the branch outright
rather than saying "this branch", and the claim beside it is a command a reader can re-run against
whatever that ref resolves to, so the sentence still reads correctly after the merge. Attested rather
than reworded.
… the gate reads

The `post-merge: checked` marker is line- or block-scoped, so a comment two lines above the sentence
it vouches for silences nothing. Wrapped in `checked-begin`/`checked-end` instead, which is the form
the rest of this directory uses. `bin/check-branch-self-reference.sh` now exits 0 and
`bin/check-all.sh` passes every runnable gate.
…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.
`check-branch-self-reference` flagged the two places this note cites #271.
It is right to ask and the answer is that the citation is the point: the note exists precisely
because a review thread stops being readable when its PR closes, while a landed PR is a permanent
link. The sentences name the PR outright rather than saying "this PR", so they read the same
afterwards. Attested with `checked-begin`/`checked-end` rather than reworded.
… only beside its siblings

The `Integration Tests` lane on #271 went red on
`PostCommitCheckpointGapTest.pcPathAlsoRefreshesTheCheckpointUnderLoad`. Diagnosed rather than
re-run, and the first reading was wrong.

**The value is `-4`, which is a sentinel and not a small number** - the checkpoint file exists and its
changelog offset was never populated. That is a different failure from a bound missed under load, and
it does not move with more time, so "the CI runner is slow" was never available as an explanation.

**A control arm ruled out the reconciliation, which was the hypothesis worth killing first**: a
commit fence and a checkpoint expectation meeting for the first time is precisely what a
reconciliation surfaces. The same class, run alone at #396's own tip in a
detached worktree, fails 3 of 3 with the identical sentinel. The merge is not the cause.

What is left is an ordering dependency: the class passes in a single-fork local suite run, where the
siblings that ran before it warmed the JVM, and fails alone - 3 of 3 here, 3 of 3 on its own branch -
and on CI at `forkCount=4`, where a forked class does not reliably get that neighbour.

**That matters more than the red build.** The class's own assertion message says it REFUTES a sibling
class's inference that `postCommit` never runs on the PC path. A refutation that holds only when
another class ran first in the same JVM is weaker than the one written down, and the green suite is
what hid it - the shape this module's handover names as "several tests here passed while proving
nothing".

Nothing was widened, shortened or re-run into green. The note names what to look for and says
explicitly not to widen the bound, because the bound is not what is failing.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 5 package(s) with unknown licenses.
See the Details below.

License Issues

.github/workflows/streams-seam-on-evidence.yml

PackageVersionLicenseIssue Type
actions/cache/restore4.*.*NullUnknown License
actions/checkout6.*.*NullUnknown License
actions/setup-java5.*.*NullUnknown License
actions/upload-artifact7.*.*NullUnknown License

parallel-consumer-examples/parallel-consumer-example-streams-pc/pom.xml

PackageVersionLicenseIssue Type
bz.stub.parallelconsumer:parallel-consumer-streams0.6.0.0-SNAPSHOTNullUnknown License
Excluded from license check: pkg:githubactions/actions/github-script

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
actions/actions/cache/restore 4.*.* 🟢 6.2
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Maintained🟢 810 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 8
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
SAST🟢 10SAST tool is run on all commits
Branch-Protection⚠️ 1branch protection is not maximal on development and all release branches
actions/actions/checkout 6.*.* 🟢 7
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Maintained🟢 1024 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Security-Policy🟢 9security policy file detected
SAST🟢 10SAST tool is run on all commits
Branch-Protection🟢 6branch protection is not maximal on development and all release branches
actions/actions/setup-java 5.*.* 🟢 5.5
Details
CheckScoreReason
Maintained🟢 1030 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Code-Review🟢 4Found 11/25 approved changesets -- score normalized to 4
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 10SAST tool is run on all commits
actions/actions/upload-artifact 7.*.* 🟢 5.2
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Maintained⚠️ 00 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
SAST🟢 10SAST tool is run on all commits
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
maven/bz.stub.parallelconsumer:parallel-consumer-core 0.6.0.0-SNAPSHOT UnknownUnknown
maven/bz.stub.parallelconsumer:parallel-consumer-streams 0.6.0.0-SNAPSHOT UnknownUnknown
maven/org.apache.commons:commons-compress 1.28.0 UnknownUnknown
maven/org.codehaus.mojo:exec-maven-plugin 3.5.0 UnknownUnknown
maven/org.testcontainers:kafka UnknownUnknown
maven/org.testcontainers:testcontainers UnknownUnknown
maven/org.apache.maven.plugins:maven-failsafe-plugin UnknownUnknown

Scanned Files

  • .github/workflows/streams-seam-on-evidence.yml
  • parallel-consumer-examples/parallel-consumer-example-streams-pc/pom.xml
  • parallel-consumer-streams/pom.xml

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

❌ Duplicate Code Report

Two 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

PR Base Change
Clones 36 30 :face_with_monocle: +6
Duplicated lines 1157 963 :face_with_raised_eyebrow: +194
Duplication 0.14% 0.12% 🫤 +0.02%
Rule Limit Status
Max duplication 0.5% ✅ Pass (0.14%)
Max increase vs base +0.1% ✅ Pass (+0.02%)
⚠️ 6 new clones introduced
  • 26 lines (109 tokens): parallel-consumer-examples/parallel-consumer-example-streams/src/test/java/bz/stub/parallelconsumer/examples/streams/integrationTests/StockBaselineFixtureSupport.java:335 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/BaselineFixture.java:141
  • 16 lines (96 tokens): parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PostCommitCheckpointGapTest.java:247 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorCommitCoverageTest.java:407
  • 13 lines (83 tokens): parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PostCommitCheckpointGapTest.java:262 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorEffectSurvivalTest.java:340
  • 11 lines (76 tokens): parallel-consumer-examples/parallel-consumer-example-streams-pc/src/main/java/bz/stub/parallelconsumer/examples/streams/pc/ArmRunner.java:220 <-> parallel-consumer-examples/parallel-consumer-example-streams/src/test/java/bz/stub/parallelconsumer/examples/streams/integrationTests/StockBaselineFixtureSupport.java:177
  • 13 lines (75 tokens): parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/PcTaskDispatcherTest.java:1258 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/PcTaskDispatcherTest.java:1349
  • 12 lines (74 tokens): parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PostCommitCheckpointGapTest.java:247 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorEffectSurvivalTest.java:317 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorWarningTest.java:219

❌ jscpd (language-agnostic)

PR Base Change
Clones 115 91 :face_with_raised_eyebrow: +24
Duplicated lines 1656 1294 :face_with_raised_eyebrow: +362
Duplication 1.15% 0.99% :face_with_monocle: +0.16%
Rule Limit Status
Max duplication 2% ✅ Pass (1.15%)
Max increase vs base +0.1% ❌ FAIL (+0.16%)
⚠️ 24 new clones introduced
  • 16 lines: parallel-consumer-examples/parallel-consumer-example-streams/src/test/java/bz/stub/parallelconsumer/examples/streams/integrationTests/StockBaselineFixtureTest.java:69 <-> parallel-consumer-examples/parallel-consumer-example-streams/src/test/java/bz/stub/parallelconsumer/examples/streams/integrationTests/StockStatefulBaselineFixtureTest.java:95
  • 10 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/RebalanceUnderPcDispatchTest.java:14 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/StreamsBenchmarkHarness.java:17
  • 8 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorWarningTest.java:12 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/WakeOnWorkShutdownTest.java:8
  • 11 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorWarningTest.java:18 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/StreamTimePunctuatorRefireOnRestartTest.java:20
  • 7 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorEffectSurvivalTest.java:14 <-> parallel-consumer-examples/parallel-consumer-example-streams-pc/src/main/java/bz/stub/parallelconsumer/examples/streams/pc/ArmRunner.java:8
  • 12 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorEffectSurvivalTest.java:15 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/StreamTimePunctuatorRefireOnRestartTest.java:15
  • 12 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorEffectSurvivalTest.java:327 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorWarningTest.java:221
  • 15 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorCommitCoverageTest.java:15 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorWarningTest.java:15
  • 12 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorCommitCoverageTest.java:406 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorWarningTest.java:218
  • 12 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PostCommitCheckpointGapTest.java:1 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/WakeOnWorkShutdownTest.java:1
  • 8 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PostCommitCheckpointGapTest.java:9 <-> parallel-consumer-examples/parallel-consumer-example-streams-pc/src/main/java/bz/stub/parallelconsumer/examples/streams/pc/ArmRunner.java:7
  • 12 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PostCommitCheckpointGapTest.java:15 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PunctuatorEffectSurvivalTest.java:21
  • 11 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenStreamsDispatchTest.java:1 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/StreamTimePunctuatorRefireOnRestartTest.java:1
  • 11 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenStreamsDispatchTest.java:7 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/ShadowedStreamsControlTest.java:6
  • 14 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenStreamsDispatchTest.java:17 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/ShadowedStreamsControlTest.java:15
  • 9 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenStatefulProofTest.java:10 <-> parallel-consumer-examples/parallel-consumer-example-streams/src/test/java/bz/stub/parallelconsumer/examples/streams/integrationTests/StockStatefulBaselineFixtureTest.java:11
  • 11 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenStatefulProofTest.java:22 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenStreamsProofTest.java:13
  • 8 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PcDrivenProofSupport.java:9 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/ShadowedStreamsControlTest.java:7
  • 70 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PaymentAuthorisationBenchmarkTest.java:10 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/WorkloadMatrixBenchmarkTest.java:9
  • 56 lines: parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/PartitionScalingBenchmarkTest.java:1 <-> parallel-consumer-streams/src/test/java/bz/stub/parallelconsumer/streams/integrationTests/WorkloadMatrixBenchmarkTest.java:1

...and 4 more

Powered by astubbs/duplicate-code-cross-check

// A fresh application id per arm. Reusing one would resume from the previous arm's committed
// offsets, and the second arm would sit there processing nothing.
props.put(StreamsConfig.APPLICATION_ID_CONFIG, armName + "-" + sequence + "-" + System.nanoTime());
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, broker.bootstrapServers());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Duplicate code detected - 11 lines duplicated with parallel-consumer-examples/parallel-consumer-example-streams/src/test/java/bz/stub/parallelconsumer/examples/streams/integrationTests/StockBaselineFixtureSupport.java:177

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

⚠️ SpotBugs Report

464 bug(s) found (rule-level exclusions only - see docs/inflight/static-spotbugs-rule-registry.md). See the annotations on the Files Changed tab for details.

@codecov

codecov Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 19.93912% with 526 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.51%. Comparing base (9b28f2c) to head (d9d56ac).

Files with missing lines Patch % Lines
...lconsumer/examples/streams/pc/LatencyScenario.java 7.89% 104 Missing and 1 partial ⚠️
...lconsumer/examples/streams/pc/StreamsOnPcDemo.java 0.00% 95 Missing ⚠️
...arallelconsumer/examples/streams/pc/ArmRunner.java 0.00% 85 Missing ⚠️
...elconsumer/examples/streams/pc/ClasspathGuard.java 2.73% 71 Missing ⚠️
...rallelconsumer/examples/streams/pc/DemoBroker.java 0.00% 70 Missing ⚠️
.../parallelconsumer/examples/streams/pc/Console.java 0.00% 35 Missing ⚠️
...lconsumer/examples/streams/pc/CompletionTimer.java 0.00% 23 Missing ⚠️
...arallelconsumer/examples/streams/pc/ArmResult.java 0.00% 21 Missing ⚠️
...arallelconsumer/examples/streams/pc/Latencies.java 0.00% 10 Missing ⚠️
...tub/parallelconsumer/streams/PcTaskDispatcher.java 93.54% 5 Missing and 3 partials ⚠️
... and 2 more
Additional details and impacted files
@@                          Coverage Diff                          @@
##             feats/ks-streams-task-lifecycle     #454      +/-   ##
=====================================================================
- Coverage                              82.49%   73.51%   -8.98%     
- Complexity                              1451     1479      +28     
=====================================================================
  Files                                     95      105      +10     
  Lines                                   5066     5717     +651     
  Branches                                 497      564      +67     
=====================================================================
+ Hits                                    4179     4203      +24     
- Misses                                   692     1318     +626     
- Partials                                 195      196       +1     
Flag Coverage Δ
chaos 53.54% <ø> (-1.04%) ⬇️
integration 64.94% <82.94%> (+0.02%) ⬆️
performance 51.72% <59.68%> (+5.52%) ⬆️
unit 71.01% <18.87%> (-6.74%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant