Repository navigation
perf(ci): trim the integration lane, close forkCount as a lever, and unblock the duplicate-code check - #439
Conversation
What changed for a user: nothing yet - an investigation, two corrected claims, and a flake
sighting. The measured optimisation follows on this branch.
The Integration Tests lane is the PR build's critical path. This records what it is actually
made of, and the result of the first experiment against it.
THE FINDING. Raising failsafe forkCount from 4 to 6 makes the lane SLOWER - 469s of failsafe
against a 420s baseline - and it destabilised a test that had never failed before. A 2x2
factorial over forkCount {4,6} x the 857 probe {whole, split}, each arm's prediction written
into its commit message before it ran:
baseline 4 forks, whole 420s failsafe 1528s work 339s tail pass
H1 6 forks, whole 469s 1691s 332s FAIL
H2 4 forks, split 424s 1533s 161s pass
H3 6 forks, split 379s 1710s 160s FAIL
The model predicted H1 340-380 and H3 260-300. It was wrong because it assumed class_time_total
is invariant to forkCount. It is not: the same tests cost 11% more CPU time at six forks. More
forks manufacture work, and contention degrades packing on top of that.
So both parallelism directions for this lane are now measured and closed - thread-parallelism in
2026-07, fork oversubscription here. The remaining lever is work reduction, not scheduling.
A TRAP WORTH THE COMMIT MESSAGE. H1 and H3 failed at verify, so Maven never built the other ten
modules and their job_seconds omit ~120s the passing runs paid. H1's job_seconds reads 548
against the 620s baseline while its failsafe was 49s worse. Ranked on job_seconds, the most
harmful arm of the four wins. Rank this lane on core_failsafe_seconds, and refuse to rank a
sample whose build did not succeed.
TWO STALE CLAIMS, corrected here because both are load-bearing and silent:
- parallel-consumer-core/pom.xml withheld a per-fork log directory from failsafe because "it
declares no forkCount, so it runs at the default of 1". maven.yml passes -DforkCount=4 and
failsafe pins no <forkCount>, so it takes that bare property - the -DforkCount vs
-Dsurefire.forkCount trap the same comment warns about. Four JVMs already share one flat
pc.log.dir, into appenders the comment says lose writes under exactly that condition. The flat
path stays, on the half of the reason that survives; the false premise does not.
- bin/ci-integration-test.sh said "the ci profile runs sequentially on GitHub-hosted 2-core
runners", which describes the unparameterised script rather than what CI invokes.
Also records a seventh sighting of the RegistrationRaceStaleResidentIT setup-guard flake, seen on
one of the measurement arms. It is deliberately NOT attributed to fork pressure: codecov shows the
identical assertion failing earlier the same day on an unrelated branch at forkCount=4. The
genuine fork-pressure casualty was ManagedPCInstanceLifecycleTest on the sibling arm, which had
passed every recorded run before this. Conflating the two would waste the next reader's time.
An unverified premise is flagged rather than asserted: the runner reports ubuntu-24.04, and
GitHub moved public-repo runners to 4 vCPU in early 2024, while docs/self-hosted-runner.md,
docs/ci.md and the prior unit-gate run all reason from "2-core" - that run built a 2-CPU Docker
replica to imitate this runner. Nothing here read nproc, so it is named as cheap to settle, not
concluded.
…nd what is still open What changed for a user: nothing - a working note. Carries the part of the ce-optimize run that is still moving, so the next session inherits it rather than re-deriving it: which levers are now closed by measurement (both parallelism directions), which changes are built and unmerged and on which branches, and the two traps that cost real time here - rank this lane on core_failsafe_seconds rather than job_seconds, because a build failing at verify skips ten modules and its job_seconds omits ~120s; and the scratch space lives under .context/, which is gitignored and does not travel with the branch. Separate commit rather than an amend: the first commit is unpushed and amending it would have been safe, but the repo's history-rewrite gate asks before rewriting and there was no reason to answer it when an ordinary commit does the same job.
Dependency ReviewThe following issues were found:
License Issues.github/workflows/maven.yml
OpenSSF Scorecard
Scanned Files
|
✅ Duplicate Code ReportTwo engines run in parallel for cross-validation. Each has its own thresholds tuned to its baseline - the real safety net is the per-engine "max increase vs base" check. ✅ PMD CPD
No new clones introduced by this PR. ✅ jscpd (language-agnostic)
No new clones introduced by this PR. Powered by astubbs/duplicate-code-cross-check |
🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its No quarantined test changed outcome since the previous push. Updated for |
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #439 +/- ##
============================================
+ Coverage 81.90% 82.33% +0.42%
- Complexity 1466 1475 +9
============================================
Files 95 95
Lines 5133 5133
Branches 500 500
============================================
+ Hits 4204 4226 +22
+ Misses 730 714 -16
+ Partials 199 193 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
🟢 Throughput — OKThis branch measured about 8% slower than master, on the one test this measures. That is INSIDE this test's own run-to-run spread of about 17%, so read it as a reading and not as a result - re-running the same commit moves it by about as much.
Allowable range 🟢 ≥ 0.70 · 🟡 0.50–0.70 (about a 30% loss) · 🔴 < 0.50 (about a 50% loss) What the numbers mean, and what they cannot tell youThe one that gets misread. Why a shape and not a rate. A rate depends on which runner you drew. A shape does not: every test here processes a fixed number of records, so a runner twice as slow doubles the subject and the controls together and leaves their ratio alone. That is the whole trick, and it is why the reported rate is shown last and labelled as this machine only. Reading the comparison. By conservation, not by correction. Every test in this lane processes a fixed number of records, so within one run the ratio of one test's time to another's is invariant under machine speed — a runner twice as slow doubles both terms and leaves the ratio alone. There is no machine-index correction to be wrong, because nothing needed correcting. Per-method times, not class times. A class time is Reference is the median of 10 recent What this still cannot do. It removes machine-to-machine variance. It does not remove this test's own run-to-run variance, measured at about 30% on a single unchanged commit while its controls stayed within 5%. That is a property of the test, not of the comparison, and no arithmetic here can touch it — which is why the reference is a median and the bounds are deliberately coarse. 🟡 means look at this; only 🔴 is outside the measured spread. Runs used: c30aaee, ca9c21b, 77fbba8, 7a8a7f0, 2e2705a, 28c6be6, c813942, 558fcfb, 5ae7b34, 10ed71c Since the previous push: ratio 0.796 -> 0.917, share 1.878 -> 1.631, rate 74090 -> 65997 (-10.9%). One push of difference sits inside this test's measured spread - read it as movement, not as a result. Updated for |
…der matters What changed for a user: nothing - a working note. Sharding the Integration Tests lane across runner jobs, the way the Chaos Pain Suite already is, is the lever that would actually halve the PR build's critical path. It is deferred rather than undecided, and the note records the measurement that sets the order. Within-job overlap is exhausted: 1528s of test time runs in 420s on four forks, about 91% parallel efficiency, and six forks fell to 75% while inflating total work 11%. So more overlap means more JOBS. What stops that being the first move is that each shard re-pays the ~136s of serial build - testCompile 60s, compile 42s, javadoc 14s, delombok 8s, Truth codegen 7s - so a 4-way split spends roughly 400 extra runner-seconds before saving anything. And the conversion rates differ: because four forks already overlap the waiting, cutting 3.6s of test time buys 1s of wall, while cutting 1s of serial build buys a full second. The build reduction is worth ~3.6x per second AND it is exactly the cost sharding multiplies. Doing it first is not a detour from sharding - it is what makes sharding pay.
What changed for a user: nothing - two sentences reworded in a working note. The note said "This branch carries a ce-optimize run" and "What has LANDED on this branch", both of which name nothing once the branch is gone. Rewritten to name #439 in the past tense, and marked post-merge: checked so the gate can tell an attestation from an oversight. Caught by check-branch-self-reference.sh in the repo: hygiene lane, which failed on both pushes of this branch while every other branch that day was green - so the attribution needed no guessing. WHY IT REACHED CI, since the gate is not obscure and AGENTS.md already names the sweep that runs it: bin/check-all.sh was run and passed - and THEN two more files were added, with only check-inflight-tags.sh and check-file-refs.sh run against them. A sweep whose whole value is that the set cannot drift from what you remembered was made to drift by remembering which gates the new files "needed". The rule is the sweep runs last, after the final edit, not once per session.
What changed for a user: the Integration Tests lane no longer builds a javadoc jar, a sources jar or delombok output for eleven modules on every run. Nothing in a test lane consumes any of them. TAKEN ON FIRST PRINCIPLES, NOT ON A MEASUREMENT, and that distinction is the finding. Verified by counting artefacts in the CI log: a run with these flags builds 0 javadoc jars against 9 without, and delombok reports skipping 11 times against 2. It provably does less work, so it cannot be slower. The saving is around 10s of a ~600s job and this lane's wall time CANNOT resolve it - three concurrent samples of identical code spread 119 seconds, roughly eleven times the effect. The first cut of this change carried a bug that no timing could have caught: -Dsource.skip=true is not a property maven-source-plugin reads - it wants maven.source.skip - so sources jars were still built in both arms while the numbers looked the same either way. Found by counting jars. The script's header now says to verify these three properties by counting artefacts rather than by timing the job. WHAT ELSE THIS SETTLES, all recorded in docs/plans/2026-09-03-001-investigate-integration-gate-wall-time.md: - The noise floor of this lane is 119s across three CONCURRENT samples of identical code. The postscript retracts an earlier claim in the same document that prebuild_seconds cleanly resolved a 10s effect; the control has since moved further than the effect. Incremental optimisation of this job is not measurable, which is an argument for sharding rather than more tuning. - Replacing the 20s compaction sleep with a poll on the log start offset is a proven NO-OP: it logged "Compaction did NOT advance" four times out of four, never fired, and paid the full deadline anyway. Dropped rather than merged - log compaction need not advance the log start offset, so the detector was wrong. The blind spot it exposed is real and now tracked in docs/inflight/test-compaction-wait-has-no-observable.md: that sleep asserts nothing, so a run where compaction never happened is indistinguishable from one where it did. - compiler:testCompile, 60s and the largest remaining serial cost, cannot be skipped: the integration sources import 19 classes out of src/test/java. The reason is written into the script so it is not re-attempted.
|
@claude review this Worth steering you at the two things most likely to be wrong here, since most of this PR is prose:
Also worth a sceptical eye: the pom comment correction asserts that |
|
Claude finished @astubbs's task in 5m 49s —— View job Review complete
1. The three skip properties
On the safety question: I grepped the whole tree for The "9 javadoc jars" vs "eleven modules" language in the script header is consistent, not contradictory: 2. The plan document's retractionThe forward-pointer at the top names three things the postscript overturns (16s spread, the 4% threshold, the compaction poll) and the postscript does retract all three cleanly - I couldn't find any earlier claim about the noise floor or threshold that survives uncorrected. One gap: the "Still open" list's item 3 is stale and neither the forward-pointer nor the postscript says so. Line 134 lists Lower-confidence secondary note: the script/commit/PR body all say the saving from this change is "~10s of a ~600s job," but the plan's own per-goal breakdown (line 50-51) attributes 3. The pom.xml
|
…aving What changed for a user: nothing - both are corrections to claims in this PR's own documents. Two review findings, both real. TESTCOMPILE WAS LEFT READING AS OPEN. The plan's "Still open" list ranks compiler:testCompile as the third-largest lever at 60s, and this PR's own script comment says it was investigated and closed as unskippable - the integration sources import 19 classes out of src/test/java. The postscript retracted the compaction poll from that same list but not this, so a reader of the plan alone would still have taken it for an available lever. Now named as closed in both the forward-pointer and the postscript. THE SAVING WAS UNDERSTATED, AND THE NUMBER WAS THE WRONG KIND. The script, the commit and the PR body all said "~10s". That came from a prebuild_seconds difference this PR elsewhere RETRACTS as drift, so it was quoting a number it had already disowned. The per-goal attribution gives javadoc:jar 14s plus delombok 8s, so roughly 22s. Corrected to 22s and, more importantly, labelled as an ESTIMATE rather than a measurement - nothing here measured it, because 22s is a fifth of this lane's 119s noise floor. Neither figure is evidence; only the artefact counts are. Also records what the review established independently: 9 rather than 11 javadoc jars because two reactor poms are aggregators, and delombok's only consumer anywhere in the tree is the javadoc sourcepath, which is skipped in the same invocation - so skipping delombok cannot dangle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUpkpGoTw9GGpC8J9CpUoh
|
Both findings were real and both are fixed in e2100e7. The stale The "~10s" figure — this one is worse than a precision note, and thank you for pushing on it. Two things you established independently that I have folded into the script comment, because they On the artefact count you could not re-run: the |
What changed for a user: the `dups: clones` check stops timing out, and starts reporting a duplication percentage that is not a quarter of the truth. WHY THE BUMP IS NOT ROUTINE. At c828d78 the action installed jscpd with `npx jscpd@4` INSIDE the step it measures, so the base-branch engine step paid a cold install the PR-branch step did not - 288s against 15s on the same tree, established with a control arm two runs of the same job on the same base sha. npm's default fetch-timeout is 300000ms, so a single stalled registry request consumed this job's whole 5-minute budget. A timed-out job reports as "cancelled", which is why this read as mysterious rather than as slow, and why it was attributed to queue pressure twice before anyone timed the steps. AND IT CORRECTS A NUMBER WE HAVE BEEN READING WRONG. The action unzipped its own PMD distribution - 106 jars - into the tree it then scanned, and its report counts newline bytes across every file matching the detected extension list. Those jars added 594,727 phantom lines to this repo's real 197,832: a 4.01x inflated denominator, so every CPD duplication percentage this check has reported was about a quarter of the truth. THAT MAKES OUR OWN THRESHOLD WRONG, and it is deliberately NOT changed here. `cpd-max-duplication: '0.5'` was calibrated against a "measured baseline 0.11%"; the honest figure is nearer 0.44%, so the cap is almost tripped rather than comfortably clear. The right value should be argued from what THIS action now reports rather than from that arithmetic, which is what the next run of this check produces. Left for that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUpkpGoTw9GGpC8J9CpUoh
Three conflicts, resolved hunk by hunk rather than by taking either whole file - `--theirs`/`--ours` take everything and a merge that discards the other side renders as nothing at all in a diff-vs-base review. - `bin/ci-integration-test.sh`: kept THIS branch's sharding blocks (master has no sharding) but took MASTER's javadoc wording, which #439 corrected after review - this branch still carried the "~10s" figure that #439's own postscript retracts as drift. Verified all three survive: the corrected size estimate, #439's testCompile-is-closed note, and the shard guide. - `docs/plans/2026-09-03-...md`: took MASTER for both hunks. Same reason - master carries #439's review fixes (testCompile closed, the size relabelled as an estimate) and this branch predates them. - `docs/inflight/ci-shard-the-integration-gate.md`: kept THIS branch for both. Master describes sharding as "deliberately deferred", which was true when #439 was written and is not now. Checked rather than assumed after resolving: the duplicate-code action pin #439 added survived the auto-merge of maven.yml, and both integration matrix entries are still there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUpkpGoTw9GGpC8J9CpUoh
…dy measured the real one MY NOTE IS DELETED RATHER THAN AMENDED, BECAUSE ITS EXPLANATION WAS THE WRONG ONE I recorded dups: clones timing out as a pathological runtime on MultiInstanceRebalanceTest.java, on the strength of a real correlation: the two PRs touching that file failed at ~5m15s while three that did not passed in ~50s, and #441 failed with a one-line diff, which ruled out the diff's content. That evidence was sound and the conclusion drawn from it was not. #439 had already measured the actual cause: at the old action pin, the cross-check ran `npx jscpd@4` INSIDE the step it measures, so the base-branch step paid a cold install the PR-branch step did not - 288.4s against 15.0s on the same tree - and npm's 300s default fetch-timeout let one stalled request consume the whole 5-minute budget. Bumping the action to v1.1.0 fixes it; the check now passes in 45s. Both observations are compatible - a cold npm install lands where it lands, and my "which file" discriminator was coincidence dressed as a mechanism. A note asserting the wrong cause is worse than no note, so it goes; the correct account lives in #439's own commit body, which is where it belongs. The one thing my note got right independently is already in that commit: "a timed-out job reports as cancelled, which is why it read as mysterious rather than slow". Both of us reached that; neither needs to say it twice. CONFLICT RESOLVED BY KEEPING BOTH SIDES, NOT BY PICKING ONE test-untracked-ci-flakes.md's RegistrationRaceStaleResidentIT row was bumped to "7 seen" on both sides for DIFFERENT sightings - master added a sixth 2026-09-03 occurrence on a ce-optimize branch, this branch added a 2026-09-04 one. Either side taken whole silently loses a sighting and leaves a count that looks authoritative. Merged to 8, with both clauses and this branch's same-hour cross-branch control kept. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WErnxQd9Ew57F9SsqzdPU5
Inherited #439, #445 and #442. The first and third matter to this branch: they fix the `dups: clones` lane that was blocking it, and they do so with a cause this branch got wrong. THE DUPS NOTE THIS BRANCH ADDED IS DELETED, NOT UPDATED `ci-dups-clones-lane-times-out-and-blocks-every-merge.md` described a problem #442 has fixed, so keeping it - even rewritten as a was-fixed narrative - is what `docs/inflight/AGENTS.md` forbids. Nothing in it survives as both true and unowned: master's workflow now carries the explanation at the point of use, and states it better. **Its causal claim was also wrong, which is the part worth recording.** The note attributed the timeout to corpus growth from #419 and #116, reasoning from a clean date boundary - every attempt on 2026-09-03 passed, every attempt on 2026-09-04 failed, corroborated on an unrelated PR. The mechanism was right (the job exceeding its five-minute cap with jscpd still running) and the attribution was not. The real cause, per #442's workflow comment: the action installed jscpd with `npx jscpd@4` inside the step it measures, so the base-branch engine paid a cold install the PR-branch step did not - 288s against 15s on the same tree - and npm's 300s default fetch-timeout let one stalled registry request consume the whole budget. The date boundary tracked registry conditions, not master's content. That is a reasoning failure worth naming rather than quietly dropping: a correlation across two branches felt like a controlled comparison, and it was not - both branches shared the runner and the registry, which is exactly the variable that moved. The control that would have settled it was re-running an OLD head, which this branch never did. THE FLAKE LEDGER'S REGISTRATION-RACE ROW, CONFLICTED AND UNIONED Both sides had grown it independently and neither is a superset. Master's version carries four #442 sightings - two of them on documentation-only heads - and counts a `ce-optimize` sighting where this branch counts #433; those are different runs, so the count is their union rather than either side's. This branch's WITHIN-branch control is restored, because master's row never had it and it is the strongest evidence in the row: the same tree produced both outcomes. Master's documentation-only heads are that same argument from the other direction, so they are kept beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C3KnLcX4KibVMCP6sMfNgM
Description
Measures the
Integration Testslane - the PR build's critical path - and takes the one changethat survived. Three of the four things tried were refuted, and the refutations are the substance
of this PR.
What actually changes
bin/ci-integration-test.shno longer builds a javadoc jar, a sources jar or delombok output foreleven modules on every run. Nothing in a test lane consumes them.
Taken on first principles, not on a measurement, and that distinction is the finding. Verified
by counting artefacts in the CI log: a run with these flags builds 0 javadoc jars against 9
without, and delombok reports skipping 11 times against 2. It provably does less work, so it
cannot be slower. The saving is ~22s by goal attribution and was never measured - the per-goal
table puts
javadoc:jarat 14s anddelombokat 8s - because 22s is a fifth of this lane's noisefloor. An earlier draft said "~10s", taken from a
prebuild_secondsdifference this PR itselfretracts as drift. The artefact counts are the evidence here; neither seconds figure is.
The first cut of this change also carried a bug no timing could have caught:
-Dsource.skip=trueis not a property
maven-source-pluginreads (it wantsmaven.source.skip), so sources jars werestill built in both arms while the numbers looked identical either way. Found by counting jars.
The measurement result: this lane cannot resolve a 20-second change
Three concurrent samples of identical code spread 119 seconds - 563 / 575 / 682 - roughly
eleven times the largest effect worth chasing here. Decomposed, the variance is not in the work but
in fork packing efficiency (3.61x for one arm against 3.00x for another, same base, same fork
count, same 42 classes, dispatched together). The residual is which VM each job drew.
This PR retracts one of its own earlier claims: an intermediate reading reported
prebuild_secondsas cleanly separating a 10s effect. The control has since moved further than the effect did. No
metric this harness collects resolves 10-40s on this lane.
That is an argument for a structural change rather than more tuning, and
docs/inflight/ci-shard-the-integration-gate.mdcarries sharding with the ordering argument: eachshard re-pays the ~136s serial build, so cutting that first is what makes sharding pay.
Refuted, and now recorded so nobody re-runs them
forkCount4 -> 6 makes the lane SLOWER - 469s of failsafe against 420s - because the sametests cost 11% more CPU time under six forks. It also produced a first-ever timeout failure in
ManagedPCInstanceLifecycleTest. A 2x2 factorial over forkCount x probe-split, each arm'sprediction written into its commit message before it ran. With thread-parallelism closed in
2026-07, both parallelism directions for this lane are now measured and closed.
NOT advance" 4 times out of 4, never fired, and paid the full deadline anyway. Dropped rather
than merged; log compaction need not advance the log start offset, so the detector was wrong.
The blind spot it exposed is real and tracked separately: that sleep asserts nothing, so a run
where compaction never happened is indistinguishable from one where it did.
compiler:testCompile(60s, the largest remaining serial cost) cannot be skipped - theintegration sources import 19 classes from
src/test/java. Killed on inspection before it costa CI run; the reason is now in the script.
Two stale claims corrected
parallel-consumer-core/pom.xmlwithheld a per-fork log directory from failsafe because "itdeclares no forkCount, so it runs at the default of 1".
maven.ymlpasses-DforkCount=4andfailsafe pins no
<forkCount>, so it takes that bare property - the-DforkCountvs-Dsurefire.forkCounttrap the same comment warns about. Four JVMs already share one flatpc.log.dir, into appenders that comment says lose writes under exactly that condition.bin/ci-integration-test.shsaid "the ci profile runs sequentially on GitHub-hosted 2-corerunners", which described the unparameterised script rather than what CI invokes.
Also records a seventh sighting of the
RegistrationRaceStaleResidentITsetup-guard flake,deliberately not attributed to fork pressure: codecov shows the identical assertion failing
earlier the same day on an unrelated branch at
forkCount=4.Flagged, not asserted
The runner reports
ubuntu-24.04and GitHub moved public-repo runners to 4 vCPU in early 2024,while
docs/self-hosted-runner.md,docs/ci.mdand the priorunit-gaterun all reason from"2-core" - that run built a 2-CPU Docker replica to imitate this runner. Nothing here read
nproc. One line in the CI step would settle it.Also: unblocks
dups: clones, which was timing out repo-wideBumps
astubbs/duplicate-code-cross-checkto v1.1.0. At the previous pin the action installedjscpd with
npx jscpd@4inside the step it measures, so the base-branch engine step paid a coldinstall the PR-branch step did not - 288.4s against 15.0s on the same tree, established with a
control arm. npm's default 300s
fetch-timeoutmeant one stalled registry request consumed thisjob's entire 5-minute budget, and a timed-out job reports as "cancelled", which is why it read
as mysterious rather than slow.
Measured on this PR after the bump: the two engine steps are 17.7s and 14.6s (1.2x, from 19.2x),
and the check passes in 45s where it was dying at 315s.
It also corrects a number this repo has been reading wrong. The action unzipped its own PMD
distribution into the tree it scanned, inflating the line-count denominator. CPD duplication now
reports 0.38% where it reported 0.11% - a 3.5x correction.
cpd-max-duplicationstays at 0.5%: 0.38% passes with real headroom, and the+0.1% vs baserule is the actual safety net. An earlier draft of this PR predicted 0.44% by scaling the old figure
and was going to recommend raising the cap; the measurement came in lower and no change is needed.
Checklist
docs/inflight/notes, and corrected comments inpom.xmlandbin/ci-integration-test.shdocs/features/-N/A - no user-facing feature; a CI build-flag change plus measurement write-upsN/A - changes no test. The evidence is thirteen CI runs cited by run id in the plan document; the one test change that was tried is documented as a proven no-op and deliberately not mergeddocs/inflight/working note (pr-/branch-) started at the PR's first commit -docs/inflight/pr-integration-gate-wall-time.mdce-simplifyandce-code-reviewlocally -N/A - asked for @claude review on the PR instead. The code diff is three Maven flags in a shell script; the rest is documentation