caffeine Documentation

A high performance caching library for Java

17,892 stars Java Java 1 file · ~168,272 tokens
RAW Doc

Technical Documentation: ben-manes/caffeine

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)








Caffeine is a high performance, near optimal caching library. For more
details, see our user's guide and browse the API docs for the latest
release.

Cache

Caffeine provides an in-memory cache using a Google Guava inspired API. The improvements draw on our
experience designing Guava's cache and ConcurrentLinkedHashMap.

java
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(Duration.ofMinutes(5))
    .refreshAfterWrite(Duration.ofMinutes(1))
    .build(key -> createExpensiveGraph(key));

Features at a Glance

Caffeine provides flexible construction to create a cache with a combination of the following
optional features:

In addition, Caffeine offers the following extensions:

Use Caffeine in a community provided integration:

Powering infrastructure near you:

  • Dropwizard: Ops-friendly, high-performance, RESTful APIs
  • Cassandra: Manage massive amounts of data, fast
  • Coherence: Mission critical in-memory data grid
  • Accumulo: A sorted, distributed key/value store
  • Kafka: A distributed event streaming platform
  • HBase: A distributed, scalable, big data store
  • Apache Solr: Blazingly fast enterprise search
  • Infinispan: Distributed in-memory data grid
  • Redisson: Ultra-fast in-memory data grid
  • OpenWhisk: Serverless cloud platform
  • Corfu: A cluster consistency platform
  • Grails: Groovy-based web framework
  • Finagle: Extensible RPC system
  • Neo4j: Graphs for Everyone
  • Druid: Real-time analytics

In the News

Download

Download from Maven Central or depend via Gradle:

gradle
implementation("com.github.ben-manes.caffeine:caffeine:3.3.1")

// Optional extensions
implementation("com.github.ben-manes.caffeine:guava:3.3.1")
implementation("com.github.ben-manes.caffeine:jcache:3.3.1")

For Java 11 or above, use 3.x otherwise use 2.x.

See the release notes for details of the changes.

Snapshots of the development version are available in
Sonatype's snapshots repository.


.Claude Docs Audit Output (`.claude/docs/audit-output.md`)

Audit Output Location

Write reports and transient analysis under .local/, grouped by producing model:

text
.local/audits/<model>/<name>.md
  • <model> — the short id of the model that produced the report, lowercased, no vendor
    prefix and no context-window suffix: opus-5, fable-5, sonnet-5, haiku-4.5,
    gpt-5.6-sol. When a run spans models (an auditor on one, an evaluator on another), use the
    model that orchestrated it. An assigned AUDIT_REPORT_PATH overrides this derivation
    entirely; see Rules for agents.
  • shared occupies the <model> slot for artifacts that are not one model's output: the
    consolidated backlog, a bug spec worked across sessions, anything aggregating several runs.
  • <name> — the invoking skill's name (audit-adversarial.md). A multi-agent run suffixes
    per agent (audit-jcache-conformance-groupABC.md, audit-sibling-divergence-groupA.md) and
    a verification pass adds -verification; the canonical unsuffixed name is the synthesis.
    Never overwrite a report you were dispatched to verify, consolidate, or read.

There is no date or cycle level. Rerunning an audit under the same model replaces its report.

Examples:

text
.local/audits/opus-5/audit-jmm.md
.local/audits/gpt-5.6-sol/audit-jmm.md                  # same audit, other provider
.local/audits/fable-5/audit-adversarial.md
.local/audits/fable-5/audit-adversarial-evidence/r1-challenges.md
.local/audits/shared/audit-consolidated.md              # cross-model queue index
.local/audits/shared/queue/<section>/<item-id>.md       # one work item

Keep .local/ between sessions for handoffs and cross-model comparison. It is gitignored and
machine-local; checked-in guidance must remain useful if that tree is absent.

Consolidated queue

Cross-audit consolidation, including run-audits.sh --consolidate, uses this layout:

  • .local/audits/shared/audit-consolidated.md: brief workflow instructions, counts by status, and
    section tables with linked ID, status, and short subject. Claims and evidence go in item files.
  • .local/audits/shared/queue/<section>/.md: one item per file. Sections, in order, are
    core, async, jcache, guava, simulator, examples, build-ci, docs. ID prefixes match
    the section except simulator uses sim, examples uses ex, and build-ci uses ci.
  • .local/audits/shared/queue/history.md: consolidation provenance, report inventory, coverage
    limits, and prior cross-item decisions. Consult relevant portions, not the entire history.

Each item starts with # : <short subject>, Status: <status>, and Severity: <severity>.
Include a Prior rulings and context section linking to named anchors in history.md, then the
current decision and next step, original claim, source-report links and finding IDs, and earlier
evidence/counterarguments. Distinguish reported measurements from current checks.
Use open for pending work (including disputed claims), unverified for unresolved reachability,
contract, or impact, resolved for an evidenced repair, and closed for an agreed no-fix outcome.

Extend the queue in place. Reuse IDs for matching claims; assign new numeric IDs above the section's
maximum, never recycling or renumbering them. Separate distinct subclaims without dropping their
parent ID. Preserve resolved/closed decisions when claims recur, and retain contrary evidence.
Never prune items to meet a survival ratio.

Map history entries by mechanism and scope, including family members not named by ID. Label
near-matches and historical counterarguments without treating them as current dispositions.
Give each history entry a stable named anchor. When adding or revising one, update the affected
items' links. If a mechanism review finds no related entry, record that explicitly with the review
date; a missing section means unreviewed. Neither case exempts an item from canonical module rules.

After each pass, refresh index links and counts from item files, using an existing helper if
available. Verify report coverage, retained items/counterarguments, and relative links. Work from
the selected item and relevant rulings; use a fresh context for unrelated items and leave a concise
handoff. Per-skill synthesis reports remain in the producing model's directory.

Rules for agents

  • The orchestrator computes the output path once and passes it to every agent as
    AUDIT_REPORT_PATH. Use that path, not the agent's self-reported model name; group and
    verification siblings go beside it. .github/scripts/run-audits.sh exports it;
    .claude/agents/auditor.md Phase 4 and .claude/hooks/audit-report-guard.sh honor it.
  • The auditor agent must not read anything under .local/audits/ (prior conclusions bias a
    fresh run) while still being required to write its own report there. See
    .claude/agents/auditor.md Evidence Boundaries.
  • Long-running tooling (audit-temporal-walk) takes its model from the AUDIT_MODEL
    environment variable, since a shell-launched walk cannot know it; the invoking agent exports
    its own model id. audit_paths.reports_dir falls back to an existing tree for the module when
    that variable is missing, so a resumed walk still finds its state.json.

.Claude Docs Audit Rounds (`.claude/docs/audit-rounds.md`)

Audit Rounds

Read when starting or triaging a batch /audit-* round.

Complete the audit batch before repairs. Use the
consolidated queue format, verify no claims or counterarguments
were dropped, then work one item at a time and revisit coverage affected by fixes. Keep the tracker
current across sessions. A full cycle has typically taken 2–3 weeks.

Serialize the runs. Quota exhaustion mid-run breaks an audit rather than pausing it, so
never fire runs in parallel. /audit-adversarial is the single priciest skill (roughly half a
weekly quota: 8 reviewers plus their evaluator challenges), so order the batch value-descending
and an early cutoff still banks the most bugs.

Prefer a second model on a skill over a third model repeating it. Prior paired runs produced
model-unique findings, including their most severe claims. The preferred order is:
subsystem-safety, liveness, feature-interaction, jcache-conformance,
sibling-divergence. build-ci and serialization are the least worth it.

Leave /audit-jdk-seam out of a batch. It is kept for an occasional manual run, not the
normal checklist.

Down-weight, but do not skip: re-entrancy (a full pass has closed at zero, and callback
re-entrancy warnings are not wanted, which removes the remedy from most of what it finds);
simulator periphery (a testing tool — its bugs mislead benchmarks, they do not harm users);
and the formal-shape lenses (jmm, linearizability, arithmetic, correctness-proof,
map-contract), which are cheap to run but have gone several passes without a core defect.

A report row is a claim, not a confirmed defect. An earlier sweep reported 186 claims,
26 after source review, and 7 after standing rulings; even high-rated claims were rejected.
Those historical counts do not establish a rejection target or release confidence. Check the
contract and matching module rulings first. Use a focused reproduction for a behavior repair;
a source/contract proof can settle a documentation correction or no-code disposition. Preserve
the trigger, controls, counterarguments, and evidence limits.

Use general-purpose, not auditor, for verification, triage, and consolidation passes
over existing reports. The auditor carries a mandatory-report-write gate (SubagentStop) that
will pick a canonical filename and overwrite the source report you are consolidating from. If
you must use it for reuse, assign it an explicit -verification path.


.Claude Docs Design Decisions (`.claude/docs/design-decisions.md`)

Design Decisions

Intentional behavior and review invariants, with their rationale and evidence. Read the
relevant section before changing a mechanism; use ruled-out for adjudications.

Eviction

Weight=0 is a pinning feature. Entries with weight=0 are skipped during eviction
(evictFromWindow, evictFromMain, evictEntry). This is an intentional user-facing
API (inherited from ConcurrentLinkedHashMap where weight was >=1, Guava's Cache where >= 0).
Used internally for in-flight async futures.

The re-scan cost is accepted. evictFromWindow restarts at the deque head each cycle and
never relocates a zero-weight node, and evictFromMain skips its victim inline, so cold
pinned entries at the LRU end are re-traversed whenever the region is over budget — O(pinned)
per cycle in the worst case. A dedicated zero-weight queue to hold them out of the scan was
explored and rejected: it made the eviction paths messier for little value. Don't propose it
again without a measurement showing the scan actually costs something.

Region transfers are budgeted per maintenance cycle and re-armed. evictFromWindow and
demoteFromMainProtected move at most QUEUE_TRANSFER_THRESHOLD entries per cycle and set
PROCESSING_TO_REQUIRED when a backlog remains, because a setMaximum shrink can leave either
region arbitrarily oversized and one cycle would otherwise drain it whole under evictionLock.
The climber's increaseWindow / decreaseWindow carry their remainder in adjustment instead
(see the climber entries below). A region briefly over its maximum right after a resize is the
budget, not a leak; the protected demotion lacked the re-arm from the 2019 adaptive commit until
the 2026-08 audit sweep, which is why an idle cache could sit with protected oversized until the
next access. Don't remove either re-arm.

Transient negative weightedSize is acceptable. maximumSize allows eviction
before/after threshold. Eventual consistency is fine given documented promises.
Weight convergence is guaranteed by the telescoping sum property across all write
buffer task orderings.

Weights are static after computation. The weigher is called at creation/update
time. The weight is not recalculated afterward — relative weights don't influence
eviction ordering, only total capacity accounting.

FrequencySketch.reset()'s pause is accepted, and chunking it is the wrong trade. The reset
halves every counter under evictionLock: 0.29 ms at a 100k maximum, 2.0 ms at 1M, 32 ms at 10M,
238 ms at 100M. It is rare in proportion to the cache, since sampleSize is 10 × maximum and the
reset fires when that many increments have been recorded, so a 100M cache resets once per billion
recorded reads, about 0.24 ns/read amortised. Amortising the pause means chunking it, which lowers
eviction quality by querying a half-reset sketch. The default async eviction hides the pause
entirely, and on a caller-runs executor the worst case is still cheaper than the misses the sketch
prevents. The count += Long.bitCount(table[i] & ONE_MASK) odd-counter correction is a
cross-iteration reduction and is what stops the loop auto-vectorizing; dropping it is much faster
but loses the correction, and no argument has justified that. SIMD is the answer once it is
available. It has produced no user reports and does not show in GetPutBenchmark.

A weak-key lookup allocates, and that is accepted. The weak-key node factories build a
LookupKeyReference per lookup (20.0 ns/op and 24 B/op against 2.6 ns/op and 0 B/op for strong
keys); it is handed to ConcurrentHashMap.get, so escape analysis cannot remove it. Avoiding the
allocation means caching one wrapper in a thread-local and mutating it to set and unset the
referent around each lookup, which pins that instance to the thread. That idea was raised and
rejected in issue #294 over virtual threads, where an instance per virtual thread is unbounded,
and over classloader pinning. A young-generation allocation is the accepted trade.

Policy weight uses a signed 62-bit payload returned as a long. Reordered update tasks
are normal: the value is written under the node monitor but the task is queued after it is
released, so a second writer can queue first. The node's own field then walks past the true value
and back, which the design tolerates. What it cannot tolerate is truncation. Once the intermediate
sum leaves the int range, a transfer copies the truncated value into the long region totals, and
nothing replays that copy: the node settles at its real weight while windowWeightedSize or
mainProtectedWeightedSize keeps a permanent 2^32 residue. Measured 2026-09-08 with weights near
Integer.MAX_VALUE, the window then reads as permanently over its maximum, evictFromWindow
drains it every cycle, and every later arrival goes straight to probation, so the admission window
is dead for the life of the cache. The released 3.2.4 and 3.2.5 reproduce it, so it is not new.
A sign test on the transfer does not close it: half the wraps land positive, where the value is a
plausible weight. The write buffer bounds queued contributions, but not writers paused before
publishing their updates, including inside inline removal listeners. The historical witness
reached 34 bits; no practically reachable overflow of the packed payload has been demonstrated.
policyWeight holds the low 32 bits and metadata's spare 30 bits the signed high portion, at no
extra layout cost (JOL over all 147 node classes is unchanged, where a plain long field grows the
16 strong-value weighted classes by 8 bytes). Pinned by
BoundedLocalCacheTest.put_reorderedUpdates_leaveNoRegionResidue, which orders two writers through
a removal listener on a direct executor. Don't re-narrow the field, and don't guard the transfer
on the sign instead.

Two notions of "weighted", and the internal one gates on both. The isWeighted field is whether
the caller configured a weigher, and it is what Policy.Eviction reads for isWeighted(),
weightOf and weightedSize (CaffeineTest.maximumWeight_large pins this for an explicit
Weigher.singletonWeigher(), where the method answers false). BoundedLocalCache.isWeighted()
answers a different question, whether entries may be assigned different weights, and that is what
decides whether the frequency sketch can be sized from the maximum or has to be sized from the live
entry count. Neither test alone answers it: an async cache always wraps its weigher in
AsyncWeigher, so identity against the singleton weigher says "varies" even for an unweighted one,
while a caller who passes Weigher.singletonWeigher() explicitly sets the field even though every
entry weighs the same. The method conjoins them. Sizing the sketch from the entry count is not free:
ensureCapacity forgets all counts when it grows, so filling a maximumSize(10_000) async cache
allocated the sketch twice (8192 then 16384) and discarded the frequencies gathered over the first
half, where the sync equivalent allocates 16384 once. It also called data.mappingCount() on every
insertion past half the maximum. The one case still answered wrongly is an async cache given an
explicit Weigher.singletonWeigher(), which needs unwrapping AsyncWeigher to reach and costs only
that same extra warmup allocation. BoundedLocalCacheTest.isWeighted_onlyWhenWeightsVary pins the
cases answered correctly.

The climber has three size tiers, in the configured maximum's native units. Weight units
are deliberate: the weighted stress track included about 200 entries of 25–100MB in a 10GB
cache and scored 14 wins / no losses. A later comparison on 18 byte-weighted trace cells found
no consistent reactive advantage at low resident counts. Grow-only entry-count selection gave
small, mixed changes; bidirectional selection repeatedly reset the controller on cells crossing
the threshold. Retain the configured-weight threshold on this evidence; see
entry-count tier selection.
Many tiny entries under a small weight bound land on reactive, which remains safe there.

Maximum Controller Reason
≤512 Slow reactive: grow first, minimum initial step 2, decay 0.995 rather than 0.98, period stretch capped at 4× Integer windows and noisy samples make an initial shrink a no-op and prevent later reversal.
513–4096 Standard reactive Density was near-neutral below ~2048 (+0.12–0.24pp), but lost up to 10pp on corda+loop at 513–1024.
>4096 Goal-audited density Added about 125pp across the large cells of the 48-trace set while accepting small frequency-trace regressions.

The gates are SLOW_ADAPT_THRESHOLD and DENSITY_THRESHOLD; slow tuning uses
SLOW_ADAPT_DECAY_RATE and SLOW_ADAPT_RATIO_CAP.

Density compares regions within one sample. It uses
ln((windowHits / windowMaximum) / (mainHits / (maximum - windowMaximum))), with a step
proportional to error (DENSITY_GAIN = 0.03), capped at MAX_STEP_FRACTION (30% of maximum).
This avoids the cross-sample workload swings
that obscure reactive gradients on a flat hit-rate curve, but it is average resident density,
not marginal value. Its equilibrium depends on the starting window; ghosts would supply a
different signal but were rejected for cost. Extending density below 4096, even with the probe
machine, lost 1.3pp on cs@563. Kickoff, regret, anneal, and wide-start variants traded one
small-cache trap for another rather than removing the bias.

Blind corners require experiments. A region with fewer than requestCount >> MIN_SIGNAL_SHIFT hits (~0.1%) supplies too little evidence to hold position. Pure density
can pin at the floor about 28pp below LRU at any size. Probe only when the starved region is
small (≤¼ maximum), or the whole sample is dead; probing for a starved large main region
destroys the functioning window on corda. Deep refractory rungs increase both reach and
commitment, while cheap early adjudication protects thin-signal floors. Failures command a full
return and back off (PROBE_BACKOFF_INITIAL 16 through 64 samples), costing about 1pp on short
w50/S1 traces and amortizing toward
zero with longer runs. Preserve the current router and ownership rules in
climber review constraints and hill-climber §4.

The evidence behind the asymmetric verdicts and crash prices remains relevant:

  • Starvation-walk interior noise pricing is min(max(5pp, 3×rateDeviationEma), 15pp).
    It recovered 8.2pp on the dosed-mixture trap; dropping the cap lost 0.6pp on metronome.
  • Audit crashes retain the depth bound and buy persistence in time. The first crash is cheap;
    an already-crashed equilibrium's retry tolerates two below-bar samples and aborts on the
    third. This raised the constructed moat from 41.97 to 44.3; a 22-cell real-corpus scan found
    no echo. Giving every walk tolerance failed mixture_d025 / mixmod on short traces.
  • Sharing crash state paired independent pulses into rung 64 / clock wait 128 and a
    130-sample floor pin, 6pp below LRU. Non-crash endings retire only their owner's streak;
    an audit confirm alone also clears starvation state when resetting that ladder. Pins:
    audit_budgetExpiry_leavesTheStarvationLedger and
    walkStep_reversalThroughBase_leavesTheOtherLayersLedger.
  • An up-probe prices the capacity it takes from probation, frozen at arm. Main's protected
    average vetoed useful positions on trickle (14–17pp below its own engine); live probation
    became enriched by the walk's demotions and vetoed every escape on demoflood. Down-probes
    keep the average comparison. The low-hit-rate lowmix trade remains a gate sentinel, with
    no real-trace echo across the defended corpus.
  • Crash veto precedes adjudication, so a destroyed region cannot win by a density ratio
    against another region earning nothing. A neutral verdict is not a success (S3). The
    current reversed-confirm rule is in the checklist below; older blanket reward rules no
    longer describe the machine.

The density floor is 2% (WINDOW_FLOOR_FRACTION), with an upward clamp from below it.
The initial window is 1%, so
a clamp that only blocks further shrink can wedge below the intended floor. At size 600 a
0.5% floor held only three entries and the recency phase scored about 23% versus 44% optimum.
The 2% large-cache safeguard cost at most 0.35pp on frequency-optimal workloads; kickoff and
EWMA-regret escapes lost up to 8pp on w50 or only moved transiently before retrapping.
Re-run corda+loop at 4097–8192 before lowering the floor. Resizing resets probe state and
sample.previousHitRate, so the new geometry is not judged against an old sample.

The sketch's shrink retrack and reset()'s zero clamp are a matched pair. ensureCapacity
keeps the table (it is grow-only, since reallocating wipes the counts and blacks out admission)
but re-points sampleSize at the new maximum, so a shrunken cache does not age on its old size's
cadence; the observed count is clamped to just below the sample to keep the equality reset test
reachable. That retrack breaks the precondition reset() was written under: count/4 < size held
only while the table matched the sample (count/4 ≤ 8·maximum < 10·maximum = sampleSize), so
without a floor at zero the correction underflows to a large negative size and no counter is
halved until ++size climbs back — restoring exactly the old-size cadence the retrack removes,
and doing it for roughly 4 × tableLength additions across successive resets. Measured
(2026-08-07, audit-arithmetic F1): a 64K-entry cache shrunk to 256 lands at size = −62,579,
about 25 sample periods, and recovery is driven only by novel traffic — 1M requests against
saturated counters advanced it by 932. Reachable with no Policy call: a weighted cache retracks
on every addition, so updating resident entries to heavy weights collapses the entry count
(200,000 → 20 measured) and drives it. During the stall admit() degrades to tie-breaking, so a
post-shrink working set cannot displace stale survivors. The clamp is inert whenever the table
matches the maximum, so it costs nothing on the normal path. Don't remove either half. Pinned by
FrequencySketchTest.ensureCapacity_shrink_denseTable_agesOnSchedule — note its neighbour
_shrink_resetReachable exercises the same flow on an empty table, where the correction is 1,
which is why the underflow shipped.

The retained table's reset cost after a large shrink is accepted. After a 1M-to-1K
shrink, an 8 MiB table took 1.94 ms under evictionLock, about 194 ns per increment amortized
against 0.19 ns when table and maximum match. Cost scales with peak/current ratio: a 10× swing
was negligible, while ~1000× was visible. This is reachable through unweighted
Policy.Eviction.setMaximum as well as weighted retracking. Hysteretic shrinking was declined
because it conflicts with per-addition weighted retracking and loses the admission history.

The large-cache sample period is SAMPLE_MULTIPLIER × maximum (4×), decoupled from the
frequency sketch's own 10× reset
(they use separate counters — sample.hits/sample.misses
gate
the climber, FrequencySketch.size gates the sketch reset). At 10× a large cache gets only one to
three adaptations over a finite benchmark trace and never reaches a large (20–50%) optimal window; 4×
gives it enough steps to converge. It is kept at 4× rather than lower because a shorter period makes
the density estimate near a small converged window noisy enough to jitter a frequency-friendly
workload off its optimum — the cs trace craters at 2×. The density signal is inherently
recency-biased (it over-values the window), so frequency-optimal traces (fiu_madmax, cs,
fiu_webmail, several ARC traces) give back ~0.5–2pp versus the reactive climber; on every one they
still beat LRU and match/beat Merlin and stay near-optimal, and the trade buys large recency-workload
gains (corda's cliff, OLTP/fiu_ikki/metaCDN/fiu_homes converging to their ceiling). An
adaptive period (short while moving, long once settled) was tried and rejected: starting short
made dup-heavy/phasey traces jitter persistently and never lengthen, cratering cs worse than a fixed
2×. Don't reintroduce it.

The climber is held until the cache is half full, and the gate is residency, not the sketch's
existence.
climb() skips determineAdjustment while weightedSize() < (maximum() >>> 1), discarding the
sample through WindowClimber.discardSample once it fills, and still resets it outright when the
sketch is uninitialized. A cache below that
line is not evicting, so its sampled hit rate says nothing about the window/main split, and the
sketch's sampleSize caps the sample period, so a sketch sized for less than the maximum reads
that meaningless signal on a compressed cadence. Caffeine.initialCapacity(n) is one way in (the
generated constructor calls ensureCapacity(min(maximum, n))) and growing maximum through
Policy.Eviction.setMaximum is the other. Measured 2026-09-08 on product.Caffeine: a
maximumSize(400_000) cache hinted at initialCapacity(1000) scored 29.27 on arc/S3 against
41.04 un-hinted, arc/DS1@1M 8.76 against 12.48, arc/P3@100k 33.50 against 40.43, and the
window sat at 47–80% of the maximum forty million operations later where the un-hinted arm rested
at 26%. The sketch reinflates on schedule at half fill; the window it drove to does not come back,
which is why the old acceptance ("a transient that self-heals as the cache fills") was wrong. Don't
weaken the gate back to the sketch's initialization alone, and don't latch it. The unlatched form
is what closes the setMaximum growth case, and it is two field reads under evictionLock. Pinned
by BoundedLocalCacheTest.adapt_partiallyFilled_holdsWindow / adapt_filled_climbsWindow.

Climber adjustment is a multi-cycle carry-over, not stale state. increaseWindow /
decreaseWindow transfer at most QUEUE_TRANSFER_THRESHOLD (e.g. 1000) nodes per maintenance
cycle, then store the unfulfilled remainder back into the climber's adjustment
(quota / -quota) — the leftover, not zero. On a large cache the per-decision step
(≈ 0.0625 × maximum, e.g. 62,500 at a 1M maximum) dwarfs the (e.g 1000) node cap, so a single
climber decision is deliberately drained across many later cycles. Each of those cycles
determineAdjustment early-returns at requestCount < effectiveSampleSize (the sample was
reset and has not refilled) without touching adjustment, and climb re-applies the
carried remainder. Once the sample refills, a fresh determineAdjustment overwrites
adjustment with a new decision. This looks like "a stale adjustment re-applied without a
fresh hit-rate sample," but it is the completion mechanism for a work-capped transfer.

The symmetric give-back after the transfer loop (mainProtectedMaximum += quota; windowMaximum -= quota) keeps the partition sum (windowMaximum + mainProtectedMaximum + implicit-probation == maximum) constant — the maxima track the partial transfer that
actually happened (added by "Fix bugs in adaptive policy"). Three consequences
worth not flagging:

  • Pinned leftover. If the carried quota is smaller than the policy weight of every
    candidate (e.g. quota = 1 while all entries weigh 100), the loop moves nothing and
    re-stores the same value, so the window stays put until a real sample overwrites
    adjustment. The window genuinely cannot grow by a fraction of an indivisible heavy entry.
    increaseWindow may already have demoted protected entries against its provisional cap.
    Giving back the quota restores the caps, not those entries' positions; later promotion depends
    on accesses and available protected capacity.
  • Probation is the implicit slack region. The transfer draws from both probation and
    protected but only decrements mainProtectedWeightedSize for protected moves, so probation
    absorbs the difference between the window-maximum shift and the protected weight moved
    (Δ windowMaximum == total weight transferred holds exactly).
  • The quota is a soft knob, not an accounting invariant. quota is how much room the
    climber may borrow from the other region for a probabilistic guess about an unknown future;
    taking a little too much or too little is meaningless. A node carrying a transient negative
    policyWeight (a same-key UpdateTask reordered against its predecessor — see "Two weight
    fields") passes quota < weight and inflates the quota via quota -= weight, so the
    give-back can overshoot and move windowMaximum / mainProtectedMaximum opposite to the
    commanded direction, or out of [0, maximum]. That is not a defect and must not be clamped:
    the partition sum still holds, the region weighted sizes stay exact (they debit the same
    snapshot they credited), a negative windowMaximum / mainProtectedMaximum only makes
    evictFromWindow / demoteFromMainProtected drain that region, a policy-quality wobble. An
    out-of-range cap walks back only by the weight each later transfer moves: the
    min(adjustment, donor), <= 1 and max(0, …) guards stop a call from pushing it further
    out, they do not pull it back. A swing larger than a cycle's transfer (one key's weight
    swinging by more than the window holds) therefore suspends the split for many cycles, the
    window cap above maximum idling evictFromWindow while evictFromMain still bounds the
    total; the verdict does not turn on the duration. The invariant that matters is
    that policyWeight converges, so a region's size keeps reflecting the entries inside it;
    how a mid-flight snapshot lands on the quota does not. Clamping the quota also would not
    restore a reservation — it just relocates the inaccuracy from the maxima to the transfer
    volume. The duration of the excursion does not change this ruling.

The hardening companion to this: ReactiveClimber.samplePeriod guards the small-cache
ratio against a 0/0 NaN (when both the maximum and step size are zero). The NaN would
otherwise zero the effective sample size, defeat the sample guard, and poison
sample.previousHitRate. Neither construction nor reactive decay reaches that state.
Generated constructors call WindowClimber.resized(maximum()), which seeds a step of at least
2.0, including for maximumSize(0); decay never rounds it to exactly 0.0. Keep the guard
as defensive hardening. BoundedLocalCacheTest.adapt_smallCache_zeroMagnitudeDoesNotPoisonHitRate
explicitly forces the zero step.

The climber commands in double; the cache applies (long) of the command, truncated
toward zero once at publication.
Positions, region maxima, and the walk's base are long, and
position identity is band-based (Reading.stableBand, 2% of the maximum), so a command's lost
fraction is not itself an error. Two consequences follow and are accepted: walkStep's
base-crossing predicate reads the continuous command, so a reversal whose truncated landing is
exactly the base fails the walk one sample earlier than an integer predicate would (the same
ending, one sample cheaper); and the 2% floor is a double, so the integer window rests one entry
below it (pinned by walkStep_floorBasedWalk_endsAtBudgetWithAFullUndo), and at maxima above
2^53 weight units the floor comparison has a rounding band of a few units, which is unreachable.
Don't make the floor or the predicate integral.
Undo accounting closes in integral published commands:
undoRemaining is a long charged with each return command as published, not with the
fractional capped stride. Charged with the fraction it closed short of the base by the cap's
fraction per capped stride (8,192: 2,457 + 2,457 + 84 for a 5,000 return), and at a permanently
starved corner, where every deep-rung probe fails and undoes, that re-based each cycle 1–2 entries
toward the probed direction, a slow creep toward the corner boundary. Pinned by
probeEnding_adjudication_wrongSignFailsAndDoubles (the commands sum to the distance).

Physical undo arrival is best effort. The cache can retain unapplied movement when an entry
does not fit or the transfer budget runs out; the next completed sample replaces that carry.
Several capped strides can each lose a remainder, and later probes can inherit the resulting
landing. There is no one-entry bound per return or across returns, and the transfer-cap case also
affects unweighted caches. Retaining the base and reconciling actual movement was measured in two
forms: bounded to the original return duration, or continued until arrival/nonprogress. Across ten
weighted cells at eight seeds, mean object-hit-rate changes were only -0.050 to +0.060pp, while the
latter prolonged retreats and showed a throughput slowdown signal. Retain the approximation on
that evidence, not an assumption that displacement is harmless or confined to tiny caches. See
reconciling failed-probe returns for scope and costs.

Async load completions replace quietly. A completed future's handleCompletion (and the
bulk fillProxies) calls replace(..., quietly= true): the UpdateTask finalizes the weight and
expiration but skips onAccess's sketch increment and climber hit counters. The entry already
paid its miss at insertion; counting the completion as an access doubled the key's per-load
admission frequency and window-attributed one synthetic, write-buffer-lossless hit per miss —
measured at up to −38.6pp (w50) on the density climber and −12.7pp (corda+loop stress @ 512) on
the reactive climber. A
material quiet update (weight changed, or the write time moved beyond the 1s tolerance) routes
through the UpdateTask and still reorders the deques; an immaterial one skips policy work.
With access expiry alone, a slow same-weight completion can leave a live head ahead of older
expired entries when the tail is newer. Cleanup can defer reaping those entries until the head
is reordered or expires; reads still filter them. This is the same accepted delayed reaping as
when a read-buffer reorder is dropped. User-initiated writes remain loud by design. Don't re-add
access recording to the completion path, and don't flag the immaterial-completion skip as a
missing reorder/refresh. Pinned by
ExpireAfterAccessTest.quietCompletion_staleOrder_defersPhysicalExpiration.

The refreshIfNeeded completion's remap is quiet the same way (RemapHints.quietly, honored at
remap's update dispatch): the triggering read already recorded its access, so a loud reload
completion double-counted every refresh-eligible read into the sketch and the climber's hit
sample (1000 reads → 2000 of each once past the refresh interval). A reload finalization is
bookkeeping, not a usage. Manual LoadingCache.refresh / async tryComputeRefresh completions
are unchanged (explicit per-call API action, no read-stream amplification); revisit only with a
measured skew. Pinned by BoundedLocalCacheTest.refreshCompletion_doesNotRecordAccess.

A committed refresh completion always takes the material branch, so remap's immaterial one is
quiet-capable but unreached in production (a rejected one returns at the preserveTimestamps
exit, before the dispatch). refreshIfNeeded requires refreshAfterWrite, which
makes exceedsWriteTimeTolerance's refresh disjunct true at every completion: either the
configured duration is within the 1s tolerance, or the refresh fired because the entry aged past a
duration longer than the tolerance. The quietly guard on that branch is not defense-in-depth, it
is the same contract replace implements for the async completions, whose callers do not require
refreshAfterWrite and do settle immaterially. Don't delete it as dead code; it is pinned through
the compute(..., hints) seam by BoundedLocalCacheTest.remap_quietly_doesNotRecordAccess and its
loud twin.

In-place reloads record a climber miss. computeIfAbsent, compute, put, and putIfAbsent
reuse a node when its entry has expired or its value was collected. Their UpdateTask uses
Access.RELOAD to increment the sketch and record the miss an AddTask would record after
physical removal. replace rejects dead entries. Access.QUIET takes precedence for internal
refresh completions.

For caches with size eviction, a nonquiet reload must use UpdateTask even when the weight is
unchanged and timestamps are within tolerance: the read buffer always records Access.HIT.
Access expiration without write expiration exposed this missing case in remap;
expiredRemap_recordsClimberMiss covers both expiration modes.

Steady contamination can cancel in cross-sample rate differences, but it biases the density
tier's within-sample regional ratio. In an earlier synthetic reuse study (maximum 512, reuse
gap equal to expiry), misattribution moved the converged window from 402 entries to 5 across
all seeds and both maintenance lags. Nearly all false hits were in main, and reloads rose from
4.2% to 13.6%. Those measurements concern the earlier reload-accounting fix, not the
same-weight remap case. No end-user hit-rate loss has been established for either case.

The simulator's product.Caffeine uses neither expiration nor reference values, so its gate
cannot exercise this path. Pins: BoundedLocalCacheTest.expiredReload_recordsClimberMiss,
expiredRemap_recordsClimberMiss, expiredPut_recordsClimberMiss, and put_recordsClimberHit.
They also check the sketch increment, which a quiet update would incorrectly suppress.

Random admission of rejected candidates. The TinyLFU admission filter admits 1 in 128
candidates of frequency 6 or more that would otherwise be rejected. This is the HashDoS
protection: an entry whose frequency hash collisions have raised, whether it is the victim or was
admitted as an inflated candidate, is eventually evicted rather than holding its place. It lets the
inflated entry leave the cache; it does not prevent its admission (see
ruled-out, Eviction and maintenance).

Climber review constraints

The current machine and its rejected alternatives are described in hill-climber
§4–6. Preserve these distinctions when changing related branches:

  • Recovery takes samples. A density sample is 4× maximum requests; typical log error of
    1.5–2 yields steps of 4–6% of maximum. Descending from an 80% window takes 13–16 samples,
    or 52–63× maximum requests. On ten frequency-optimal cells, 41 samples recovered 73% of
    an 80% plant, two samples recovered 2%, and 4× replay cut the deficit 3–8×. Density recovered
    roughly twice as much as reactive. Shortening the period needs a measurement of its jitter cost.
  • Reactive reversal substitutes for a floor. The reactive tier can reach a one-entry
    window; Reading.floor's 2% floor belongs only to density. A banded reactive law without a
    floor drove corda from 30.96 to 1.13. Any change that sustains its runs must revisit the floor.
  • Setpoint density has an accepted burst bias. Budgeted transfers can leave actual window
    occupancy above windowMaximum, inflating its density. Candidates still pass admit and
    LocalCacheSubject checks the bound at quiescence. Do not substitute occupancy without measuring.
  • References differ by purpose. Up-probes freeze probation density and its sample length
    at arm, scaling the baseline to the live sample length; down-probes compare one sample.
    Starvation-walk deviation pricing stays live. Audits freeze their rate reference and use a
    raw-sample streak plus one beat-base test. The rail's 3×deviation price is separate.
  • Each layer owns its state. Crash streaks and ladders belong to their walk. Non-crash
    endings clear only that layer's streak, so alternating crash/budget endings do not repeatedly
    get tolerance. Crashes never double the audit clock. Stillness alone advances that clock;
    moving samples decrement its run by one rather than reset it. Only interior-chosen directions
    update the alternation bit; a due audit pre-empts a refractory hold.
  • Walk exits use different bars. Audit crash abort is a level test against the arm's frozen
    rate, with 5pp depth capped at AUDIT_BAR_FRACTION of that rate. Reversal is a first-difference
    test, priced by AUDIT_BAR_FRACTION × max(baseHitRate, noiseBand) under the same absolute cap.
    That fraction is 0.15 × VETO_MARGIN_SCALE; widening the bars failed the measured controls.
    A floor-based walk can miss crossesBase through integer rounding; budget expiry gives the
    same FAILED price and full return command, pinned by
    WindowClimberTest.walkStep_floorBasedWalk_endsAtBudgetWithAFullUndo.
  • Anchor discard and metric reset stay paired on crash-scale shifts. The inherited EMA is
    about 80% old regime. A distant crash keeps the claim and reference. On-anchor resync runs
    even during return drain; planting waits for both a walk and its pending undo to finish.
    Symmetric claim aging lost 11.9pp on the ramp control; one-sided aging disarmed the rail.
    The pairing also holds inside a starvation walk, whose crash and reversal bars the reset widens
    to their cap: a census of the battery found seven such stand-downs, all on gains, which neither
    bar judges, and skipping the reset through 38 of them on seeded runs changed no hit rate.
  • An audit judges against the position it leaves. Walk.baseSmoothedRate, not Anchor.rate,
    is its baseline (ghostclaim 31.2 → 48.5; cp_w100 +2.0). Discarding a claim on a still swing
    loses retreat recovery (moat_h3000, −0.7 to −1.9 on all eight seeds); resetting the metric
    while keeping the claim sends the rail to a dead anchor (ghostclaim_p30 41.2 vs 49.2).
  • A return retests its frozen claim only at its original position. RETEST_SETTLE = 2
    avoided the seed losses of 1 and 3; ghostclaim_p35/p40 improved 31.7 → 33.9 and 31.4 → 33.5.
    Retest follows anchor.returning, rechecks isAt, and is cleared by both discard and plant.
    Clearing only on stand-down misses budget-expired returns and confirmed-position replants
    (#2002). Do not retest a claim after its anchor has moved onto a position the return never reached.
  • A reversed starvation confirm is a completed experiment. Escalate its ladder and hand
    to density with zero refractory; rewarding it restarted a recurring dither (668/881 confirms,
    bandtrap2 −4.4pp and an absorbing shallowmoat). Accepted costs include arc_DS1 −0.7,
    deadphase −0.2, and norank_rep_r6 seed 3 falling 41 → 20 while seven seeds were unchanged.
    The unlanded wedgeshift guard needs a holdout. Only deepest-commitment, audit-grade confirms
    park; other starvation confirms keep their density handoff.
  • Park and refractory scopes remain narrow. A parked audit covers its own walk's crash-scale
    move, but an external shift and undo arrival still judge the park (demoflood −1.9 if widened).
    Only a starvation undo arms the starvation refractory. Removing audit-undo rearming bought
    widepin +5.1, rep_r6 seed 3 +5.4, shallowmoat +1.1 for metronome −0.9,
    balloonflip −0.3 and cp_w050 −0.55.
  • Repeat confirms deepen rather than reward. Per-direction memory tracks the farthest
    confirmed window; starvation failure/crash clears it, audit endings and anchor discard do not
    (absolve_p8 27.95 → 46.3). A park's first audit follows its confirming direction only while
    the park and smoothed-rate guard still hold. Consume that exception at arm; later audits
    alternate. Raw-rate substitution fails absolve_p12; unguarded direction costs moat_h5000
    1.75pp and fails climbtrend_up / whisper_mod_p6.
  • Main-space experiments already priced the alternatives. Across 276 cells, plain-LRU main
    lost 93.1pp net (mean −0.337); SLRU won >1pp on 40 cells versus nine, 38/46 traces preferred it,
    and N=3 had no sign flips. Partial promotion gates were worse than either extreme (−25.7/
    −20.3pp tails), because sparse protected entries stay outside probation's victim pool.
    The 80/20 split and one-hit promotion beat alternative constants; a perfect per-cell oracle
    was only +0.21pp after the max-of-N noise floor, with winners changing across sizes on all
    46 traces. Wrong constants cost −6.16pp to −25.72pp. Probation stays near 19.8%; the density
    window borrows protected capacity over roughly [2%, 80.2%].

Audit confirms park without a parting steering step; starvation confirms normally do neither.
The room check and walk use Ladder.stride. Before changing floor-crossing arithmetic, use
seeded admission comparisons to distinguish a repair from a shifted probabilistic outcome.

Expiration

EXPIRE_TOLERANCE = 1 second. Expiration is a maximum lifetime,
not a minimum hold time. Like ScheduledExecutorService, the timing is never exact.
The tolerance applies to multiple per-entry timestamps:

  • writeTime reorder decisions when a write updates a live value
    (exceedsWriteTimeTolerance) — avoids write buffer saturation from rapid timer wheel
    rescheduling, ~4x throughput on write-heavy workloads. A write over an expired or collected
    value is a creation and restarts the write time.
  • accessTime updates on the read path — avoids cache-line true-sharing on a hot
    entry under expireAfterAccess. When the configured duration is <= tolerance
    the skip is bypassed so tiny expiration windows still behave exactly.
  • variableTime updates on the read path — the same saving under an Expiry, with the same
    bypass. Only an extension is skipped: a read that moves the deadline earlier is stored, since
    skipping it would serve the entry past the deadline the Expiry asked for. An unchanged
    deadline is not rewritten, even inside the bypass. Under Expiry.creating or writing every hit
    in an entry's last second keeps its deadline, and rewriting it was a CAS that cost ~45 ns per hit
    on 8 threads. The skip is pinned by
    BoundedLocalCacheTest.expireTolerance_variableTime_skipsForUnchangedShortDuration.

A put dates a new entry from its call, not its insertion. The create path reads the clock
when it builds the node, before data.putIfAbsent, and a retry after a node-monitor wait reuses
that node. An insertion that waits on another thread's bin (a same-key computation that returns
null, a bin neighbour's computation, a synchronous eviction listener in maintenance) publishes the
entry already aged by the wait, so it expires early by that much, or is a miss on the next read
and an EXPIRED notification when the wait outlasts its duration. That is not a defect: the
contract does not say when the clock starts, and either placement satisfies it. The caller obtained
the value before the call, so after an hour's wait the value is an hour older, and dating it from
the call does not overstate its freshness. A computation dates its result after the function
instead (#191), because the cache produced that value under the lock. The price of a back-dated
insert is in the order deques: appended at the tail with the oldest time, it makes the next
expireAfterWriteEntries scan relink every fresher live head before reaching it (one walk of the
live deque, once per such insert), and in access order it can linger unreadable behind live
heads until they expire, with size eviction reporting it as EXPIRED.

ASYNC_EXPIRY = ~220 years (Async.java). Computing futures get this sentinel
duration to prevent expiration during async computation. The isComputingAsync()
check tests both the isAsync flag AND whether the future is complete.

The sentinel also records that a load has not been accounted for, so every
read-extension path preserves it.
AsyncExpiry.expireAfterUpdate routes to the
user's expireAfterCreate when currentDuration > MAXIMUM_EXPIRY, which is how
handleCompletion's quiet replace tells a first load from an update. AsyncExpiry
cannot make that distinction on a read: it keys off getIfReady, so once the value
arrives it delegates and returns a real duration. The guard therefore belongs in the
cache, and both tryExpireAfterRead and expireAfterRead carry it (isAsync && currentDuration > MAXIMUM_EXPIRY returns without consulting the Expiry). The
window it covers is not the in-flight state, which AsyncExpiry handles: it is the
value arriving while the completion waits on the node lock a putIfAbsent is
holding. Consuming the sentinel there also hands the user's callback a 220-year
currentDuration, and an Expiry that returns what it was given, Expiry.creating
among them, then pins the entry for that span instead of expiring it after its
configured duration.

The completion finalizes only what the insertion deferred. handleCompletion
finalizes an entry with a same-instance quiet replace, which lands on the update path.
For a future that was in flight when it was inserted that is the entry's first and only
evaluation, since the install stored the sentinel and AsyncExpiry routes it back to
expireAfterCreate. This includes an incomplete future replacing a ready value: it begins a
pending lifecycle, then receives creation expiry on materialization. AsyncTest.asyncExpiry_pending
pins a pending update discarding a real prior duration; asyncExpiry_completion pins the
subsequent creation callback. A future that was already complete when inserted was weighed and
dated by that write, so finalizing it again charged the creation as an update: the user's
expireAfterCreate ran at the install and their expireAfterUpdate a moment later, and
the update's duration is the one the entry kept. Measured with an Expiry of 1h on create
and 1m on update, put(k, completedFuture(v)) and a loader returning a completed future
both produced a 1m entry where the synchronous cache and an in-flight future produced 1h.
So the callers pass whether the insertion deferred its accounting and the completion skips
the replace when it did not. A ready future replacing a live entry likewise takes one
update rather than two. Reachability is not exotic: any loader that can answer without I/O
returns a completed future.

Three things about the shape are load-bearing. The readiness is read before the store,
because a future observed as ready before the install was ready when the install evaluated
it, whereas one observed afterwards may have completed in between, and skipping there
leaves the entry holding weight 0 and the sentinel permanently (measured: moving the read
after the put stranded 2,950 of 20,000 entries against a completion race, versus 0 of
500,000 with the read where it is). The decision cannot live in AsyncExpiry: its
expireAfterUpdate receives the key, the new value and the current duration, so a
completion's replace(k, f, f) and a genuine put of an equal ready future are identical
from inside it; the sentinel test only works for the deferred case because the entry still
carries the mark. And it must not be AsyncExpiry.expireAfterCreate deferring every
creation
— the synchronous view's asMap() compute family installs completed futures
with no completion handler at all, so deferring strands them at the sentinel and the user's
expireAfterCreate is never called (9,013 failures across the narrowed async matrix when
tried).

The flag is conservative rather than exact, so the write re-tests the mark. Reading
before the store rules out one direction only. A future observed as ready was ready at the
install, but one observed as unready may still complete before the install evaluates it, and
the stale flag then charges as an update a creation the install already accounted for.
Measured on the shipped defaults with an ordinary future and an Integer key, four to nine
puts in every 200,000 take that path, and a key whose hashCode completes the future makes
it deterministic, yielding a 59s entry for the 1h/1m Expiry above. No read point fixes it,
since readiness is monotonic and neither side of the store is the observation the install
used, and narrowing the window is not a repair. So replace asks the entry instead. A quiet
write, which only a completion performs, preserves the variable time when the entry no
longer carries the sentinel, and still finalizes the weight and the write time. Nothing else
can have cleared the mark, because the two read-extension paths above return early on it.

MAXIMUM_EXPIRY = ~150 years (Long.MAX_VALUE >> 1). User-provided variable expiration
durations are clamped to prevent nanoTime arithmetic overflow; fixed expireAfterWrite and
expireAfterAccess durations are stored as given (see Timing and arithmetic in
ruled-out for a near-MAX one under a scheduler). now + ASYNC_EXPIRY
overflows to negative after ~73 years of JVM uptime, but this is within the
documented assumption that JVM instances don't run for 73+ years continuously.

accessTime uses opaque write, not CAS. CAS on every read would cause contention
storms on hot entries. Backward movement only causes benign early expiration, which
is acceptable. Variable time CAS (casVariableTime) is justified because
expireAfterRead can change duration arbitrarily.

Read-path expiry extension can briefly resurrect a just-expired entry — accepted.
A reader that observed an entry live and then extends it (tryExpireAfterRead's
casVariableTime for variable expiry, or setAccessTime for expireAfterAccess) can land
the extension just after the entry crossed its boundary, leaving it visible slightly later than
expiry. This is inherent to lock-free read-extension over lazy expiration: the expired entry
keeps its timestamp until maintenance removes it, so the CAS — which only checks the field is
unchanged — cannot reject an expired entry, and any fresh-clock guard before the write still
races a context switch between the read and the write; only a read-path lock (rejected) would
close it. The window is a few instructions for a normal Expiry callback; a wide one requires a
slow expireAfterRead (callback misuse, like a slow Weigher). So "never visible later than
expiry" is best-effort for read-extension — the over-stay is bounded by one duration and
self-heals on the next maintenance. Don't add a fresh-clock re-check guard for this
over-stay — it still races a context switch and can't reject an already-expired entry.
(Distinct from the node.getValue() == value value-identity check that casVariableTime
does carry: it stops a read duration rebinding onto a replaced value and is load-bearing;
keep it. It covers a replacement during the expireAfterRead callback because a loadLoadFence
orders the deadline the CAS expects before the value it checks, pairing with setValue's
storeStoreFence as hasExpired's fence does. Without it, aarch64 satisfied the value load first,
so a reader saw the replacement's deadline with the old value, passed the check, and gave the
replacement the old value's read duration, later than its own when that duration is longer
(ExpiredReadTear.ReadExtension). It does not cover a replacement in the few instructions between
the check and the CAS: an update that keeps the exact deadline, as an expireAfterUpdate
returning currentDuration does, leaves the timestamp the CAS compares unchanged, so that
replacement takes the read's duration, a same-deadline rebind bounded by one duration like the
over-stay above. The adapter accepts the same race for its native timer.)

Bulk reads evaluate expiry at a single scan-wide now, by design. getAllPresent
(and containsValue) read expirationTicker() once and reuse that now for every
element's hasExpired/setAccessTime/tryExpireAfterRead. This gives the batch
internal consistency — every key judged at one instant, a point-in-time snapshot. A key
late in a long scan can therefore be returned present just after a concurrent single-key
get (fresh now) reported it expired (a LATE-direction over-stay). That's accepted
best-effort: a concurrent single-key read can always disagree with a bulk read on a
boundary entry under lock-free expiration, the over-stay self-heals on the next
maintenance/access, and it lasts no longer than the scan (measured at up to about 2 ms for
10,000 keys and 140 ms for 1,000,000), inside EXPIRE_TOLERANCE unless a user
Expiry.expireAfterRead callback is slow (callback misuse). Don't "fix" this by re-reading
the ticker per element — it judges keys of the same call at different instants (a downgrade
of the snapshot) and adds a nanoTime read per key on the hot path.

A removal's cause is attributed at the instant that removal is performed, and clear() is not
an exception to that.
clear() reads the ticker once and passes that now to removeNode,
which amortizes the read across the entries it removes under the eviction lock and calls a
user-supplied Ticker once rather than once per entry while holding it. It is a cost decision,
not a point-in-time guarantee, and it cannot become one: the sweep breaks out as soon as
writeBuffer.size() reaches WRITE_BUFFER_MAX / 2, and every straggler goes through the public
per-key remove(key), which reads its own clock. Measured on 200,000 entries with the deadline
crossed after the sweep began, one writer running: the prefix covered by the captured clock was 5
and 18 entries in two of three trials, with the remaining ~199,990 attributed EXPIRED by their
own reads. Uncontended, the same call reported all 200,000 EXPLICIT. Across methods the gap is
the same thing from outside: with a ticker stepping 1 ms per read and the deadline set midway
through, invalidateAll() reported 1,000 EXPLICIT where invalidateAll(keys), a loop of
independent remove(key) calls, reported 250 EXPLICIT and 750 EXPIRED. So cause counts are
not comparable between the two bulk calls and are not stable within one clear(). The consequence
is confined to the removal cause and evictionCount, both best-effort. Don't read the captured
now as a snapshot: making it one needs an internal removal variant carrying it through the
straggler path, and dropping it puts a per-entry ticker call back under the eviction lock.

A hit probes the value future's readiness only where the answer is consumed. hasExpired
is timestamp-only, so a reader probes isComputingAsync solely on an expired verdict, and the
successful-read blocks in getIfPresent and computeIfAbsent's optimistic hit test
expiresAfterRead() (access or variable) before their own probe, since setAccessTime and
tryExpireAfterRead early-return otherwise. Async.getIfReady calls isDone,
isCompletedExceptionally and join, each an acquire load of CompletableFuture.result that
the JIT cannot elide or coalesce, so a maximumSize-only async hit was paying six of them for
a result no branch could read. Measured on AsyncGetPutBenchmark.read_only (M3 Max, JDK 26, 8
threads, 3 forks, ABA): 238–246M ops/s before, 370–384M after, +53%; an acquire load is
ldar on arm64, so x86 should gain less. The probe count per healthy hit is 0 with no
expiration or with expire-after-write, 1 for expire-after-access and 2 for variable expiry (the
extra one is AsyncExpiry.expireAfterRead's own getIfReady), pinned by
BoundedLocalCacheTest.getIfPresent_readinessProbes. Don't cache the first probe's answer to
save the second: a future can complete, or be obtruded, between the two observations.

The expiry read protocol pairs timestamp-before-value reads with value-before-timestamp
writes.
A lock-free read must never return a value whose EXPIRED notification a concurrent
rewrite already fired. hasExpired is therefore timestamp-only and every lock-free reader
consults it before loading the value; a loadLoadFence at the end of hasExpired (repeated
before tryExpireAfterRead's value-identity check) and a storeStoreFence in the generated
setValue hold both orders on weak memory, and put
stores the value before setWriteTime (the other rewrite sites already did). A reader that
observes a fresh timestamp therefore observes the rewritten value, closing the LATE direction.
The EARLY direction (stale timestamp with the fresh value) is one spurious miss that
linearizes between the old value's expiry and the rewrite, a 64-bit timestamp read being
atomic, unless a reader already returned the fresh value: a writer preempted between its value
and timestamp stores across the old deadline shows the value, a miss, then the value again. The
other non-linearizable windows are the read-extension resurrection and same-deadline rebind
above, the bulk single-now scans, a read preempted after its clock sample (it judges a value
published later against that clock, so it can return a replacement past its deadline, even one
created expired), a compute whose function outlasts its input's remaining lifetime (a
concurrent read reports the input expired before the result is stamped with a fresh clock),
and a put or replace over a live entry, which judges liveness under the node monitor before
calling expireAfterUpdate (a read crossing the old deadline in between reports the key absent
while the write returns or replaces that value with REPLACED; a few instructions for fixed
expiry, the callback's run time for variable expiry).
Each is bounded; closing them needs a read-path lock or a second ticker read per read, both
rejected above, or applying the function twice, which the compute contract forbids. A caller
acting on an expired verdict must exempt an in-flight async load by
probing isComputingAsync against a value loaded after hasExpired returns. writeTime's
setter is opaque (it was plain, a formal tearing gap on 32-bit VMs), and the swap in put
shifts a nanosecond refreshIfNeeded window from suppressing a refresh to launching one
wasted best-effort reload, absorbed by the reservation re-check and the ABA commit guards.
Pinned by ExpirationFrayTest.getIfPresent_expiringRewrite_neverReturnsExpiredValue (failed
on the first iteration before the reorder) and the ExpiredReadTear jcstress test, whose
old-modes model reproduced the tear at 0.21% of samples on aarch64 while the fixed pairing
produced zero across a tough-mode soak. The measured price on the M3 Max is nil: the reader
fence sits within run-to-run drift on HotEntryBenchmark's expiring configs, and the writer
fence is below GetPutBenchmark.write_only's noise floor (interleaved baseline/fixed forks;
that cell drifts more between forks than any fence effect, so judge it with paired runs, not a
plain before/after). Don't move a reader's getValue() above its hasExpired call, and don't
reorder a writer's setValue below its timestamp stores.

Expiration eviction is capped at EXPIRATION_THRESHOLD (1000) entries per maintenance
cycle.
expireAfterAccessEntries (shared across its window/probation/protected deques),
expireAfterWriteEntries, and the variable TimerWheel.advance each evict at most this
many entries, then set PROCESSING_TO_REQUIRED so rescheduleCleanUpIfIncomplete re-arms
and the backlog drains across subsequent cycles — mirroring drainWriteBuffer's cap and the
climber's QUEUE_TRANSFER_THRESHOLD. The cap is high enough that normal traffic never
reaches it; it only bounds the abnormal spike where a cache with no Scheduler goes idle,
lets a large population expire logically, then returns to traffic — one maintenance cycle
would otherwise evict the whole backlog under evictionLock, stalling any writer that
overflows the write buffer and assists (post "Assist maintenance directly when the write buffer is full"). The work isn't reduced, only
sliced, and since eviction runs async by default the slicing keeps a single cycle from
blocking a thread too long. The timer wheel rewinds nanos to previousTimeNanos when
its budget is exhausted and restores the unprocessed bucket remainder, so the next advance
reprocesses the backlog. Empty buckets rescan cheaply, but a large advance can cause
rescheduled survivors to be revisited after a rewind. The eviction check keeps non-expired
nodes from being evicted early. A capped cycle can briefly leave expired entries counting
toward weightedSize, so a same-cycle
evictEntries could pick a live victim over an expired one; negligible — frequency-based
selection favors the cold expired entries and it self-corrects next cycle. Don't flag the
cap as under-expiring, and don't remove the PROCESSING_TO_REQUIRED re-arm.

The expiration scans hold no entry across a callback except their captured tail. evictEntry
delivers the removal notification; under executor(Runnable::run) or the rejection fallback the
listener runs inline, and a RemovalListener is permitted to modify the cache, so a nested
maintenance() can unlink, transfer, or reorder any entry before the scan continues. Reentrancy
is not a supported style and cannot be detected or refused, so the requirement is only that it
not corrupt or wedge the scan. After evictEntry a scan therefore resumes from its deque's head
rather than the successor it read before the callback, and it repositions with a bare
moveToBack, since the entry it holds is always its deque's head. Without a nested cycle the
head is that successor, because each step removes the head or moves it behind last; an entry
that evictEntry resurrects is examined again. Resuming on the held successor fails three ways:

  • An unlinked entry has both links null, so unlink runs first = next; last = prev and
    discards the whole deque, leaving every entry in it live in data and in no eviction queue.
  • A transferred entry carries the links of the deque that now holds it, so unlink splices
    that deque and assigns one of its nodes as this deque's first/last. The window, probation,
    and protected deques share one pair of link fields on the node, so
    AccessOrderDeque.contains answers "linked somewhere", not "linked here".
  • A successor moved behind last (a listener that renews entries and runs cleanUp()) leaves
    every entry the walk meets fresher than last, so the walk reorders them forever.
    Guarding the reposition with contains and the queue type covers only the first two. Counting
    nested cycles would need a field and would see only relinks made by maintenance(). Pinned by
    BoundedLocalCacheTest.maintenance_recursive_accessOrder / _writeOrder,
    expireAfterAccess_promotedDuringScan, and
    ExpirationTest.expire_removalListener_renewsScanSuccessors, and searched by
    ReentrancyFuzzer, which found the relinked-successor loop within a minute of fuzzing from an
    empty corpus. Don't resume a scan on an entry read before evictEntry.

Expiration scans re-check their captured tail after callbacks. A scan ends once it has
examined last, and reorders do not consume the eviction budget, so a nested cycle that removes
or transfers last leaves entries cycling indefinitely under evictionLock. While last stays
linked the walk reaches it, because between evictions every step moves one entry from in front of
last to behind it. After evictEntry, a scan with work remaining checks tail membership, and
for access order the tail's queue type as well, since membership answers only "linked somewhere";
the write-order links are exclusive to the one write-order deque, so expireAfterWriteEntries
needs only membership. If absent, it exhausts the budget so PROCESSING_TO_REQUIRED starts a
fresh scan. A scan that reached its own tail needs no re-arm. The timer wheel instead uses its
field-backed pending sentinel and rejects nested advances with advancing. Pins:
maintenance_recursive_accessOrder_removedTail and maintenance_recursive_writeOrder_removedTail.

The wheel budget counts only evictions, never the cascade (rescheduling a non-expired
node to a finer level) — mirroring the deque caps, which count evictEntry but not the
moveToBack reorder. Cascading a densely-populated coarse bucket is O(n) and not sliced,
but it's accepted: an O(1) pointer splice with no CHM write or listener (~1–2% of an
eviction), done at most once per node per advance, and a given coarse bucket cascades only
~once per its multi-day span. The only trigger is a whole cache landing in one coarse bucket
— entries scheduled past the ~6.5-day overflow span (a JVM won't outlive it) or bulk-loaded
at startup with periodic reload (a cache anti-pattern). Debated and declined (2026-07-03):
capping it bounds a lock-hold no worse than one already-accepted post-cap eviction cycle
(the equivalent threshold is ~50–100K), for the cost of new concurrently-mutated state in
the wheel. Critically, a cascade cap must never reuse the eviction rewind: the rewound
re-advance re-scans the finer levels the cascaded nodes moved to and re-cascades them,
starving the eviction drain (or livelocking). Evictions can rewind only because an evicted
node is gone, so the re-traversal skips it. Don't cap cascades via the rewind.

If ever revisited (needs a repro — a coarse bucket with ~10^6 live entries pinning
evictionLock past the eviction cap while a writer-assist blocks):
a safe cascade cap needs
a forward-carried backlog, not a rewind — the wheel's analog of the climber's adjustment
carry-over. On hitting the budget, stitch the unprocessed remainder (the current bucket's tail
plus the un-visited buckets/levels, reusing the nodes' existing variable-order links) into a
backlog list held on the wheel, and let nanos advance normally. The next advance flushes
that backlog first (evict the due, reschedule the rest), then resumes the level walk — forward
progress, nothing re-scanned. The hard part is lifecycle reconciliation between advances: a
backlogged node that gets descheduled unlinks transparently (same links), but reschedule
must move it out of the backlog and back into a wheel bucket, and the flush must tolerate the
list shrinking under it. That concurrent-mutation surface in the codebase's most intricate
structure is why it's deferred, not the mechanism itself.

Collected references are drained at REFERENCE_THRESHOLD (1000) per queue per maintenance
cycle
, then PROCESSING_TO_REQUIRED re-arms the backlog like every other budget here. The
budget counts polls rather than evictions, so a run of stale references cannot extend the
hold, and each queue gets its own so both make progress. This is the expiration cap's shape
with a different trigger: a garbage collection clears an arbitrary number of keys or values at
once, with no user action to blame it on, and the drain then ran to exhaustion under
evictionLock. Measured (M3 Max, JDK 26, 1M weak keys cleared and enqueued at once): one
cleanUp() held the lock 130–190 ms; capped it is 0.12–0.5 ms per cycle over 1000 cycles,
and a concurrent writer completed 2.8k–81k operations during the backlog where it had completed
~1,100. The cost is ~45% more total drain time from the extra cycles. What the cap does not
do is bound a concurrent operation's wait
: rescheduleCleanUpIfIncomplete re-submits
immediately on the common pool and evictionLock is not fair, so the drainer barges back in
and the backlog as a whole still holds the lock (a probe acquiring and releasing every 100 µs
got in 2–92 times over the backlog, capped or not). That residual is shared with the expiration
budget and is a lock-fairness question, not a reason to drop the cap or to widen it.

The maintenance consumer never waits for a producer's publication. MpscGrowableArrayQueue
publishes in two steps, a CAS of the producer index and then a release store of the element, and
the strong poll() spins on that element when the index says the queue is non-empty. Both
consumers (drainWriteBuffer and clear) hold evictionLock, so a producer descheduled
between those two instructions — a container throttling a thread, say — stalled every other
policy operation for its whole pause and burned a core doing it. Both use relaxedPoll(), which
returns null instead, and JCTools offers exactly that pairing for a consumer that must not
block. Nothing is stranded: every production offer is afterWrite's, and scheduleAfterWrite
runs after offer returns, so the passed-over task is re-armed by its own producer (IDLE →
REQUIRED, or PROCESSING_TO_IDLE → PROCESSING_TO_REQUIRED, which makes maintenance's final CAS
fail). clear is safe for a second reason: it already abandons the buffer once a concurrent
writer refills it past WRITE_BUFFER_MAX / 2, AddTask links only if (isAlive), deque
removal is contains-guarded, and makeDead takes the weight from the node rather than from the
policy precisely because an update may still be buffered, so a task that outlives the entry it
describes costs a weight swing that telescopes back to zero. It ends with
rescheduleCleanUpIfIncomplete, which is what carries the remainder forward.

Exception Handling

Checked-exception conversion and the listener guards restore interruption. Kotlin, Scala,
and Groovy callbacks can throw InterruptedException through Function, BiFunction,
CacheLoader, or RemovalListener; checked exceptions are a javac rule, not a JVM constraint.
Caffeine.toUnchecked rethrows Error, returns RuntimeException by identity, restores the
current thread's interrupt status for InterruptedException (JDK interruptible waits clear it
when throwing), and otherwise wraps in CompletionException. The default
CacheLoader.asyncLoad converts inside its supplier, on whichever thread the executor uses.
Conversion is used by loader chains, catch-commit-rethrow for COLLECTED/EXPIRED recomputation,
and AsyncBulkCompleter. Absent-key paths and UnboundedLocalCache propagate unchanged and need
no conversion-side repair. The eviction and removal listener guards log the failure instead of
converting it, so they restore the status before logging: the eviction listener runs on the
thread doing the eviction, often the caller's own put or compute, and a removal listener
runs on the caller under a caller-runs executor or a rejected submission. Later interruptible
calls on that thread then fail at once, including the listener's for the next entry evicted in
the same pass; that is the interrupt taking effect, not a cascade to contain. The statistics,
scheduler, and future-completion guards do not restore, since the callbacks they wrap are not
expected to wait.

Catch-commit-rethrow pattern in doComputeIfAbsent and remap. Both catch
Throwable, not just RuntimeException. When user code
(mapping function, weigher, expiry) throws after notifyEviction was called, the
phantom eviction is made real: the node is retired, null is returned to CHM, and the
exception is deferred past cleanup.

notifyEviction is called BEFORE user code, not after. This can't be reordered —
it preserves linearizability for resource-based listeners (e.g., file delete before
recreate). The catch-commit-rethrow pattern handles the case where user code then
throws.

wasEvicted flag in remap: boolean wasEvicted = (ctx.cause != null) is captured
BEFORE the try block because ctx.cause can change from null to REPLACED
inside the try. The catch block uses !wasEvicted to distinguish eviction-path
exceptions (commit+defer) from non-eviction exceptions (immediate rethrow).

remap's no-op exit records itself in ComputeContext.unmodified; the post-write dispatch
must not re-derive it.
The in-lambda short-circuit takes the exit on four conditions
(preserveTimestamps, a same-instance return, and no removal cause); the dispatch used to test
only the hint, so a hinted call that did mutate committed the mutation and then skipped its
AddTask/UpdateTask. A hinted update left weightedSize short by the delta forever, and a
hinted create on an absent key (the absent branch has no short-circuit at all) installed a node
linked into no deque and counted in no weight, which makeDead later subtracted from
weightedSize anyway, relaxing the bound by that much per cycle. Not reachable from the public
API: every caller that sets preserveTimestamps returns the instance it was handed, and each
insert branch returns before the hint is set. It is latent because RemapHints is
package-private, so nothing warns a future caller. Pinned by
BoundedLocalCacheTest.remap_preserveTimestamps_newValueDiffers_publishesTheUpdate and
remap_preserveTimestamps_absentCreate_publishesTheAddition. Don't restore a second copy of the
predicate; the exit that skips the work is the one that says so.

remap same-instance return is a setter no-op, NOT a metadata no-op. When a
user compute/merge remapping function returns the same value instance as the
current value, setValue is skipped, but weight, accessTime, variableTime,
and writeTime still update. This is intentional: compute is a mutation API,
so a same-value return is still treated as a write for expiration, refresh eligibility,
and eviction policy, subject to timestamp tolerance. The only documented full no-op is the
explicit preserveTimestamps path. A reader expecting compute(k, (k, v) -> v) to leave eviction
ordering and expiration deadlines unchanged would be surprised; the source does not call this
out, so this entry is the canonical place the behavior is documented (preferred over a source
comment).

Value-bearing user callbacks propagate; fire-and-forget callbacks are guarded.
Ticker, Weigher, Expiry, and loaders return information the cache needs, with no safe
default. Stats, scheduling, and listener wrappers can instead return an empty/default result.
Do not add containment for broken equality, hostile futures, or JVM errors. The precedent is
to fix the user component (as Quarkus did for its broken future), not add defensive code or a
must-not-throw note to Ticker.

A value-bearing throw can land after a commit. Examples include AddTask / UpdateTask
updating policy totals before a ticker read, and refresh/async prologues reading statsTicker
before cleanup. Such failures can skew accounting or strand tokens/proxies; those mechanisms
are accepted under this boundary, not denied. Broken key equality can cost the size bound. A
hashCode that throws in evictEntry during an UpdateTask skips that update's weightedSize
delta, relaxing the bound by the delta for the cache's lifetime; one that throws when admission
compares frequencies stops size eviction, logging each failed cycle, until it stops throwing,
because every removal hashes the key too. Containment often invokes the same broken component.
Preserve existing targeted cleanup, including refresh completion's own-token catch; the boundary
is not a reason to remove it.

Concurrent standard-future obtrusion is the supported exception. Between readiness checks
and join, an obtrudeException can make Async.getIfReady throw. A plain-future stress run
produced 3.5M throws in 1.28B query rounds. Its narrow CancellationException /
CompletionException catch now returns null as the method promises, including to maintenance
under evictionLock; other hostile-subclass exceptions propagate. Completion handlers are
one-shot, so obtruding after success can leave a physical entry that queries filter. That is
accepted, although the entry never expires: evictEntry reads the failed future as in flight and
re-dates it with the async sentinel, so only size eviction or an explicit removal reclaims it.
AsyncBulkCompleter.failProxies removes before obtruding.

A maintenance throw defers buffered work; it does not drop it. The final CAS can settle
PROCESSING_TO_IDLE → IDLE on a throw, leaving unprocessed tasks in the buffer. Later writes
re-arm, and a full buffer forces inline assist. Do not force REQUIRED or add a re-arm to
performCleanUp's throw path. The inline fallback's own write task is different: it must run
in maintenance's finally, preserving the repair for that task's former loss.

Executor rejection and lock contention need different epilogues.
scheduleDrainBuffers deliberately does not follow rejection-fallback maintenance with
rescheduleCleanUpIfIncomplete. The rejecting executor cannot use the common-pool arm; a
pacer would submit to the same broken executor, where its fire-time rejection is swallowed.
Recovery relies on later activity, as documented for discarded/never-run
tasks. A drain that merely bounces off clear's held lock still has a healthy executor and
must reschedule. Do not generalize the rejection exception to that case.

Buffers

MpscGrowableArrayQueue is a shaded JCTools port and is not wrap-safe; that is accepted. At
pIndex == cIndex == 2^63 - 256 the "is there room" sum overflows and a resize is selected on an
empty maximum chunk, stranding the odd producer-index marker so every producer spins at 100% CPU. It
needs 2^62 offers on one never-reset queue (millennia at a measured 10-20M put/sec), and upstream
master is identical. Don't harden it: (pIndex - cIndex) < bufferCapacity does not suffice
(producerLimit stores the same wrapping sum), and rolling the index back after producerBuffer = newBuffer strands the consumer, which is worse than the spin.

StripedBuffer.offer treats FULL as success and only expands on FAILED. A failed CAS is
a contention hint; weak CAS can also fail spuriously. A full buffer only means the drain is behind,
which striping does not fix. Routing FULL into expandOrRetry would let one thread's
routinely-full buffer grow the table and allocate stripes nobody contends for. So a FULL home
stripe returns FULL without probing a sibling, which is what makes a stalled stripe skip that
thread's reads until it drains. Both are intended: the FULL return is the signal that tells
afterRead to drain. Don't make FULL probe for another slot.

A thread's starting stripe never moves. The probe is re-derived from the thread id on every
offer, so the incremental hashing in expandOrRetry varies the slot only within one call. This is
forced, not chosen: ThreadLocalRandom.getProbe/advanceProbe, which Striped64 uses to
permanently move a colliding thread, are package-private to java.util.concurrent. Don't "restore"
the convergence search; the only alternative is a ThreadLocal on the read hot path.

References

The keyReference in a weak/soft value reference is read and written opaquely. The
field is set during construction and, under synchronized(node), mutated to a sentinel
value (RETIRED_*_KEY / DEAD_*_KEY) when the node is retired or dies. Lock-free
readers tolerate the resulting staleness window as weakly-consistent observation, but a
strong-key weak/soft-value node keeps its key inside that reference, so getKey() and
isAlive() read the field independently: with plain reads, a reader that observed the
sentinel and then observed the older key would judge a retired node alive while handing
out an internal sentinel as its key. Opaque access forbids exactly that, since opaque
operations on one variable are coherent, and it costs nothing at runtime because the
constraint binds the compiler rather than the hardware (opaque loads compile to ordinary
loads on x86 and aarch64 alike). The field stays non-volatile and the constructor's store
stays plain, the object not yet being published; only the accessors are opaque. A jcstress
probe was written to arbitrate first and found no violation over both weak and soft values
while exercising the window (it observed the monotonic key-then-retired interleaving),
so it was discarded rather than kept as a pin: the hazard is a legal compiler
transformation that a green run cannot refute.

In setValue, a new WeakValueReference is installed via
setRelease followed by VarHandle.storeStoreFence(), then the old reference is
cleared via ref.clear(). The fence prevents the old reference's clear() from
being reordered before the publication of the new reference. Without the fence, a
reader that re-reads the same reference and observes a cleared referent cannot
distinguish "the clear was already committed" from "the clear's store buffer is
ahead of the new reference's publication" — breaking the getValue re-check loop
invariant. The reader's loadLoadFence pairs with that ordering through the observed clear;
this does not require first acquiring the replacement reference. See
RA fences and their specializations.
setRelease alone orders the new reference's constructor writes before
the publication, but does not constrain the subsequent ref.clear() against any
racing reader (#1820, confirmed on aarch64 M3 Max via JCStress IntermittentNull test).

In the constructor, a plain VALUE.set is used since the object itself is not yet
published. Strong-value setters also use a store-store fence, to order the value before
subsequent timestamp stores as required by the expiry read protocol;
they have no reference-clearing operation to order.

Weigher.boundedWeigher wraps all user weighers and enforces weight >= 0 at
runtime via requireArgument.

Concurrency

maximum and weightedSize have a plain reader and an acquire reader, and no public-facing path
may use the plain one.
AddMaximum.addPlainAndAcquireField emits maximum as a VarHandle.get,
which guarantees bitwise atomicity only up to 32 bits, so a plain read of the 64-bit field can tear
against setMaximum's release write on a 32-bit JVM. The plain reader is for callers already
holding evictionLock; everything reachable from the public API takes
maximumAcquire/weightedSizeAcquire. There is no way to assert an access mode in a test, so this
is a review-time invariant: check the call site's lock state, not the accessor's name. The three
duration fields need no such care, since addAcquireReleaseField emits only an acquire reader.

No debug-mode assertions. Runtime invariant assertions are impractical for
concurrent code — too hard to assert on a running system. Correctness relies on
testing (Fray, LinCheck, JCStress) and static analysis (ErrorProne @GuardedBy).

nanoTime is monotonic. Per JVM spec, System.nanoTime() is monotonic. Backward
movement would be a JVM bug, not a cache issue.

scheduleAfterWrite's IDLE arm retries its failed swap rather than acting on what it read.
The status can advance between the opaque read and the compare-and-swap, so a writer that read
IDLE may find PROCESSING_TO_IDLE. Dropping the failed swap and calling scheduleDrainBuffers
anyway leaves that write with no driver: the guard sees a drain in flight and returns, while the
drain has already passed the task's slot, and its exit swap then settles the machine at IDLE
with the task still buffered. Retrying against the observed status routes to the processing arm,
which converts the exit to PROCESSING_TO_REQUIRED — the same shape the processing arm has always
used, and why the two arms now look alike. The end state it prevents was benign (the task is not
lost; the next write, a cleanUp, or a read that fills a stripe drains it, which is the deferral
already priced for the maintenance-throw path and the WRITE_BUFFER_MAX cap) and reaching it
needs the entry store's visibility to lag the drain's loads, so this is a tidiness fix, not a
correctness one — recorded because three audit runs have now reached for this machine. IDLE is
written in exactly one place, the value-checked casDrainStatus(PROCESSING_TO_IDLE, IDLE), so no
REQUIRED is ever swallowed. Don't collapse the arm back to an unconditional swap. Pinned by
BoundedLocalCacheTest.scheduleAfterWrite_staleIdle_retriesAgainstTheObservedStatus.

Read recording starts with the frequency sketch. A fastpath cache (bounded, strong keys and
values, no access expiration) starts with the disabled read buffer, so its reads skip the eviction
policy until the sketch is initialized: at half occupancy, when presized by initialCapacity, or
when setMaximum makes the cache half full. Each of those sites calls recordReads(), which swaps
in a BoundedBuffer under the eviction lock. A test that initializes the sketch directly must call
recordReads() as well; BoundedLocalCacheTest.fastpath, fastpath_presized and
fastpath_setMaximum pin the three sites.

readBuffer is volatile, so the swap is a safe publication. A plain field is also safe: a new
buffer's constructor writes nothing, and JLS §17.4.4 makes default values visible to every thread.
But that holds only while StripedBuffer stays stateless at construction, plain reads have no
guarantee of ever observing the swap, and race detectors flag it (a TSAN report, #2020). An
acquire read, as drainStatus uses, would also suffice, but C2 compiles the two identically on
every port except PPC64, the only one where a volatile load gets a leading sync. The volatile
read was kept because the JLS memory model formally defines it, while the other modes lack a full
formal specification
(Lea, JDK 9 memory order modes). On an Apple M3
Max, plain and volatile measured within 3% of each other on JDK 11 and 27 in
GetPutBenchmark.read_only, and on 11, 26 and 27 in a harness matching it. A JDK 26 JMH run with
release-26 bytecode showed volatile 24% faster under identical inlining; it did not reproduce on
11 or 27, so it is a C2 layout artifact rather than a cheaper read.

The hit path offers to the buffer without testing the sketch. The skipReadBuffer() check it
replaced loaded sketch.table, which shares a cache line with size, and the maintenance thread
writes size for nearly every drained read. On a read-only JMH sweep of a presized 65,536-entry
cache (Apple M3 Max), removing the check took a hit from 11.65 to 9.99 ns at one thread and from
11.13 to 7.00 ns per thread at eight, and a cache under half full was no slower. Two alternatives
were measured and rejected: padding size 128 bytes away from table recovered less (12.21 to
10.35 and 10.28 to 9.02 ns/op) at 128 bytes per cache, and keeping the count in the table's
trailing slot recovered a third of the gain at one thread and none at eight, for a reason not
established.

The maximums and weighted sizes (maximum, weightedSize, and the window and protected pairs) are
padded from the fields that a hit loads, because the maintenance thread writes them for drained
writes, promotions and climber steps. Unpadded, they shared a 128-byte line with an expiring or
refreshing cache's ticker and duration (expiresAfter*Nanos, refreshAfterWriteNanos) at every
alignment, and with the stats counter and the base class's data, nodeFactory and readBuffer
at others; a line that also held drainStatus, which every drain writes anyway, cost little. On an
Apple M3 Max with eight readers and a writer doing 500k put+invalidate/s, averaged over the cache's
16 alignments in a line on JDK 27, padding cut the mean time per hit (eight readers over their
throughput) from 28.1 to 24.6 ns for a size and expire-after-access cache, from 23.1 to 21.8 ns
with stats, and from 17.1 to 16.0 ns for a size-only cache, whose put+invalidate fell from 399 to
329 ns. JDK 25 agreed, except that the size-only hit was within noise there.

So AddMaximum.supertypes puts a class of 15 longs (120 bytes) before the class that adds the six
fields, and another between that class and its expiring and refreshing subclasses: 120 bytes per
cache bounded by size or weight, 240 with expiration or refresh. The class that adds them holds only
those longs, and its climber, sketch and access-order deques go in a class before the first pad,
because HotSpot (JDK 15 and later) places a subclass's fields in its superclass's alignment gaps:
those references leave a 4-byte gap, and a subclass's ticker filled it beside the counters. Each
pad is a class of its own because a class's own fields of one size may be laid out in any order (JDK
17 and 21 sort them with the platform's qsort, which on macOS swaps the first and last of the run;
JDK 24 made the sort stable, JDK-8341471). A size-only cache's end is left unpadded: the object
after it is the collector's choice (the eviction lock at allocation, the write buffer's leading pad
after a young collection on G1 or Parallel), and the gains were the same with either beside it.
Moving the six fields into a separate object would cost about 20 bytes, but that object shares its
line with whatever the collector places beside it, which JOL cannot check. The JDK pads its own
separate counter objects (ConcurrentHashMap.CounterCell, Striped64.Cell) for that reason.
BoundedLocalCacheTest.evictionFields_ownCacheLines checks the layout with JOL; only JDK 15 and
later fill gaps, so only they can fail it.

Node State

Two weight fields: weight (entry's perspective, guarded by synchronized(node))
and policyWeight (policy's perspective, guarded by evictionLock). They're correlated
but updated at different times — this is intentional for the telescoping sum to work.

makeDead subtracting the finalized getWeight() (not policyWeight) and UpdateTask.run
being deliberately dead-guard-free are a matched pair: a late-applied UpdateTask adds back
exactly the δ that makeDead over-subtracted. Don't add an isDead guard to UpdateTask and
don't switch makeDead to policyWeight — either one alone breaks the cancellation.
Both retire() and die() preserve the queue-type bits, so late updates use the same region
classification as makeDead. The same reconciliation applies to global and regional weighted
sizes; until late updates arrive, a region counter need not equal its deque's policy-weight sum.
Because racing updates offer their UpdateTasks outside the node lock, out-of-order
drains can leave a live node's policyWeight transiently negative; the climb transfer
loops then charge that weight to their quota and over-shift the region caps beyond the
commanded adjustment (the net can even invert the commanded direction). Adjudicated
tolerated, not guarded (2026-07, audit-adaptivity F1): the caps are the controller's
policy targets, not capacity enforcement (eviction and the total bound compare weightedSize
and each node's policyWeight with maximum, and eviction on the same transient before it
telescopes is the premature eviction ruled-out.md §Core accepts), and the split coerces back
on its own: the next completed sample overwrites the inflated carry-over, the below-floor lift
is not step-capped, and the excursion is bounded by one weigher swing per writer racing on the
key. Don't
clamp the transfer quota against negative weights, and don't "fix" the offer ordering.

Queue type constants are plain ints, not enums: WINDOW=0, PROBATION=1, PROTECTED=2.
The field is plain (not volatile), guarded by evictionLock.

ConcurrentHashMap Constraints

No recursive computations. Writing to the cache from inside an atomic
compute/computeIfAbsent/merge callback violates ConcurrentHashMap's contract; this is
not a Caffeine bug. Detection is best-effort: encountering an empty bin's ReservationNode
throws IllegalStateException("Recursive update") (surfaced raw, unwrapped), as does a
recursive append detected by compute or computeIfAbsent in a populated linked bin.
Other recursive updates, including tree-bin operations, can go undetected and silently
corrupt (lost inserts, double count updates, clobbered writes). A nested insert that crosses
the resize threshold runs the resize on the computing thread, which re-enters the held bin and
abandons the resize for the life of the map, leaving misplaced keys that iteration finds, get
misses, and invalidateAll() does not remove. Never rely on the ISE as a safety net. During a
refresh completion this can orphan the key's refreshes token (suppressing its auto-refresh)
only if data.compute throws before remap's lambda — a broken hashCode or a rare
cross-bin ISE (same-key recursion silently re-enters a populated bin instead); in-lambda throws
self-clean on the exits a completion can reach (the create-branch finally and the
present-entry catch). The one exit that preserves — an absent-branch user-function throw
(both siblings; ULC guards its catch with value != null) — a refresh completion never hits,
because its own lambda cannot throw before materialization. Either way the orphan self-heals on
the next write/removal. Don't add a catch-side refreshes.remove — it re-throws on the
broken-hashCode sibling.

The same restriction covers a read inside a computation or eviction listener that triggers an
inline refresh update or inline maintenance. Two remappers reading each other's stale key can
deadlock when completed reloads commit under the enclosing computations' locks, even with a pure
loader and the default executor, and a commit that removes a bin neighbour (a null reload)
leaves the computed key mapped to a dead node if the computation removes it. On an executor that
runs tasks on the caller (Runnable::run, a saturated CallerRunsPolicy pool), any read that
schedules maintenance runs the whole cycle under the locks the callback inherits. Its eviction of
the entry under computation or of a bin neighbour loses the computed value while the call returns
it, notifies the old value twice, drifts the weight, or leaves a dead node mapped; from put's
eviction listener it takes the bin under the node monitor and deadlocks against a concurrent
removal or load of that key. Both are still unsupported recursive modification under the existing
warnings that these callbacks must not modify the cache; neither calls for a dispatch change,
callback-scope tracking, or another API warning. A liveness check after the callback is too late:
the nested eviction has already unlinked the map's node, so the enclosing compute writes through
stale pointers whatever the lambda returns. The check could only choose between losing the value
silently and throwing, and it cannot detect a neighbour's eviction.

The evictionListener runs inside the CHM compute lambda — notifyEviction is called
within data.compute/computeIfPresent, holding the entry's bin lock — so it is subject to this
rule: a listener that modifies the cache (same-key or other-key) is a recursive update → an ISE
(caught + logged in notifyEviction, so the write is silently lost) or silent corruption. The
corruption can outlive the listener: a dead node left mapped makes every later put of its key
throw the broken-equality IllegalStateException, which blames the key's equals or hashCode,
and neither invalidate nor invalidateAll removes it. The Caffeine.evictionListener javadoc
says "must not modify this cache." That (and the parallel
mappingFunction/remappingFunction warnings across Cache/AsyncCache/LoadingCache/Policy)
was tightened from the wording inherited from ConcurrentHashMap's compute javadoc — "must not
attempt to update any other mappings" (CHM's phrasing through JDK 13; JDK-8232652 replaced it with
"must not modify this map" in JDK 14, though merge still carries the old form) — which by negation
wrongly implied a same-key mutation was safe. The removalListener, by contrast, runs outside the
atomic operation (async/after the fact) and may modify the cache. Don't flag a same-key mutation
from the eviction listener as a corruption bug — it's documented misuse.

CHM never hands back a stored key, so the unbounded cache notifies with the caller's
instance.
compute and computeIfPresent pass the remapping function the key the caller
supplied, and the table keeps the instance it already held; there is no API that returns the
stored one. UnboundedLocalCache is a thin wrapper over that map, so its Object-accepting
removals notify with the argument, where BoundedLocalCache reads node.getKey() and Guava's
LocalCache reads entry.getKey(). A RemovalListener therefore receives an equal key, not a
particular instance. The visible consequence is a cross-type equal key: with a stored HashSet
and an equal TreeSet passed to remove(Object), remove(Object, Object),
keySet().remove(Object) or entrySet().remove(Object), all eight bounded and unbounded cells
removed the entry, but a correctly typed listener ran on every bounded one and none of the
unbounded ones, where the checkcast throws and notifyRemoval logs it as a listener exception.
Recovering the canonical key there needs an O(n) scan or a per-entry node, which is the structure
the unbounded cache deliberately does not have, so this is a boundary rather than a repair.
Identity preference in cases that lose no notification is separately unspecified.

CHM bin blocking is not a Caffeine bug. compute() locks the hash bin. If the
mapping function (cache loader) is slow, all other operations on keys in the same
bin are blocked. This is the #1 recurring user issue (~20 reports). The answer is
always: use AsyncCache for slow loaders, increase initialCapacity to reduce
collisions, or make loaders faster.

A refresh commit waits on its bin like any write. The commit is a compute run by the thread
that sees the reload complete: the refresh caller, or the read that triggered an automatic
refresh, when the reload is already complete; otherwise the thread completing it, such as an
executor worker. It waits out any computation holding the key's bin. refresh's "asynchronously"
describes the load. Dispatching the commit with whenCompleteAsync would add a task per refresh,
give up installing a completed reload before refresh returns and returning it from the
triggering read, and still run inline under a caller-runs executor. A load that holds the bin while
waiting for work on the executor completing the reload deadlocks with the commit, as when Guava's
CacheLoader.asyncReloading shares a single-thread executor with the loader's own fetches; that
wiring is the application's, under the executor rule in
standing principles.

The same doctrine covers a two-map deadlock, not just blocking, including when it wedges an
innocent thread.
A loader that touches the cache can take the refreshes bin monitor and then a
data bin monitor, while any write takes data and then refreshes through discardRefresh.
Opposite orders, and findDeadlockedThreads() reports it on both implementations. A read closes
the cycle as well as a write. Reading a stale key registers its refresh, which takes a second
refreshes bin, so two reloads reading keys in each other's bins deadlock on refreshes alone, and
a nested reload that completes inline commits into a data bin. On one thread, a registration that
lands in the bin the reload holds makes CHM throw Recursive update: from the loader's read when
the bin was empty, failing the outer refresh, or from the caller's get or refresh when it was
populated. The counterparty can be maintenance evicting or expiring a key whose refresh is
registered; it waits holding evictionLock, so every later cleanUp() and every writer that falls
back to inline maintenance hangs with it. The reload body holds the monitor only on a caller-runs
executor or in an asyncReload's synchronous prefix; a CacheLoader.reload on the default
executor runs on a pool thread. It is declined on the same basis: the only path that takes another
lock while holding a refreshes bin is the user's reload/asyncReload, and
CacheLoader.reload's javadoc bolds the prohibition ("loading must not attempt to update any
mappings of this cache directly or block waiting for other cache operations to complete").
Everything else inside refreshes.computeIfAbsent is either a lock-free data read
(getIfPresentQuietly) or the asyncReload call itself, and the completion handle that does the
data work is attached outside the lambda, deliberately, so the refreshes monitor is not held
while it runs. Keep it outside.

Two objections are answered rather than ignored. That the victim is an ordinary put which did
nothing wrong is true, and is a property of every lock-order inversion in a shared structure; the
counterparty of misuse is always innocent. And a silent deadlock is a worse diagnostic than
Cache.get's documented IllegalStateException for recursive updates, which is a fair criticism
of the diagnosis rather than evidence the ordering is wrong. Don't reorder the internal maps to
make a forbidden loader safe.

Expiry must not call into the cache, and carries no javadoc saying so. The callback is
invoked under synchronized(prior) in put and under the bin lock on the compute paths. A
calculator that calls cleanUp() or a Policy ordering method waits on evictionLock while
holding the node monitor, deadlocking against the maintenance thread's evictEntry, which takes
those locks in the documented order. One that calls invalidate re-enters the monitor on its own
thread, retires the node, and lets the outer put commit into the dead entry. Neither is guarded.
The omitted warning is deliberate: computing a duration has no reason to re-enter the cache, so
the note that fits CacheLoader.reload and evictionListener would be noise on Expiry and
Weigher. Hoisting the callback out of the monitor the way Weigher was hoisted is not available
either, since expireAfterUpdate and expireAfterRead read the node's variable time under it and
the compute-path invocations need the atomic context.

A Policy ordering snapshot or setMaximum inside an atomic scope re-enters maintenance on any
executor.
Both take evictionLock and run a maintenance cycle, and Cache.policy() forbids
policy operations within another operation's atomic scope. The eviction listener runs while its
victim is still linked and counted, so a nested cycle there evicts the same victim again, through
the reentrant lock, until StackOverflowError: one due eviction became 407, and the drifted
counters let a maximumSize(2) cache hold 407 entries. From a remapping function the nested cycle
corrupts the bin like an inline eviction (see No recursive computations) or deadlocks against
maintenance. Not guarded: a held-lock check in snapshot would miss the listener's cleanUp()
and the remapping-function arms.

clear()/invalidateAll() do not wait for an in-flight computeIfAbsent insert. The insert
is invisible to clear()'s data.values() snapshot (the CHM Traverser skips the in-flight
ReservationNode), so it survives and serializes after the clear. CHM blocks per-bin and removes
it, but both orderings are linearizable, and the layered design can't see CHM's internal per-bin state
to block (a fix would require forking CHM). Inserts only — an in-flight update on an existing node
is waited for (clear()'s removeNode goes through computeIfPresent + synchronized(node)).
Documented user-facing on the invalidateAll() javadoc ("behavior … is undefined for an entry that is
being loaded (or reloaded) and is otherwise not present") + the wiki. Don't flag the clear-vs-in-flight-
insert divergence.

Eviction is async, not immediate. After put, the cache may temporarily exceed
maximumSize until the executor runs maintenance. Use executor(Runnable::run) for
inline eviction in tests, or call cleanUp() before assertions.

That determinism has a cost worth knowing before reaching for it under load. The expiration and
window scans bound evictions, not traversal: remaining-- runs only on an eviction or a
transfer, so a node the scan relinks and skips (an in-flight async load, a zero-weight entry) is
free and the walk continues past it. The default executor hides that, since maintenance coalesces
through the drain status and many writes share one cycle. Runnable::run removes the coalescing,
so a burst of pending async loads pays a walk over the pending set on every write in the burst,
which is quadratic across it. Fine for a test with a handful of entries; not a knob to reach for
in a benchmark or a reproduction that holds thousands of loads in flight. The expiration scans are
self-correcting once any load completes (they relink pending entries to the MRU end and stop at the
first completed, unexpired node), so their cost needs a deque with no completed entry at all. The
window scan relinks nothing, so its walk persists while pending entries sit at the window head; its
price under the default executor is in the pending-async scan entry of
ruled-out.

Expiration and cleanup are amortized, not instant. Caffeine performs maintenance
during write operations and occasionally during reads. For idle caches, use
Scheduler.systemScheduler() to get prompt expiration. This is best-effort with
no hard timing guarantees.

No close() by design. The Cache interface deliberately does not extend
Closeable. The cache is a data structure that becomes GC-eligible when
unreferenced. PerformCleanupTask is a WeakReference to its cache, which breaks the
scheduler→cache reference chain, so scheduled maintenance becomes a no-op
when the cache is unreachable. (JCache's CacheProxy is the only component
with explicit close semantics, as required by JSR-107.)

Refresh

Refresh returns the stale value, not the fresh one. get() returns the current
value immediately and triggers an async reload. The next get() returns the
refreshed value. This is the entire point — hiding reload latency from callers.

Refresh only triggers on access. An idle cache with no reads will never refresh.
For proactive refresh, use ScheduledExecutorService with cache.refresh(key).

"Logged and swallowed" is a promise about the future's result, not about producing it.
refresh documents that a failed reload is logged and swallowed, and that covers the
CompletableFuture completing exceptionally: the load ran and failed, the mapping is unchanged,
and the caller who ignores the returned future sees nothing. It does not cover asyncReload
throwing synchronously, which happens before there is a future at all. Those are distinct
failures. Guava and early Caffeine did not hand the future back to the caller, which is the
wording's origin; a loader that throws while merely constructing its future has a bug in the most
basic step, and surfacing it is right. Do not "fix" refresh/refreshAll to swallow a
synchronous throw from asyncReload/asyncLoad, and do not cite the declared throws Exception
on those methods as evidence that it should be swallowed.

expireAfterAccess + expireAfterWrite together is discouraged. Inherited from
Guava for compatibility. The two timestamps are independent; whichever has the
shortest remaining duration wins. Prefer expireAfter(Expiry) for custom logic.

Iteration

EntrySet.removeIf predicates receive immutable snapshots (Map.entry(k,v)), so
setValue throws. Like CHM and Guava's JDK-8078726 repair, removal is conditional on the
captured value. This applies to bounded, unbounded, async synchronous AsMapView, and raw
AsyncEntrySet; both async views delegate to the inner cache's removeIf, like their values
views. Keep write-through entries for iterator / spliterator / toArray, and do not
restore the positional iterator.remove default for predicate removal.

Map equality uses size, iteration over this map, and count == expectedSize. The
AbstractMap shape is symmetric with HashMap for equals-keyed caches and costs O(n), versus
CHM's O(n+m) two-sided scan. Under weakKeys() it is not: the cache equals a HashMap holding
an equal but distinct key, and that HashMap does not equal the cache (IdentityHashMap is
unequal both ways). Two equal-but-distinct keys also make the cache equal a map holding one of
them plus an entry the cache lacks, with unequal hash codes, as Guava's weakKeys() cache does;
only IdentityHashMap's identity pass avoids that, at the cost of making the single-key case
false both ways. The final count catches maintenance trimming dead entries after the size
prescreen; otherwise a surviving subset can incorrectly compare equal. Preserve it in
BoundedLocalCache.equals and the future-typed LocalAsyncCache.AsMapView.equals.

asMap() iteration is not a cache read. Iterators do not update access times or
frequency counters. This prevents iteration from polluting the eviction policy.
Expired entries are skipped during iteration.

Under weakKeys() the key and entry spliterators still claim DISTINCT, and must. The
weakKeys() javadoc names IdentityHashMap as the model for its semantics, and the JDK's own
identity-keyed map advertises DISTINCT on exactly those two spliterators while omitting it on
values(), which is the shape Caffeine matches. It produces the same triple audits report as an
anomaly: two keys that are equals but not == give size() == 2,
keySet().stream().distinct().count() == 2, and new HashSet<>(keySet()).size() == 1 (verified
on JDK 25). Removing the flag would not be a no-op, it would be wrong: distinct() would then run
with equals, merging two genuinely distinct live entries so the stream reports fewer elements
than the cache holds. Under identity semantics the keys are distinct, so the flag is the correct
claim.

Identity comparison applies to one-sided queries, never to equals or hashCode. A
one-sided query (remove(k, v), replace(k, old, new), containsValue, values().remove,
keySet().removeAll) can be identity-based unilaterally, because the worst case is declining to
act. A bilateral contract cannot: the other side is AbstractMap or AbstractSet, which compares
with equals and never reciprocates, so identity there breaks Object.equals symmetry and buys
nothing. Measured with the cache holding one value and a HashMap holding an equal-but-distinct
one: at HEAD both directions of equals are true, while an identity-comparing equals gives false
one way and true the other. This is the boundary of the _byIdentity convention, and every method
that convention covers is one-sided. No implementation anywhere compares keys by equality and
values by identity in equals: IdentityHashMap is reference-based on keys and values alike, and
Guava's LocalCache extends AbstractMap and declares no equals at all.

So value identity is coherent with no map, and that is accepted. Under weakValues() or
softValues() the value-bearing queries compare by identity (containsValue, remove(k, v),
replace(k, old, new), values().contains/remove, entrySet().contains/remove, and through
AbstractSet the entry view's equals), while Map.equals, hashCode and the
WriteThroughEntry objects the views emit compare with equals. The bulk removals compare with
equals as well: values().removeAll/retainAll ask the argument collection and
entrySet().removeAll/retainAll compare the emitted entries, so they can remove an entry
contains reports absent or keep one, under weakKeys() too; IdentityHashMap's identity entries
do neither. Probed against
a HashMap holding an equal-but-distinct value: equals true both ways with matching hash codes,
entrySet().equals false one way and true the other, contains and containsValue false, and the
emitted entry equal to the probe entry. Guava's weakValues() cache reproduces that row element for
element, though its values().remove removes by equality where Caffeine's compares identity. Both
coherent alternatives are worse. IdentityHashMap buys internal coherence and is
still asymmetric against a HashMap (equals false one way, true the other, differing hash codes),
which is the bilateral case above. Making the queries equals-based would contradict the value
semantics weakValues() and softValues() document. Reference implementations do not agree with
each other here, and the cache is biased toward size eviction rather than reference collection, so
do not raise the disagreement as a defect or propose a consistency repair.

View equality has an accepted physical-size/logical-content gap. Key and entry views
inherit AbstractSet.equals / hashCode. size() includes expired or collected entries
pending maintenance; iteration, containment, and hash code skip them. With live a and
pending-dead b, HashSet{a,b}.equals(keySet) can return true while the reverse comparison
is false and hash codes differ. The argument's HashSet.equals performs the true-returning
comparison, so overriding the view's equality cannot repair it. A logical size() was
rejected to preserve the lock-free physical estimate; a physical hash is unavailable once a
weak key is collected. WeakHashMap has the same changes-during-comparison problem.
Values views use identity equality like CHM. The same gap lets isEmpty() disagree with
iteration (an isEmpty()-guarded iterator().next() can throw NoSuchElementException),
null-pads a toArray(new T[size()]), and lets a receiver that compares entry sets (Guava's
ImmutableMap) equal asMap() while holding a never-cached entry per pending one. The inherited
AbstractSet.equals can also return true under one concurrent put that lands after its size
reads; the mirrored shape (physical size() prescreen, iterate this view, probe the argument's
contains, count == expectedSize) was evaluated and declined, since it reverses two adjudicated
rows and moves the weakKeys() false positive to the view. For exact comparisons, call
cleanUp() with no concurrent operations; under variable expiration an entry that expired
within the current wheel tick stays until the next tick (see TimerWheel). An
additional asMap() size warning was declined as redundant with the cache's existing
approximation contract.

The async synchronous view has the same gap for in-flight futures, which its size() counts and
its iteration skips. cleanUp() cannot remove them, so the gap lasts until the load settles.
Receivers split by algorithm: AbstractMap's size check (HashMap, the bounded cache, the view
itself) makes the comparison false in both directions, while ConcurrentHashMap's two-sided scan,
which the unbounded cache delegates to, makes chm.equals(view) true and view.equals(chm) false.
No equality on the view is symmetric with both, and moving the unbounded cache to the size check
would leave ConcurrentHashMap itself asymmetric.

Maintenance nudges

Read paths nudge scheduleDrainBuffers() when they observe an expired/collected
entry
(getIfPresent, containsKey, containsValue, getAllPresent, the iterator,
and the key/value/entry spliterators), so lazily-detected garbage is reclaimed promptly.
On a plain miss, getIfPresent still assists when the status is REQUIRED; getAllPresent
and membership reads have no corresponding absent-entry assist. The nudge is a cheap flag
check when maintenance is already running. On a caller-runs executor it runs maintenance inline,
so an in-progress scan of data.values() can have a node reaped underneath it — e.g.
containsValue is an O(n) scan and the internal LocalCacheSubject validator calls it
per node while iterating data.values(); a weak key collected mid-scan is then
drained, correctly removing and killing a node the weakly-consistent iterator still
yields. Production readers tolerate a dead node (they check isAlive/getValue), and
the validator was made robust to it: it iterates data.entrySet() and validates a node
only if it is still mapped under its key, so a node reaped mid-scan is skipped while a
node genuinely stuck in the map (a leak) stays mapped and is still caught. Inside a
computation or eviction listener the same inline cycle is unsupported nesting; see
ConcurrentHashMap Constraints.

A failed replace nudges only when the entry is garbage. Both overloads signal "did not
update" by clearing ReplaceContext.oldValue, which alone cannot tell a dead or expired entry
from a caller's expected value that did not match. ReplaceContext.garbage separates the two so
that only the first schedules maintenance. "fix minor edge cases in put and remap" added the nudge
for the expired case but hung it on the shared signal, so replace(k, expected, new) also
submitted a task on every healthy CAS failure. Over 10⁶ failing replaces against a one-entry
cache, removing it took an unexpiring cache from 34 ms to 15 ms and an expireAfterWrite cache
from 33 ms to 28 ms. Both directions are pinned:
AsMapTest.replaceConditionally_wrongOldValue_noMaintenance and
ExpirationTest.replaceConditionally_expired_maintenance.

The conditional overload tests hasExpired before containsValue, matching remove(k, v)'s
branch order, so an expired entry is reclaimed whether or not the caller's expectation held.
Testing containsValue first would let a healthy mismatch skip the ticker read and reach 16 ms
on the expiring cache, and it was rejected: the two conditional operations should read alike, and
the saving is one System.nanoTime() (9 ms per 10⁶ calls on the same machine). containsValue
cannot be tested before the null checks at all, since a cleared value reference fails it and the
entry would be misfiled as a mismatch.

Known JDK Interactions

StackOverflowError can leak the eviction lock. If user code causes a
StackOverflowError inside a cache operation, the eviction lock can stay permanently held and
block all subsequent writes (JDK-8318888, open; JDK-8319309 is its duplicate). It happens at
release, when unlock() fails to run, and at acquisition: lock() and tryLock() are
@ReservedStackAccess, so an overflow inside them completes the acquisition and raises the error
on return, before the caller's try. The canonical lock(); try { } finally { unlock(); } leaks
the same way, so no call-site change removes it.

PerformCleanupTask is not a ForkJoinTask. ForkJoinPool.execute(Runnable) does not wrap a
ForkJoinTask, and its source comment says "avoid re-wrap". The steal path claims a slot with a
CAS on the slot content, then writes the absolute value base = b + 1. A worker can stall between
its base recheck and the CAS. If the pool receives the same task object again, the CAS can
succeed on a later copy, one or more laps around the array. The worker then moves base back by a
multiple of the array length, and every slot is null. No code in the pool repairs this state.

JDK 22 and later reject every submission to that queue with Queue capacity exceeded (#2021).
JDK 21 and earlier silently lose or overwrite the tasks in that queue. From v2.2.4 to v3.3.0, the
task extended ForkJoinTask, and exec() returned false. This reuse avoided the per-submission
adapter, which was a profiling hotspot in 2016. The reuse also made a throw from exec()
permanent: run() catches every Throwable, so only its own logger.log could throw, on a
StackOverflowError while logging or a logging backend that throws, and that completed the task
exceptionally so that a ForkJoinPool never ran it again, leaving maintenance to the write
buffer's inline assist and cleanUp(). A fresh wrapper per submission confines such a throw to
that submission.

A call to reinitialize() before each submission does not prevent the race. The race needs only
the same reference in the same slot, with any task status. A small set of rotated task objects
does not prevent it either. The fix is in the class and not in scheduleDrainBuffers, because the
pacer also submits the same object through Scheduler.forScheduledExecutorService.

The pool now allocates one RunnableExecuteAction for each submission. drainStatus coalesces the
submissions that cache operations trigger, and the pacer holds at most one delayed submission. The
two paths can overlap, so one cache can have two submissions in flight. On JDK 25.0.1,
GetPutBenchmark and a weakKeys() copy ran with 3 forks and -prof gc. Every throughput change
was within the 99.9% confidence interval. Allocation increased by at most 0.06 B/op, and by 0.4
B/op for weakKeys() read/write. The 2016 result, that the adapter halved weakKeys() read
throughput, did not reproduce. Each bounded cache is 8 bytes smaller, because its task no longer
has the ForkJoinTask status field.

The task extends WeakReference rather than holding one, which saves an object per cache (16
bytes with compressed oops). Its clear() and enqueue() do nothing, so only the garbage
collector clears the referent. enqueue() clears without calling clear(), so both overrides are
needed. Without them, an executor that cleared the task it receives would make every later
execution a no-op. This is a deliberate exception to treating a contract-violating component as
the user's bug: the overrides cost nothing on any path, and the ForkJoinTask version guarded the
same way with no-op complete, completeExceptionally, and cancel. An executor can still halt
maintenance by never running the task. BoundedLocalCacheTest.cleanupTask_ignoreClear pins the
overrides.

Pacer

The Pacer rate-limits expiration maintenance scheduling. It uses
TOLERANCE = ceilingPowerOfTwo(1 second) (~1.07s) as a minimum delay threshold,
preventing scheduling storms from rapid expirations.

nextFireTime = 0L is the unscheduled/cancelled sentinel. calculateSchedule
bumps any computed fire time that would equal 0L up to 1L to prevent a
collision with the sentinel — needed only in edge cases where now + TOLERANCE
or scheduleAt lands exactly on zero, but the guard removes the ambiguity for
readers of schedule()'s recursion check.

future == null && nextFireTime != 0L is a deliberate transient, not a wedge —
and its safety leans on GuardedScheduler.
schedule() commits nextFireTime
(via calculateSchedule) before calling scheduler.schedule(), then publishes
the returned future. Between those two steps the pacer is momentarily in that
state, which the immediate-scheduler short-circuit relies on: an immediate scheduler
runs command synchronously inside schedule(), re-entering schedule() before the
future is published, and the nextFireTime != 0L check breaks the recursion.
Reaching that state on the replacement path is what makes the short-circuit
complete, and it is why the reschedule arm calls cancel() rather than
future.cancel(...).
cancel() clears nextFireTime and the field together, so a
re-entrant call meets a fully unscheduled pacer and takes the guard; cancelling the
future alone leaves the old one published, and then !future.isDone() is permanently
false for an immediate scheduler, so every re-entry cancels and reschedules without
bound. That was a live defect: a Scheduler running its command synchronously and
returning a completed future hung put on every executor including commonPool,
silently, because GuardedScheduler and PerformCleanupTask.run swallow the
StackOverflowError and the stack immediately re-descends. Pinned by
ExpirationTest.schedule_immediate_completed; its neighbour schedule_immediate
returns an incomplete future, which is the shape a real inline scheduler never
produces and is what masked this. If
scheduler.schedule() could throw on the first call, that same state would never
clear — every later schedule() early-returns and cancel() no-ops (future stays
null), permanently disabling prompt expiration. It can't: GuardedScheduler catches
every delegate throw and maps a null return to DisabledFuture. Caffeine.getScheduler
wraps a user-supplied scheduler this way, but passes the built-in systemScheduler()
and disabledScheduler() through unwrapped — Caffeine's own scheduler types are known
not to throw or return null, so only user types need guarding. Either way
scheduler.schedule() here never throws or returns null. The ordering is safe as written
— don't wrap it in a try/catch to "harden" an unreachable throw, and don't hand Pacer
an unguarded user scheduler (that, not the ordering, would be the bug).

SystemScheduler returns a future separate from its timer, so that a cancel removes the JDK's
delayed task.
The timer is orTimeout on an internal future, and its timeout completes the
returned future and submits the task. Any completion of the returned future, a cancel included,
completes the timer normally, the one completion on which orTimeout's canceller removes the
delayed task on every JDK: on 11, and on 17 before 17.0.14, it ignores an exceptional completion
such as a cancel (JDK-8303742). delayedExecutor keeps no handle at all, so with it each
cancelled task stayed queued until its original fire time (896 B on JDK 25), one per cycle of a
cache emptying and refilling. SchedulerTest.systemScheduler_cancel pins the release; on JDK 11,
the default test JVM, a single future fails it. Revisit when the JDK baseline moves: from 21 one
future suffices (completeOnTimeout, with the task chained by thenRunAsync), and from 25
ForkJoinPool.commonPool().schedule returns a cancellable future.

The system scheduler has three accepted consequences. A cache that is dropped leaves its
pending timer queued until it fires. The cache is held weakly and remains collectable, but a
process that churns caches with long expiries keeps about churn rate times remaining delay of
those timers. The timer submits the task from the JDK's shared delay thread, so with a direct
executor the maintenance cycle, eviction listener included, runs on that thread and delays every
other orTimeout, completeOnTimeout and delayedExecutor task in the JVM until it returns. A
rejection at fire time loses that cycle only: the returned future is complete before the
submission, so the next re-arm schedules normally, and the rejection is dropped in the timer's
discarded stage. The executor(Executor) javadoc warns that an executor "that discards tasks or
never runs them may experience non-deterministic behavior."

rescheduleCleanUpIfIncomplete piggybacks an already-scheduled pacer fire, by
design.
Its immediate resubmission is limited to executor == ForkJoinPool.commonPool(),
since it cannot tell a transparent wrapper from a caller-runs executor, so a wrapper around the
common pool (for context propagation, say) takes the custom-executor arms: with a removal
listener under continuous eviction a forwarding wrapper measured 232.9 against 144.5 ns per
insert, against about 8 ns without one. A drainStatus == REQUIRED backlog re-arms the
pacer only when !pacer.isScheduled(); if a fire is already pending (the next expiration
event), the backlog rides that fire rather than stacking a second schedule. An expiration
backlog stays prompt regardless — a >EXPIRATION_THRESHOLD backlog leaves an
already-expired deque/wheel head, so getExpirationDelay returns ≤ 0 and
expireEntries already scheduled the pacer at TOLERANCE (~1s). Size eviction is
uncapped (drains fully in one cycle), so it never backlogs. A region transfer
(evictFromWindow, demoteFromMainProtected) is capped at QUEUE_TRANSFER_THRESHOLD and
re-arms after expireEntries armed the pacer, so a large window resize rides the pending fire
too, delaying the rebalance rather than overfilling the cache. A reference backlog defers
the same way a write-buffer one does, and the delay costs a late COLLECTED notification
rather than a stale read, while the entry's other half (a weak key's value, or a weak or soft
value's key, node and table slot) stays reachable until a cycle polls it. A GC schedules no
maintenance, cleanUp() polls at most REFERENCE_THRESHOLD + 1 references per queue per
call, and a cache with only reference collection has no pacer even with a Scheduler. The
shape is a write-buffer backlog (drainWriteBuffer's WRITE_BUFFER_MAX cap, reached only when
a concurrent writer refills during the drain, or its relaxedPoll passing over a slot a
producer has not published) on a cache whose next expiration is
distant, that then goes idle: the buffered policy tasks — LRU/weight bookkeeping over
CHM mappings that are already committed and visible — wait for that distant fire or
any later write / read-stripe / cleanUp. If the pass over an unpublished slot instead settles
IDLE (the accepted weak-memory strand) while no other entry is expiring, no fire is pending,
and only a later operation drains the task. Worst observable is a transient over-
maximumSize on an idle cache, the documented async-eviction contract — plus, under
variable expiry, a deferred expiration notification: an entry whose AddTask is
still buffered is not yet in the timer wheel, so it was invisible to the
getExpirationDelay that armed the pending fire, and its removal listener waits for that
fire (which can exceed the entry's own TTL, since the armed time came from the
policy-visible entries) or the next cache operation. Reads stay correct throughout — lazy
hasExpired gates every read. Best-effort amortized maintenance; don't drop the gate to
force a ~1s reschedule (it churns the distant fire's cancel+reschedule for a narrow,
self-healing transient).

Without a Scheduler, maintenance is amortized onto callers, by design. The immediate
re-arm in rescheduleCleanUpIfIncomplete is restricted to commonPool because any other
executor may run the submission on the calling thread, where the work is no longer
amortized and the caller pays a whole cycle. The deferred pacer arm above is the fallback
for a custom executor, and it needs a Scheduler. Configuring one requests prompt expiration;
a size-only cache has no pacer. Without this arm, a REQUIRED backlog waits for the next cache
operation, so a quiesced cache stays over maximumSize until then. The excess is capped,
not unbounded: the backlog is write-buffer tasks, MpscGrowableArrayQueue is bounded at
WRITE_BUFFER_MAX, and a full buffer forces afterWrite's inline assist, so a quiesced
cache's estimatedSize() cannot exceed maximum + WRITE_BUFFER_MAX. Measured with
maximumSize(10), a single-thread executor and no scheduler, 200,000 writes then idle: 4
of 10 runs stayed over maximum, and the largest residue over 20 runs was 1,987 against a
2,058 bound. The live peak while writers run is higher. A drain applies up to
WRITE_BUFFER_MAX + 1 insertions before evictEntries runs, producers refill the buffer
meanwhile, and each writer blocked in the inline assist has already committed its mapping, so
the peak is maximum + 2 * WRITE_BUFFER_MAX + 1 plus the writers. With WRITE_BUFFER_MAX at
2,048, 16 writers peaked 4,080-4,113 over maximums of 10 to 10,000 and 64 writers 4,161 over,
converging to the maximum when they stopped. The model is a garbage collector's: the excess is
capped and reclaimed on the next operation rather than on a timer. Don't add a third arm. A caller
cannot know whether the executor would run the submission inline, and moving it into
PerformCleanupTask, where a held eviction lock does identify a caller-runs execution after the
fact, fails on the same ground: prompt size eviction in an idle cache is not a contract the cache
offers.

Refresh Internals

The loader's original future is both refresh token and public result. Its identity
represents the generation. Per-generation copies would change refresh(k) / policy.refreshes
and stop cancellation reaching the loader's future. Any holder's completion reaches it the same
way: complete(v) is cached and counted as a load success, orTimeout fails every caller waiting
on that load and removes the entry, and completing a Policy.refreshes() future installs its value
and discards the loader's. Completion also precedes dependent
handlers, so awaiting that future cannot guarantee the cache-updating handler has run.

Reusing one pending future for two generations of the same key is outside this model: the
older handler can claim the successor's token and both can dispose of the produced value,
affecting a non-idempotent listener. Ordinary LIFO dependent completion makes the window
narrow; a coalescing loader can hand out copies itself. A write-time ownership condition
repairs neither double disposal nor orphaning across preserved timestamp changes.

Distinct future instances with custom equality are likewise unsupported. Map conditional
remove / replace and refreshes.remove use equality, which matches identity for standard
CompletableFuture but can remove a successor for a hostile subclass. Repair would require
identity-conditional operations throughout both caches and the refresh registry, not a local
ownership check.

A manual synchronous-cache refresh uses one LocalLoadingCache.RefreshOperation per call.
It carries the registration and completion state, while the loader's original future remains
the token and return value. Registration runs inside refreshes.compute; completion is attached
after it returns, and coalescing onto an existing refresh attaches no additional handler.
The async view's absent-key branch registers with putIfAbsent rather than a compute and owes
the same rule: it attaches its by-key release only when it won the registration. Attaching that
release to whichever future it returns puts a refreshes.remove on a token this caller never
registered, and since dependents run most recently registered first it fires ahead of the owner's
commit, which then reads itself unowned and discards a reload no write had superseded. The
identity argument does not catch it, because the token removed is the owner's; only the caller is
wrong. A refresh completion that commits a mutation still discards the current registration by
key. Pinned by AsyncLoadingCacheTest.refresh_absent_racingRefresh.

refreshIfNeeded is intentionally lock-free. Reads of writeTime, getKey,
getValue, getKeyReference, isAlive, and the CAS of writeTime happen
without synchronized(node). A stale observation could let asyncReload fire
on a just-retired node, but the completion-path ABA guards (currentValue == oldValue + (node.getWriteTime() & ~1L) == writeTime) discard the result. Cost
of the rare spurious loader call is accepted to keep the refresh fast path lock-free.
The write-time guard is itself immune to a same-instance overwrite reusing the old
value: a refresh-eligible entry's age exceeds refreshAfterWriteNanos, which makes
exceedsWriteTimeTolerance true in both of its refresh arms (a duration within the
tolerance takes the always-true disjunct, a longer one is exceeded by the age), so an
intervening write always moves writeTime and the stale completion cannot match it.
That arithmetic is load-bearing; a change letting an update skip setWriteTime on a
refresh-eligible entry would re-open the stale-reload commit.

The low bit of writeTime is a soft-lock marker, and the completion ABA check
must mask it.
A reader probing for a refresh CASes writeTime → writeTime | 1
while it registers the token in refreshes, then resets it; it starts no load if
the token already exists, so its transient marker is invisible to any stampede check.
The completion compares the base write time (& ~1L), not the raw value — a
concurrent reader's transient soft-lock is not a modification, and comparing the raw
value discards a perfectly good reload (issue #1970). The completion also keeps the
token registered in refreshes across the value swap: it reads the token to confirm
ownership rather than removing it, and the compute machinery's discardRefresh clears
it after setWriteTime. Holding it for the swap keeps concurrent reads debounced so
none can trigger a stampede in the remove-then-refresh window. Finally, the
computeIfAbsent lambda re-validates the marker under its per-key atomicity and aborts
when a concurrent refresh already completed (its setWriteTime cleared our marker),
so a delayed reader that passed the containsKey gate does not launch a duplicate,
stale reload from the same prior value. On the absent-create path the token clear runs
in a finally, so a weigher or expiry callback that throws while creating the entry
cannot orphan the token (#1970).

discardRefresh is deliberately over-aggressive. A mutation that races a
refresh discards whatever token is in refreshes without trying to prove it's
the same generation. A refresh that registered before the mutation published its value was
launched against the old value, so killing it is correct for linearizability. Insertions and
unbounded writes release inside the map computation, before the new mapping is visible, so they
discard only such refreshes. A bounded update of an existing entry (put, both replace
methods, and remap's present-entry write) publishes the value before its release, so a refresh
registered in that gap loaded the new value and is discarded as well. That over-discard is
accepted: the successor's reload is declined and the next refresh loads again.

A refresh completion's mutating exits follow the same rule, and releasing only the completing
token was rejected.
A commit that replaces, creates, or removes the entry is a mutation, and a
manual refresh(k) may replace its completed token while it runs. Releasing only the completing
token rescues a successor that loaded the published value, which only a bounded update exposes,
but keeps one that loaded the replaced value and can never install: its registration outlives the
commit, so a later refresh(k) coalesces onto its load and is discarded with it, and
refreshIfNeeded's containsKey gate suppresses automatic refresh until that load finishes. That
is the residue the prescreen race below leaves, widened to any refresh that registers during a
commit before its value is published. In aligned races of 10,000 trials per case, owner-conditional
release removed 31 discarded successors when nothing followed and added 147 discarded later
refreshes when one did. Pinned by
RefreshAfterWriteTest.refresh_committingCompletion_discardsStaleSuccessor and its
refreshIfNeeded and absent-create twins, which park the commit in its Weigher.

The bounded cache's containsKey prescreen stays, and the race it leaves open is
benign
(accepted). ConcurrentHashMap.remove takes the bin lock
whether or not the key is there, so the prescreen is what keeps an ordinary write
off that lock. It was added to the bounded cache only; the unbounded cache still
removes unconditionally. What the prescreen cannot see is an in-progress
computeIfAbsent reservation, which reads as absent, so a write landing inside
that window leaves a token launched from the generation it superseded. On the
automatic path nothing is committed wrongly: the completion's ABA guards reject
the value, and the registration re-validates node.getWriteTime() inside the
reservation, which rejects any write that lands earlier. That first clause is load-bearing and was
only true by accident until 2026-08-24. The completion's write-time term reads
the node captured when the reload started, and retire()/die() leave
writeTime frozen, so a remove and reinsert of the same value instance
presented an unchanged value and an unchanged write time and the guard committed
a reload launched from the dead generation. The commit branch now also requires
node.isAlive(), which is exactly the generation test the other two terms cannot
make; both remain necessary, since the write time still catches an in-place
update of a node that never died. Pinned by
BoundedLocalCacheTest.refreshIfNeeded_reinsertedNode_rejectsStaleReload. There the residue is
bounded by one load and is a delay rather than a loss: automatic refresh is
suppressed for that key while the orphan is in flight (refreshIfNeeded gates on
the same containsKey), and a refresh(k) issued after the write coalesces onto
the superseded token and is discarded. Don't drop the prescreen to close it, and
don't add a registration-side re-check: closing it there would fix only
refreshIfNeeded and leave the manual LocalLoadingCache and
LocalAsyncLoadingCache registrations, which carry no write-time marker to test,
while implying all three were closed.

A manual refresh that started absent can commit across the same window, and that is
accepted.
LocalLoadingCache.RefreshOperation has no node and no write time, so its only
ownership test is currentValue == oldValue, which is null == null when the key was absent
at registration. A put followed by an invalidate inside the reservation window therefore
leaves the token registered and lets the completion install its loaded value over both.
Measured on the bounded cache with a loader parked inside the registration, 5/5 per arm: put
then invalidate installs the load, put alone leaves the written value (so the guard works
wherever it can tell the two apart), and a present-start refresh is discarded. A present start
whose racing writes end on the instance it read (put(k, 6); put(k, 5) after reading 5) installs
over them, since the identity test cannot tell that from no write. The escape needs the loader to
run inside refreshes.compute, which means a direct executor, a saturated pool with
CallerRunsPolicy, or an AsyncCacheLoader returning a completed future; on
ForkJoinPool.commonPool() the registration publishes first and the refresh is discarded 0/5.
The resulting history is still a legal linearization (put, invalidate, then the refresh's
install), and no public contract covers it: Cache.invalidate is undefined only for an entry
being loaded and otherwise not present, which the put makes false. The asynchronous cache does
not share the shape, since its absent case installs an in-flight future through
computeIfAbsent on data and registers with putIfAbsent.

The unbounded cache keeps the unconditional remove, so a write there waits out an inline
refresh load on the same key
(measured: a 2004 ms put against a 2 s load). Accepted.
UnboundedLocalCache is a light ConcurrentHashMap wrapper, and CHM's put is pessimistic
anyway, so there is no optimization proper to apply there. The bounded cache takes the
complexity because it already coordinates a great deal of metadata for speed.

A cleared reference is equal only to itself. InternalReference's equals compares referents,
so two distinct references that have both been cleared used to compare equal (null == null) and
aliased whenever their cached System.identityHashCode values collided. Nothing in production
depends on that: every lookup that can involve a cleared reference (drainKeyReferences,
drainValueReferences, evictEntry) passes the same object, which the identity short-circuit
already answers. referenceEquals and objectEquals therefore require a live referent. The one
property this drops is cross-type null equality between a WeakValueReference and a
SoftValueReference, which cannot arise since a cache is one or the other; ReferenceTest .reference_equality now asserts each cleared reference forms its own equality group.

Automatic refresh uses > where expiration uses >=, and that stays. refreshIfNeeded tests
(now - writeTime) > refreshAfterWriteNanos() while hasExpired tests >= on all three of its
predicates, so with equal durations an entry is expired one nanosecond before it is
refresh-eligible. The > matches Guava and is unobservable on a real ticker (only a FakeTicker
advanced by exactly the duration can see it). Ruled 2026-08-24: the javadoc's "once a fixed
duration has elapsed" is not wrong enough to reword, and other Guava-compatibility choices rank
ahead of this one. Don't re-raise the boundary or the asymmetry.

All three refresh registrations key refreshes by referenceKey(key), never by the node's own
key reference.
Under weak keys a node's retire()/die() calls clear() on the very
WeakKeyReference the node holds, and InternalReference.equals compares referents by identity,
so a cleared reference is equal to no reference built later for the same live key. A token
registered under the node's reference and preserved past retire() (the preserveRefresh exits
do exactly that) is then unreachable: the successor's own discardRefresh builds a fresh
reference and cannot match it, and Policy.refreshes() skips it because its referent is null, so
the registration and its future are retained for the cache's lifetime and the growth is invisible
through the public view. LookupKeyReference holds the key strongly and is never cleared, which
is why the two manual paths always used it; refreshIfNeeded now does too. This costs no extra
retention: the loader call and the completion closure both capture the key strongly for the
future's lifetime. The containsKey prescreen still reads node.getKeyReference(), which is
free and equal to the lookup key while the key is live, so the allocation lands once per
registration rather than on every refresh-eligible read. Don't key refreshes by anything the
node owns. Pinned by
BoundedLocalCacheTest.refreshIfNeeded_weakKeys_preservedTokenIsDiscardable.

A refresh completion releases its own token in its catch, not only through remap. The
completion's commit normally clears the registration inside remap, so the outer catch looks
redundant. It is not: remap has throw sites that precede every discardRefresh, including
requireIsAlive's broken-equals check and the ticker read behind hasExpired. A throw there leaves
the registration behind, and because refreshIfNeeded gates on refreshes.containsKey, that key's
automatic refresh is suppressed for the rest of the cache's life from one transient
user-component failure. All three completions therefore mirror their own error branch with the
identity-conditional refreshes.remove(keyReference, ownFuture), which cannot take a successor's
token. Note the throw sites after the discard are already safe, since the material tail discards
before returning from the map computation. Pinned by
BoundedLocalCacheTest.refreshIfNeeded_completionThrows_releasesToken and its refresh /
refreshAsync twins, which fail a refresh completion through a throwing Ticker.

A rejected reload is notified even though it was never in the cache. When the
completion's compute declines to install the reloaded value it sets a cause and calls
notifyRemoval(key, value, cause) — EXPLICIT on the absent exit, REPLACED on the reject
exit (a result identical to the current value is not notified). So a RemovalListener can see a
value that was never a mapping. That is intentional and follows from linearizability: the value
was produced, the cache decided not to keep it, and the listener is the disposal hook, so not notifying
would be the surprise — the value would be dropped with no chance to release what it holds.
Deliberately not spelled out in the public javadoc: the surrounding refresh ordering is
vague there (Guava was not linearizable either), and pinning this corner would over-specify it.
Don't "fix" the notification away, and don't treat the two causes as interchangeable.

Returning the captured oldValue still offers it as the new value to retain. If it is no
longer current, rejecting the refresh rejects that attempted resurrection and notifies again;
the earlier removal does not discharge disposal of this new offer. Do not suppress by comparing
with the captured old argument. Disposal after a user Weigher or Expiry throws during
installation is outside the cache's responsibilities; keep the existing failure cleanup.

The one exception is an internal query-style no-op, flagged with RemapHints.preserveRefresh:
putIfAbsent on a present key, a non-matching conditional remove/replace, or an async-view
retry that returns an unfinished future without invoking the user's remapping function. Public
remapping functions cannot set these hints; returning the same user value is still a write.
These internal no-op paths leave a racing refresh intact. Both
BoundedLocalCache.remap and UnboundedLocalCache.remap honor the hint (a same-instance
return with preserveRefresh set skips discardRefresh); a real mutation still discards.
The unbounded cache used to drop the hint and cancel the reload — the sibling caches must
stay in sync here.

The same hint also owner-scopes a refresh completion. When a refresh finishes it
re-enters compute to install or reject the reloaded value. Because refreshes holds one
future per key, a by-key discardRefresh from that completion is safe only while the
completing refresh still owns the registration
. If a newer refresh has since registered — the
prior token was cleared by a racing write, or by an invalidate + refresh that re-registers
an asyncLoad on the now-absent key — the by-key discard would steal the successor's token,
dropping its freshly loaded value and leaving the cache stale (self-heals only on the next
refresh-eligible read). So each completion path (LocalLoadingCache.refresh,
LocalAsyncLoadingCache.tryComputeRefresh, BoundedLocalCache.refreshIfNeeded) computes
owned = refreshes.get(kr) == ownFuture and sets preserveRefresh = !owned on its non-commit
exits — reject and absent — mirroring the error path, which was already owner-scoped
(refreshes.remove(kr, ownFuture)). The hint protects a successor already present at the
ownership check. A successor that registers between that check and the by-key no-op release
can still be discarded; tightening this residual was declined. A later refresh recovers, and
this interval does not justify restoring the rejected token-bearing hint.
Honoring the hint therefore extends beyond the
same-instance no-op block: remap's two absent null-return exits (n == null and the
evicted-retire) and the unbounded absent exit skip the discard when preserveRefresh is set.
The absent-create exit does not, and must not: installing a value is a mutation, so the
over-aggressive-discard doctrine applies to it like any other write. Both manual paths create only
in their owned branch, but a successor can replace the completed token before the finally runs;
that successor loaded the key as absent, so discarding it is the doctrine rather than a steal.
Every one of the twelve callers that sets preserveRefresh either returns null or returns the
existing value of a present entry, so the exit is not reachable with the hint set. Pinned by
BoundedLocalCacheTest.remap_absentCreate_discardsPendingRefresh and
RefreshAfterWriteTest.refresh_committingAbsentCompletion_discardsStaleSuccessor.

The !computeIfAbsent evicted-retire exit is outside the rule for a second reason: it runs
before the remapping function, and the remapping function is the only thing that ever assigns
preserveRefresh, so no hint exists yet whatever the caller passed (computeIfPresent and
replaceAll, the only non-creating callers, pass none). Reaping a dead entry for a caller that
may not recreate it is a purge, so it discards, the same reading as invalidate/clear below.
Pinned by BoundedLocalCacheTest.remap_evictedRetire_nonCreating_discardsPendingRefresh.

On a reject exit the hint cannot stand alone. remap honors preserveRefresh for a
same-instance return only at its preserveTimestamps no-op exit, so all three completion paths
set preserveTimestamps unconditionally there. refreshIfNeeded used to set it only when the
value or write time had changed, which left the one sub-case owner-scoping exists for
(registration superseded, entry untouched) falling through to the material tail. Both halves of
the fix then failed together: the tail's by-key discard stole the successor's token, and its
setWriteTime moved the write time the successor's own ABA guard tests, so the successor's fresh
value was dropped too and expireAfterWrite was extended by the stale reload's in-flight
duration. Gating the tail's discard on preserveRefresh fixes only the first half, which is why
the bounded cache does not carry that gate; UnboundedLocalCache.remap does, because it has no
no-op exit to route to. There is no debounce lost: a write that discards a token moves the write
time anyway, and a superseded token blocks re-arming through refreshes.containsKey. Pinned by
RefreshAfterWriteTest.refreshIfNeeded_staleAfterReinsert_preservesEntry, which reaches the case
through a remove and reinsert of the same value instance, since a dead node's write time is
frozen and the completion's ABA guard therefore sees no change. A rejected completion now leaves
the entry refresh-eligible, so the next read arms a fresh reload instead of waiting out another
interval; BoundedLocalCacheTest.refreshIfNeeded_skip_discarded reads quietly for that reason.

The absent-branch steal is reachable in sync mode only: a successor refresh(k) on an
absent key registers an asyncLoad without inserting the entry, so the stale completion observes the entry absent;
in async mode the successor's get inserts an in-flight future, making the entry present so the
completion takes the reject branch instead. Don't reintroduce an unconditional by-key discard on
a non-mutating completion exit; a mutating exit keeps it (see the over-aggressive discard above).

The same sibling-sync covers a vanished-key skip: a non-creating caller (replaceAll,
computeIfPresent) whose key was concurrently removed hits remap with value == null and
returns null — a no-op, not a mutation, so it must not discard a refresh registered
after the removal (that refresh raced nothing). Both caches thread a computeIfAbsent flag
(false for replaceAll/computeIfPresent, true for compute/merge) and return early
without discarding on the absent branch when creation is disallowed. A creating caller
(compute/merge) that returns null on an absent key still discards (over-aggressive, as
above). UnboundedLocalCache.remap used to discard unconditionally on the absent+null path,
diverging from BoundedLocalCache on the replaceAll-races-remove race. The async views route
computeIfPresent, and the synchronous view's conditional writes when the key vanishes between
their quiet prescreen and the compute, through the creating compute, so they discard an absent
key's registration. That is harmless: an async registration for an absent key can only be an
orphan (the optimistic refresh inserts before registering, and eviction and expiry discard with
the entry), so a later refresh(k) starts a fresh load instead of joining it.

invalidate(k)/clear() discard an absent key's pending refresh — a purge is not a
query.
A refresh(k) on an absent key registers a reload in refreshes with no data-map
node
(sync asyncLoad only), so a remove/clear that reaches discardRefresh only through
a present-node lambda leaves the registration alive; its completion then commits (owned still
holds, null == oldValue[0]) and resurrects the key past the purge — sync-only, since an
async refresh-of-absent inserts a physical in-flight entry that remove does see.
The fix keeps remove(Object) on data.compute (not computeIfPresent) so an absent key still
enters the lambda under the bin lock and discards there — deliberately, because the refresh
completion commits under that same bin lock, so doing the discard outside it (after a
computeIfPresent miss) races: the completion can insert between the absence check and the
discard. clear() purges the whole refreshes map up front for the same reason (the node loop
can't reach node-less registrations). This is the over-aggressive-discard doctrine applied to a
purge, which is legitimate exactly as it is for present keys. Note remove(k, v) is not
extended: a conditional remove that matched nothing (absent or wrong value) is a query-style
no-op that "raced nothing," so it preserves the refresh (like remove(k, wrongValue) on a
present key). The traversal-based bulk removals (removeIf and retainAll on the key, value
and entry views) and computeIfPresent also preserve the node-less registration: the key is not
an element of any view, so they match nothing for it, and only a purge (remove(k),
invalidate, invalidateAll, clear(), compute -> null) discards it. Don't move the
absent-key discard back outside the bin lock.

A sync refresh(k) on an absent key is an isolated side-load; the async view makes it a
first-class in-flight entry — a structurally-forced divergence, not a bug.

LocalLoadingCache.refresh on an absent key registers asyncLoad only in refreshes() and
leaves the data map untouched until the completion compute inserts. So the pending reload is
invisible to a concurrent get(k), whose computeIfAbsent sees an absent map and loads again
— two loader invocations, and the refresh's value is then discarded with a phantom REPLACED
notification carrying a value that was never a mapping. The async view
(LoadingCacheView.tryOptimisticRefresh) instead side-loads via asyncCache.get(...), inserting
the in-flight future into the data map immediately, so a concurrent get joins it (one load) and
the future is joinable / invalidatable / cancellable. This is inherent: a sync cache cannot join
an in-progress computeIfAbsent from outside the bin lock, so it cannot dedup a refresh against a
get the way the async view (whose loads are visible cache entries) can. The LoadingCache.refresh
javadoc — "Returns an existing future without doing anything if another thread is currently loading
the value for {@code key}" — holds under the narrow reading (another refresh in flight; both
siblings dedup via the refreshes map) but not the broad reading (any load, including a
get-initiated one); that scope gap is intentional and left as-is. Cancellation blast radius
differs accordingly: cancelling the future from async refresh(absentKey) cancels the shared
in-cache load (all get waiters get CancellationException, handleCompletion removes the entry),
while cancelling the sync refresh future only unregisters the isolated reload and leaves a
concurrent get's own load untouched. An expired entry that maintenance has not yet removed
takes the same absent route in both caches: if maintenance removes it before the load completes,
that removal discards the sync cache's registration, so the loaded value is notified as EXPLICIT
while refresh still returns it, whereas the async view replaced the expired entry with its
in-flight load when the refresh began and installs the value. Don't try to make the sync cache
dedup a refresh against a get; pinned by RefreshAfterWriteTest.refresh_absent_sideLoad_*.

Sync getAll discards an unloaded key's refresh only on the sequential path — by design,
and it reflects a real consistency difference, not a bug.
For a key that fails to load
while a refresh is in flight (an absent key whose refresh is still pending): the sequential
path (loadSequentially, no loadAll override) loads via get(key) → computeIfAbsent, so a
null load discards the refresh; the bulk path (an overridden loadAll) loads via
loadAll outside any lock and side-loads the results with put, so an omitted key never
reaches compute and its refresh survives. This is not an oversight to unify. A single
get(k) that loads null discards (same computeIfAbsent), and sequential getAll is exactly
N linearized get(k) calls — each observes absence atomically under the bin lock, which is
the standing that justifies discarding a racing refresh. The bulk path has no such standing:
loadAll cannot run under a lock (CHM won't let us hold a bin across the load, and we can't
lock entries), so it is a non-linearizable side-load — closer to refreshAll than to a
load. Its only linearized moments are the put insert/replace points (where we match Guava,
atomic or not); the "this key was absent" observations happen outside any lock and a key may
materialize afterward (we stomp the still-missing keys but do not remove ones that appeared).
So a put, invalidate or clear() issued while loadAll runs is overridden when its results
are stored, re-caching what the loader read, where a per-key load makes the writer wait on its bin.
With no atomic absence-observation instant, it has nothing to hang a discard on. So the
sequential path discards because it can judge; the bulk path preserves because it can't —
forcing bulk to discard would impose an absence-decision onto the one path structurally unable
to make one. Don't "fix" the split; don't add discardRefresh to the LocalCache interface
for it. The asynchronous bulk load does not stomp a write to a requested key that lands during the
load: fillProxies completes the proxy the write displaced, so the caller receives the loaded
value, the cache keeps the write, and the write's REPLACED notification carries the loaded value.
Unrequested keys it returns are stored with put and do stomp such a write, or another caller's
in-flight load of that key, whose waiters keep their own result.

doComputeIfAbsent's new-node path preserves a racing refresh on a weigher/expiry throw —
by design; don't add a discardRefresh there.
It discards on a clean value return (a real
mutation) and on a clean null return (the loader's authoritative "no value" verdict, same as a
single get(k) that loads null), but a weigher.weigh/expireAfterCreate throw aborts the
creation without installing anything, so it is not wrapped in a token-clearing finally. The
rule is coherent: a clean completion (value or null) is an authoritative verdict that discards a
racing refresh; a throw is an aborted op with no verdict, and an independent in-flight refresh
may still legitimately populate the absent key, so it is preserved. This is asymmetric with
remap's create branch, which does discard on a throw (try {…} finally { discardRefresh },
the #1970 fix) — but only because remap doubles as the refresh-completion path and must
self-clean its own token on every exit; it can't tell a user compute from a completion, so it
discards uniformly (a doctrine-safe over-discard). That uniform discard is scoped to the exits a
completion can reach — the present-entry catch and the create-branch finally; the
absent-branch user-function throw precedes any materialization and no completion throws there,
so both siblings preserve it. UnboundedLocalCache.remap's catch used to discard on that path
too (a blanket catch (Throwable) from the "unbounded compute throws" fix whose intent was the
present-entry parity); it was narrowed to if (value != null) to match BoundedLocalCache,
which never had an absent-branch catch. doComputeIfAbsent is only ever a user load,
so it can afford the correct behavior. Adding the finally here would make a failed
computeIfAbsent also abort an unrelated legitimate refresh — the inverted direction. Pinned by
BoundedLocalCacheTest.computeIfAbsent_absent_weigherThrows_keepsRefresh (extends the
already-adjudicated "mapping-function throw doesn't discard is correct" to the weigher/expiry
throw).

Quick reference — discardRefresh across the compute family (verified consistent, both
siblings). compute/merge/computeIfPresent/replaceAll all route through remap;
computeIfAbsent uses doComputeIfAbsent (BLC) or a direct data.computeIfAbsent lambda (ULC).

Verdict remap (compute · merge · computeIfPresent · replaceAll) computeIfAbsent
clean value (mutation) discard discard
clean null — present (removal) discard discard
clean null — absent (no-value verdict) discard discard
same-instance no-op (preserveRefresh) preserve preserve (returns existing)
user-function throw — present discard discard
user-function throw — absent preserve preserve
weigher/expiry throw — absent create discard preserve

Cross-cutting rules layered on top: non-creating callers (computeIfPresent/replaceAll) on a
vanished key take the computeIfAbsent=false early return and preserve; refresh completions and
async-view no-ops set preserveRefresh so a stale completion can't steal a successor's registration
(owner-scoping); invalidate/clear discard an absent key's refresh (a purge, not a query). The
only intentional split is the last row — remap doubles as the completion path and self-cleans,
doComputeIfAbsent is a user-load-only path (and ULC, having no weigher/expiry, has no such case at
all). The unbounded remap catch is guarded by value != null, preserving an absent
user-function throw like the bounded implementation.

The jcache adapter deliberately does not get this narrowing: RemapHints does not cross the
package boundary, so the adapter's query-style operations (a failed putIfAbsent, a NONE-action
entry processor) do discard an in-flight refresh and reset the write time. Adjudicated won't-fix —
see .claude/rules/jcache-adapter.md.

Async Synchronous View

AsyncCache.synchronous().asMap() queries are logical, mutations are
physical.
containsKey, get, iteration, and containsValue treat in-flight
entries as absent (Async.isReady / Async.getIfReady). But KeySet.remove,
removeAll, removeIf, retainAll, and EntryIterator.remove operate on the
raw delegate map without blocking on in-flight futures. Blocking everywhere
would invite deadlock, and unlike values()'s removals and entrySet().removeIf/retainAll,
which skip an in-flight mapping, these discard it. That is deliberate: the view is best effort,
and it is better to discard too much than to keep stale contents on linearization assumptions
the async cache cannot support. keySet().contains(k) != keySet().remove(k)
on a loading entry is accepted. size()/isEmpty() are physical too — they delegate straight to
the backing map and count in-flight entries.

Value-conditional CAS ops are the exception to "raw", and it is CHM-faithful. remove(k,v),
replace(k,v), replace(k,old,new) and the compute/merge family block on an in-flight
future (resolved outside the compute, then CAS inside), mirroring CHM where a mutation must
take the bin lock an in-flight compute holds: in-flight is absent to reads but blocks
mutations as if present. Only the key-based removals above are raw. Bulk collection-view ops
split along the same line: values().removeAll/remove/retainAll are value-searches that skip
in-flight via the getIfReady filter (like CHM ValuesView.remove), so they do not block on a
mapping in flight when scanned (one replaced by a pending future between the scan and its
conditional remove(k, v) still waits in remapWhenSettled), while
entrySet().removeAll's iterate-argument branch routes through the blocking remove(k,v) (like
CHM EntrySetView.remove → map.remove(k,v) → bin lock) so it can. It iterates the argument only
when the keys are strong and the argument is not a Set larger than the physical size(), which
counts in-flight entries, so whether it waits for and removes an in-flight entry depends on how many
other loads are in flight. Don't "fix" that asymmetry —
and a test on the blocking path must coordinate threads (complete the future off-thread), as the
_async conditional remove/replace tests do.

A logical read can return the value of a superseded future — the synchronous
view is not linearizable.
Each read is a two-step composite (map read, then
Async.getIfReady unwrap) with no re-validation between. If the future found in
step one is in-flight, is superseded by a concurrent put/invalidate, and then
completes, the resumed unwrap returns that future's value even though it was the
mapping at no instant in the reader's window (the completion's identity-conditional
replace/remove no-ops once the future is unmapped). Only the raw
AsyncCache.asMap() view over the futures is linearizable; the synchronous view
reads "the future it found." Double-collecting (re-reading the mapping after the
unwrap and returning null on change) would close it but adds a map re-read to every
sync-view hit for a narrow, non-linearizable-by-design corner. Don't add the
re-check guard.

The same two-step shape is AsMapView.computeIfAbsent's optimistic prior-future
branch, the one exit in its retry loop that does not re-read the mapping. A future
removed and completed while the caller sits between the map read and the isDone
test yields that future's value with no mapper call, where an unpaused caller would
have waited on the pending future, looped to getIfPresentQuietly, found the key
gone, and computed. Same adjudication: the value came from a future that was the
mapping when it was read, an in-flight mapping counts as present for
computeIfAbsent under the coalescing rule below, and ConcurrentHashMap promises
nothing about a mapping surviving the return either.

The compute variants adopt the found future too — synchronous().get(k, func) /
getAll load-coalesce, they do not recompute.
The present-branch of
LocalAsyncCache.get returns any in-flight future it finds (no isDone/isReady
screen) and AbstractCacheView.resolve joins it, so the caller's function never runs
and the future's outcome is adopted — a null result, a rethrown foreign load
exception, a raw CancellationException (resolve doesn't unwrap it), or an
indefinite wait on a stuck future. This diverges from sync Cache.get(k, func) (=
computeIfAbsent, the ConcurrentHashMap family, which recomputes when the prior load
left no mapping) and from the same view's asMap().computeIfAbsent (whose retry loop
recomputes on null/exception/cancel). That is not a bug: Cache.get/getAll promise
neither model, load-coalescing is a legitimate policy (Guava's LoadingCache coalesces;
CHM does not), and the synchronous view cannot fully emulate the async view's
linearizability regardless — it "reads the future it found" for computes as well as
reads. Whether coalescing is better or worse is perspective-dependent; the point is only
that it differs. Don't "fix" get(k, func) by routing it through
AsyncAsMapView.computeIfAbsent's retry loop.

The adopted outcome includes the caller's own failure. A failed future stays mapped until its
completion's dependent logs the failure and removes it, and the caller's join can return while
the completing thread is still in that dependent. An immediate retry, through the view or from a
dependent on the future, then rethrows the same exception without calling its function, in 85-99%
of immediate view retries on defaults. Computations are not special here: a future passed to put
that fails is visible to the same reads until its removal runs, which is why the public javadoc
does not name a failed computation's window. Removing the failure in the view before rethrowing
was declined: it helps only that caller's retry, since any other call in the window still finds
the future, and "the mapping is left unestablished" holds once the removal runs.

Read bookkeeping happens only at the lookup that finds a ready value. asMap().putIfAbsent
and computeIfAbsent record the access (access time, read expiry, frequency) on their first
lookup. A value they obtain by waiting on a load, or that their atomic step finds after that lookup
missed, is returned without it, so a variable Expiry sees no expireAfterRead until the next
read. Waiting matches Cache.get(k, func) and the future-typed API, neither of which applies read
expiry to an in-flight future. The raced exit is the one place a ready value skips the read
(Cache.get goes through computeIfAbsent, which reads the entry it finds); covering it needs a
second map read or a remap hint honoured by both caches, for a window between two steps.

A write or read in the finalization window is accounted as meeting an in-flight load. The
view's caller has the loaded value once the future completes, but handleCompletion's quiet
finalization runs in a dependent that can follow it, and until then the node carries the async
sentinel. A put there is dated by expireAfterCreate rather than expireAfterUpdate, a read's
expireAfterRead is dropped, and the loaded value's own creation is then skipped or applied over
the read. Only which Expiry leg dates the entry differs; the mapping, listener and weight are
correct. Telling a delivered value from an in-flight one needs a readiness probe on every write
and read.

In the same window the age and remaining-time accessors (ageOf, getExpiresAfter(key)) return
empty and every weight surface reports 0, while the value reads, getEntryIfPresentQuietly and
the snapshots report the entry present with its current deadline, which under variable expiry is
the insertion time plus ASYNC_EXPIRY. Both are deliberate: the entry cannot expire before the
handler runs, and its creation expiry has not been computed; hiding the entry would make
getEntryIfPresentQuietly disagree with getIfPresentQuietly, and a clamp would still report
about 146 years. Pinned by the ageOf_async_pendingCompletion tests and
ExpireAfterVarTest.getExpiresAfter_async_pendingCompletion.

Async Put Re-registration

AsyncCache.put(k, future) re-registers a completion handler whenever the prior
mapping differs by identity, so re-inserting an already-registered future
double-fires handleCompletion.
The dedup in LocalAsyncCache.put only skips a
consecutive same-instance put (prior == castedFuture), so put(k, f1); put(k, f2); put(k, f1) leaves two whenComplete handlers on f1. A single
completion then replays replace + recordLoadSuccess once per handler. Accepted,
because there is no correct dedup: we cannot inspect a future's already-registered
dependent actions, and tracking our own registration history would be wrong (unbounded,
and stale the moment the entry is replaced). It is benign regardless. Re-inserting a
specific future instance after replacing it is unusual, and the second
replace(k, f1, f1) is idempotent: notifyOnReplace suppresses on identity, and the
quiet write preserves the expiration because the first handler already cleared the async
sentinel, so the user's Expiry sees one creation rather than a creation and an update.
The load is still counted once per handler. Pinned by
AsyncCacheTest.put_reregisteredInstance_completionRegisteredTwice.

Failure cleanup is scoped to the future, not to each insertion. A deferred finalizer that
fails its weigher or expiry removes (key, future) conditionally. If the same completed future
was removed and reinserted meanwhile, that cleanup can remove it again: reinsertion does not
create a new computation or cleanup owner. A distinct successor survives, as pinned by
AsyncCacheTest.handleCompletion_weigherFails_preservesConcurrentReplacement and its getAll
twin. Do not add an insertion-generation token for the same-future case. Repurposing that future
with obtrudeValue does not establish an independent computation lifetime either; use a distinct
future for that. This does not change the intentional bulk-proxy obtrusion below.

A cancelled bulk proxy stays mapped, and the completer re-asserts its lifecycle.
getAll installs a proxy per absent key and gives its lifecycle to AsyncBulkCompleter
rather than to handleCompletion, so a caller who cancels the mapped proxy leaves the
entry in place. Measured: the entry stays mapped and counted while queries filter it, a
get in that window adopts the cancelled future instead of starting a fresh load, and
once the load settles fillProxies obtrudes the value onto that same future, leaving the
entry holding it and the proxy no longer cancelled. The single-key path differs because
its handleCompletion removes an entry whose future completed without a value, so
cancelling there is self-healing.

Accepted 2026-08-16 as the least bad of unsatisfactory options, and the reasoning is what to
re-read before proposing a change. Cancellation does not stop the computation, so the value
still materializes, and a cache that dropped the entry on cancel would have no way to hand that
value to a removal listener for cleanup, where an entry removed while in flight does have a
listener attached to notify. A later refresh(key), invalidate(key), or synchronous-view
write such as putIfAbsent or compute still treats the cancelled proxy as a completed
valueless mapping and replaces or removes it, so in that sequence the bulk value reaches
neither the cache nor the listener, as after a per-key cancel. Cancelling says downstream
chained actions may be abandoned, which still happens; removing the mapping directly says the
value is not cacheable. Obtruding is then the cache treating the value as having materialized
elsewhere and re-asserting its own lifecycle over it. Removal-on-cancel is not free either: the
cancellation happens outside the cache, so it takes a dependent action per proxy, and it would
leave the completer's replace unable to install the loaded value. CompletableFuture offers
no API that satisfies every case and there is no clean decision matrix, so don't add
cancel-aware completion logic to the bulk path, and don't screen the cancelled future out of
LocalAsyncCache.get's present branch, which the adopt-the-found-future decision covers.

TimerWheel

Wheel resolution is one level-0 tick: 2^30 ns (~1.074s). An advance within a tick
(for example 0 → 1) yields delta = 0; crossing -1 → 0 after the wrap-bias repair crosses
a rebased boundary and yields 1. Hashed wheels process whole ticks to retain O(1) insertion
and deletion; see research foundations on timing wheels.

Queries filter an expired entry immediately, but physical removal and its notification can
wait until the next tick. On a fake ticker, expiries of 1ns, 1ms, and 500ms stayed resident
after cleanUp; setExpiresAfter(k, 0) was reaped at 2^30, not 2^30 - 1.
The bound is one tick, not a whole wheel revolution: expire scans [start, start + delta]
inclusively (steps = 1 + delta) and checks each node's deadline. getIfPresent and
containsKey filter without removing; the compute path (get(k, fn) through remap) reaps.
Fixed expiration is more eager because its deque has no tick boundary to wait for.

That bound assumes a deadline that has not moved since the node was scheduled. A
variable-expiry read applies its new duration through tryExpireAfterRead's CAS and leaves the
matching reschedule to onAccess, which runs only when the read buffer accepted the node. A
dropped offer leaves the node in the bucket its previous deadline chose, so a shortened deadline
waits for that bucket's sweep. Measured with an Expiry of 1h on create and 2s on read, 400 keys
read once each on the default executor and system ticker: 208 to 368 keys across five trials kept
their original bucket and none were readable, and the deterministic fake-clock arm reaped them with
EXPIRED at 3574s against the 3s they had asked for. A Scheduler does not shorten the window,
because getExpirationDelay reads bucket occupancy rather than deadlines and reports the next flush
3569s out. The delay ends at the sweep of the previous deadline's bucket, at or before that deadline,
so a dropped read never makes an entry outlive the schedule it already had; a lengthened deadline
self-heals the same way, its node sitting in an earlier bucket whose sweep pushes it forward.
Accepted: the node reference is gone once the offer is dropped, routing the reschedule onto the write
buffer would put most reads of such a configuration on the MPSC queue, and declining the CAS instead
would leave the entry readable past the duration the user asked for.

setExpiresAfter reschedules through the write buffer only for a shortening or for an extension
beyond the 1s tolerance.
A smaller extension relies on the same self-healing, so oldest() and
youngest() order the entry by a deadline at most a second stale. A shortening keeps the write
buffer: a lost reschedule would leave the entry resident past its new deadline. The JCache adapter
calls setExpiresAfter on every access-expiry hit, and the UpdateTask made each hit pay what a
write pays. Alone, the skip took such a hit from ~2.2 us to ~195 ns on 8 threads. Beside the
adapter's own millisecond skip it saves ~25 ns more. Pinned by
BoundedLocalCacheTest.expireTolerance_setExpiresAfter and
ExpirationTest.setExpiresAfter_reschedules.

The resolution is accepted for a cache's maximum-lifetime contract. It aligns with the 1s
expiration tolerance and pacer's minimum delay; a bounded cache can also reclaim by size.
An eager current-bucket sweep remains a possible optimization, not an approved change.
It costs O(bucket occupancy) per maintenance cycle: detach onto pending, deschedule, and
reschedule every not-yet-due node, instead of paying once per tick. Measure that cost before
removing delta <= 0; a nearest-bucket heap is another structure for stricter timer resolution.

expire detaches its bucket onto a field-backed, circular pending sentinel. Detachment
prevents a not-yet-due node from being rescheduled into the list being drained, especially on
the last wheel's single bucket. It does not prevent a nested RemovalTask from descheduling
an entry it already holds. A stack-local detached chain used to strand the remainder when
that operation cleared the carried successor's links. Preserve all three protections:

  • The chain lives on a field (pending.next), not a local, so a nested unlink of the head
    repairs the walk's anchor through the ordinary path with no special case. The loop must re-read
    pending.getNextInVariableOrder() each iteration rather than carry next across the callback.
    This is the leg advancing cannot cover: a nested deschedule/reschedule arrives via
    drainWriteBuffer → RemovalTask/UpdateTask and never calls advance. Verified by keeping
    advancing while restoring the stack-frame detach — the regression test fails identically.
  • Both lists stay circular. A linear or sentinel-less chain breaks reschedule's
    getNextInVariableOrder() != null scheduled-check (a tail with a null next reads as
    unscheduled, so it gets linked into a bucket while still chained) and makes unlink a silent
    no-op on the tail. The invariant is scheduled ⟺ non-null links, which is also why the in-flight
    node's links are nulled before it is processed. A self-closing ring fixes the live-bucket splice
    but not the stranding — verified by building it and watching the regression test still fail.
  • advance defers while advancing. A recursive advance would append its bucket to the same
    list and splice the combination into its own bucket, and — the leg that matters more — it moves
    nanos forward underneath the in-progress caller, which then rewinds or skips buckets and leaves
    entries permanently unexpired. It returns before touching nanos, so the nested call is a true
    no-op rather than skipping a time span. The predicate must be the explicit flag, not "is
    pending non-empty": the outer walk empties pending the moment it unlinks its last node, so
    the inferred version lets a nested advance through in exactly that window. That was a real
    defect in the first cut of this fix, found by the fuzzer in 559 runs.

Pinned by BoundedLocalCacheTest.maintenance_recursive (a removal listener on a same-thread
executor invalidating a sibling and calling cleanUp()).

getExpirationDelay returns when a bucket is next flushed, not when an entry is due. A flush
either expires the entry or cascades it towards a finer resolution, so the wake-up is needed even
when nothing is due at it. expire covers ticks [previousTicks, previousTicks + delta], so a bucket
at offset b flushes at (b << SHIFT[i]) - (nanos & spanMask) and the current bucket — whose tick
has already passed — flushes at offset 1, one tick out, not a full SPANS[i]. Both places that
compute this must use that offset-by-one:

  • The main scan clamps with Math.max(1, j - start). It previously fell back to a whole SPANS[i]
    when the current bucket was occupied and returned before probing start + 1, hiding an event one
    bucket ahead that then fired up to a span late (68.7s / 1.22h / 1.63d on wheels 1–3; wheel 0 is
    absorbed because both values are ≤ Pacer.TOLERANCE). Reachable because findBucket chooses the
    wheel by duration but the index by absolute time: a duration within nanos & spanMask of
    SPANS[i+1] wraps a full revolution onto the current bucket, so a far-future entry masks an
    imminent one. Don't restore the delay > 0 ? delay : SPANS[i] ternary — with buckets >= 1 the
    result is always positive, so the guard is dead.
  • peekAhead checks the higher wheel's current bucket as well as ticks + 1; both flush at the
    same instant. This leg is benign on its own — only wrap-arounds land in a wheel ≥ 1 current bucket
    (measured: over 300 random wheels no entry there is ever due before that wheel's next tick), so the
    missed flush would only re-file an alias. It is honored anyway so the derivation "current bucket ⇒
    only aliases" isn't load-bearing, and so the contract is assertable.

Partial-drain remainders are not affected — expireVariableEntries re-arms via
PROCESSING_TO_REQUIRED, so the backlog never depends on this delay. Pinned by
TimerWheelTest.getExpirationDelay_occupiedCurrentBucket and by getExpirationDelay_fuzzy, which
asserts exact equality against an oracle scanning every bucket of every wheel. That fuzzy
assertion was previously vacuous — it guarded the assert with its own condition and compared an
absolute variableTime against a relative delay — which is why the defect survived the fuzzer.

The VarExpiration traversers list wheel 0's current bucket last. It holds the entries due
within the current tick and aliases a revolution away, so oldest() lists the former after entries
due up to a minute later, and youngest() lists them first. That is accepted under the views'
"roughly ordered" contract. Starting at the current bucket misplaces the aliases instead, and
splitting it into two visits must classify each timer once, since a concurrent read can move a
deadline between the visits and list the entry twice or not at all; the buffering that needs is
not worth it for a best-guess order. The traversers are also wheel-major and unsorted within a
bucket, so they can invert two entries by up to one span of the higher wheel.

Interner drainKeyReferences does not need a value-identity check. Unlike values, keys on
interned nodes never rebind. Cleared references now compare equal only to themselves. In the old
implementation, hash-colliding cleared keys could alias, but both polls still evicted both nodes;
only attribution swapped, unobservable with uniform Boolean.TRUE values and no listener.

Builder and bulk loading

LoadingCache.getAll is not atomic. Valid entries may commit before failure and are not
rolled back. Per-key null results omit that key; bulk maps must omit a key to mean no value.
An explicit null value fails the bulk load in both sync and async paths, matching Guava's failure,
though not its residue: Guava first stores every non-null entry, where the synchronous path keeps
only the entries before the first null and the asynchronous path keeps none.

Caffeine's builder mirrors Guava's CacheBuilder validation shape, asymmetries included.
refreshAfterWrite(long, TimeUnit) checks the unit before state and requires duration > 0;
expireAfterWrite / expireAfterAccess do not check the unit first and allow zero.
Consequently expireAfterWrite(-1, null) throws IllegalArgumentException rather than NPE.
Changing either order diverges from CacheBuilder and the adapter's compatibility suite.

Caffeine.from(CaffeineSpec) disables strict parsing, matching CacheBuilderSpec and
allowing programmatic overrides such as a weigher after maximumSize. The possibility of
accidentally disabling eviction is accepted.

Serialization

The SerializationProxy captures configuration only, and intentionally drops
executor, scheduler, and custom StatsCounter suppliers.
Threads and
executors are runtime state, not serializable configuration; the deserialized
cache uses the defaults (common pool, disabled scheduler, default counter),
matching Guava's proxy behavior. Don't propose capturing them.

Serialization is same-version only, and that is not a gap to close (ruled
2026-08-15). The library never promises cross-version compatibility, and neither
does Guava; a promise of that kind has to be offered by the contract rather than
expected of it. So a finding of the shape "a stream written by an older release
deserializes into a broken or silently wrong cache" is closed on principle, not
on reachability or on whether the failure is loud. No serialVersionUID bump, no
readObject defaulting, no wire-format tolerance shim, no release note, no
javadoc qualifier. This covers SerializationProxy, CaffeineConfiguration, and
any serializable type added later.

Proxy field names and sentinel values are still wire format within a version.
Two changes broke cross-version streams: the loader → cacheLoader rename
(3.0.4) and the 0 → UNSET_INT sentinel change for the three duration fields
(3.2.4, the zero-duration fix). The reason to know this is not compatibility but
debugging a bug report: streams from ≤ 3.2.3 carry literal 0 for unset
durations, and a field absent from an old stream deserializes to the JVM
default (0/null), because field initializers do not run during
deserialization.


.Claude Docs Finding Taxonomy (`.claude/docs/finding-taxonomy.md`)

Finding Taxonomy

Standard classification for all audit and review findings. Referenced by the
auditor agent and all /audit-* and /review-change skills.

Severity

Level Definition Example
critical Violates correctness under any valid interleaving Data race causing lost update
high Violates contract under specific conditions ConcurrentMap postcondition broken on retry path
medium Correctness preserved but fragile Works only because of current CHM implementation detail
low Style or robustness concern Missing null check on code path unreachable today

State the affected configuration separately from the harm within it. Being non-default or in a
lower-priority module does not establish a severity ceiling; apply the module's stated use-case
scope and price the demonstrated impact.

Categories

Category Scope
memory-model JMM access mode violations, missing happens-before edges
state-corruption Weight divergence, lifecycle violations, deque corruption
specification ConcurrentMap/Map/Cache/Policy API contract violations
resource-leak Unclosed resources, unrecoverable state after Error
notification Missing, reordered, or duplicated removal/eviction notifications
exception-safety Incomplete cleanup on exception paths, phantom state
api-surprise Public method returning nonsensical values for valid inputs
performance O(n) on O(1) path, unnecessary allocation on hot path
redundancy Removable work or state with a behavioral-equivalence proof and a concrete source simplification
liveness Progress/termination failure without corruption: stranded drain status, lost wakeup, unbounded stall
external-contract Misuse of a third-party or JDK API contract (dispose-on-error, merge-less collectors, live-view iteration)

Severity must be priced on a realistic configuration

A severity is a claim about what a user can reach, so the reproduction has to run the
configuration a user runs. The test harness is built out of instruments that exist to make
behaviour deterministic, and each of them can manufacture an impact nobody can reach in
production:

  • A frozen FakeTicker. Several mechanisms self-heal simply because the clock moves.
    TimerWheel.advance sets nanos = currentTimeNanos unconditionally, so with
    Ticker.systemTicker() any operation reaching maintenance invalidates a live wheel iterator.
    Freeze the ticker and that detector stops working, which turned a truncated Policy snapshot
    into a reproducible non-terminating traversal holding evictionLock. With a real ticker the
    same scenario throws. The wedge was an artifact of the instrument.
  • executor(Runnable::run) or CacheExecutor.DIRECT. Inline maintenance removes the
    coalescing that hides per-operation cost and turns an asynchronous drain into a re-entrant one.
    Use it to control scheduling; its timings do not price default-executor behavior.

Before assigning high or critical, re-run the witness with the defaults a user gets: the
system ticker and the common pool. If the impact depends on harness effects absent from the
claimed user configuration, identify those effects and do not attribute them to that configuration.
A mechanism that is real and an impact that is reachable are two separate claims, and the second
is the one severity encodes.

Confidence

Level Meaning
high Concrete interleaving or input constructed; reproducible
medium Plausible scenario but depends on timing or conditions not fully verified

Omit low-confidence speculation entirely — it wastes triage time.

For redundancy, high confidence may rest on a complete source equivalence proof covering the
relevant exits, callers, and observations. Keep cleanup severity low and report test execution
and runtime effects separately. A measurable speedup or broken contract is not required; do not
invent one to justify the cleanup.

Classification (for triage)

Label Meaning
patch Real issue, fixable in the current change
defer Real issue but pre-existing, not introduced by this change
reject Noise, false positive, or explained by design decisions
escalated Cannot determine statically; needs dynamic testing

.Claude Docs Hill Climber (`.claude/docs/hill-climber.md`)

The adaptive window climber

The deep reference for WindowClimber, the window-sizing loop BoundedLocalCache drives from
climb: what it must do, why that is hard, the shipped design, and the graveyard of alternatives
with the reasons that killed them. Read this before touching determineAdjustment, the climber
constants, or the simulator's climbing package. The quick rules live in rules/design-decisions.md.

This is a design record, not a project log. Every entry answers one of two questions — what the
machine does and why, or what was tried and why it failed — and anything that only answered "what
happened on a given day" has been removed. Where a number appears it is load-bearing: it is the
price of a step, the bar a family is held to, or the measurement that settles an argument. Numbers
that can be re-derived by running the gate battery are not kept here.

wiki/adaptive-window.html is the companion design document, written for an external reader judging
the algorithm and its evidence. Internal QA logistics — test and fuzzer names, mutation baselines,
gate tooling — stay out of it and live here and in rules/testing.md.

Naming: the window climber is the whole controller (WindowClimber); its tiers are each named
for their steering signal — the reactive climber (≤4096, cross-sample ΔHR) and the density
climber
(>4096, within-sample density ratio). The density tier in full is a goal-audited density
climber
: density steers, probes rescue its blind corners, and the goal-metric layer (anchor, guard
rail, equilibrium audits) polices what density cannot judge. "density arm/tier" below always means
the steering component, not the whole machine.

The organising result of the original failure atlas — the steering rule rests where
capacityShare = hitShare, not at the hit-rate optimum — is §2.1's identity, and the
marginal-steering thread it opened is settled in §5.

1. The problem

W-TinyLFU splits the cache into a recency-serving admission window (LRU) and a frequency-gated main
region (SLRU behind a TinyLFU filter). The best split is workload-dependent and non-stationary:
recency-heavy workloads want windows of 20–80%, frequency-heavy ones want ~1%, and real traces
switch regimes. The climber's job is to track the optimal split online using only signals the cache
can observe about itself, at zero cost to the request path.

Practical comparators include LRU, static windows and ARC on the same access stream, with MiniSim
testing what additional observation state can reveal. The objective is broad workload quality
within the cache's cost budget, not a universal policy optimum. Losses to these references remain
generic headroom; richer application metadata cannot explain them away.
A specialized policy may do better using context or structure, such as request category, turn and
prefix depth. OPT/Bélády instead knows future accesses. A gap to that oracle proves neither that
an online feature can recover the gap nor that learning such a feature must be expensive.

Inferring useful reuse structure from generic observations is a separate, testable question. It
requires bounded state and work, enough labels during cold start, an observation stream available
in the cache, and controls that distinguish persistence from scans and changing workloads.
The failed skew classifiers (§5's opening) do not rule out all inexpensive inference. Arbitrary
key hash values do not supply application semantics: do not treat them as addresses,
request categories or turns. Specialized contextual policies and generic adaptivity therefore
need separate evidence, while sharing the same discipline for information, action and cost (§6).

Constraints that shape everything:

  • The counterfactual is invisible. The cache observes only what its current split earns.
    What a bigger window would have caught requires ghost state (ARC's B1/B2), which Ben rejected:
    heavy, implies untrue things, and does not pay off as caches grow.
  • Moving has real cost. A resize transfers nodes between regions (capped per maintenance cycle
    by QUEUE_TRANSFER_THRESHOLD with a multi-cycle carry-over) and displaces resident entries.
    On corda_large the churn alone cost 10.5pp — every fixed window scored 33.33 while the moving
    climber scored 22.8.
  • The goal metric is noisy at exactly the wrong times. A phasey workload swings the sample hit
    rate ±10–20pp for reasons unrelated to the window, burying the window's marginal effect (often
    under 1pp) — a cross-sample hit-rate comparison then chases the workload's mood.
  • There is no setpoint. Control-theoretic machinery (PID and friends) needs an error signal
    toward a target; hit-rate climbing has no target, so integrators accumulate noise unboundedly
    (the 2026-05 PID catastrophe: corda_lg −32 to −98pp, unfixable by any anti-windup variant).

2. The signals

  • Cross-sample ΔHR (the reactive climber, ≤4096): bold-driver — keep direction while the hit
    rate improves, reverse otherwise, decay the step, re-anneal to a 6.25% step when |ΔHR| ≥ 5pp
    (RESTART_THRESHOLD). Goal-driven, so it can be confused but never persistently
    wrong
    — the restart keeps it exploring. Weaknesses: churns on flat/phasey curves (corda), and
    cannot cross wide plateaus deliberately (bold-driver flips on any sub-noise negative ΔHR, turning
    a plateau crossing into a random walk).
  • Within-sample density ratio (the large tier's primary signal):
    error = ln(windowDensity / mainDensity) with windowDensity = sample.windowHits/windowMaximum
    and mainDensity = (sample.hits − sample.windowHits)/(maximum − windowMaximum); step
    sign(error) · min(0.30·max, |error|·0.03·max), window floored at 2% of max. Both densities are
    measured in the same sample, so the workload's phase cancels in the ratio — this is what makes it
    immune to the swings that defeat the reactive climber, and why it converges recency workloads to
    their static-window ceiling (OLTP 1%→~21%, metaCDN, fiu_homes, corda's collapse fixed).
  • What density cannot see: it is resident-only and measures average (not marginal) value.
    Two consequences: (a) bias — window hits are cheaper to earn, so frequency-optimal traces
    give back ~0.5–2.5pp typically (worst observed 4.3pp: websearch2@4M holds a ≳10% window where
    static-1% ≈ reactive wins, a fresh-holdout find; all still beat LRU by 11–61pp,
    that cell by +24); (b) blindness —
    a region earning ~nothing has an unmeasurable density, and the signal will happily hold or push
    it further into nothing forever.
  • Signal hygiene (what feeds the counters): hits are recorded in onAccess (read-buffer
    drained, so real hits are ~5% lossy single-threaded — the drain was historically triggered only
    by the offer after the fill; the buffer now reports full on the filling offer so the loss is
    recovered), misses in AddTask. Only genuine usages may count: async load completions replace
    quietly (no sketch increment, no hit counters — the UpdateTask only finalizes weight/expiry).
    When loud, each async miss injected one synthetic, write-buffer-lossless, window-attributed hit
    plus a doubled per-load admission frequency: the reactive tier wedged on phase shifts
    (corda+loop @ 512 −12.7pp, deterministic) and the density tier read miss-heavy async traffic as
    window-dense (w50 −38.6pp). Any new write-path task that calls onAccess must decide
    loud-vs-quiet explicitly — user-initiated writes are loud, internal bookkeeping is quiet.

2.1 The control-theory map

The density tier was derived from measurement, not from a textbook, but it lands on a named
architecture: a supervisory switching controller with hysteresis and state-dependent
average dwell time
, wrapped around a certainty-equivalence inner loop, with an explicit
dual-control probing term — an event-triggered, non-dithering extremum-seeking scheme.
The vocabulary is worth carrying because three of the design's empirical facts fall out of it as
derivations, and because it names what a proposed change would be breaking.

Mechanism Formalism Reference
DensityClimber.steer on ln(d_w/d_m) certainty-equivalence inner loop; P-on-error into an integrating plant = I-on-the-balance-condition Åström & Wittenmark
the log ratio itself exponentiated-gradient step on the capacity simplex (scale-free by construction) Kivinen & Warmuth 1997
its rest point vs. the optimum average- vs. marginal-value equalization Stone, Turek & Wolf 1992
anchor + guard rail hysteresis switching; VETO_MARGIN_MIN is the constant h Morse, Mayne & Goodwin 1992; Hespanha, Liberzon & Morse 2003
VETO_STREAK, AUDIT_COMMITMENT, Ladder.commitmentDepth, Anchor.freshLeft dwell time Morse 1996/1997; Liberzon 2003
the doubling ladders average dwell time with chatter bound N₀ Hespanha & Morse 1999
probes and audits dual control — steering has no dual effect, the probe supplies it Feldbaum 1960–61; Bar-Shalom & Tse 1974
the probe verdict (retain unless refuted) unfalsified control — no plant model, data from the active controller only Safonov & Tsao 1997
starvation bar loss-of-persistent-excitation detector Boyd & Sastry 1986
confirm / veto streaks sequential change detection Page 1954; Basseville & Nikiforov 1993
terrain B, the moat, the rung-scaled stride non-local stability of extremum seeking (escape needs large-amplitude excitation) Tan, Nešić & Mareels 2006

Three consequences worth stating outright, because each replaces a fact currently carried as
folklore:

  1. "There is no setpoint" is true of the goal metric and false of the loop. The command is a
    change in window size and the cache accumulates it, so the plant is a pure integrator and
    proportional-on-error is integral action with setpoint error = 0. The 2026-05 PID
    catastrophe was not "integrators explode here" — it integrated HR − target, an error whose
    zero was invented, through a plant that already integrates. The density arm integrates an
    error whose zero is a physical balance condition. The design decision was to move the
    integrator onto a signal that has a true zero, which is why no anti-windup variant could
    rescue the old one.
  2. The rest point has a name and the recency bias has a derivation.
    error = ln((H_w/C_w)/(H_m/C_m)) = logit(hitShare) − logit(capacityShare), so error = 0
    ⟺ capacityShare = hitShare: average-value (proportional-share) equalization. The
    optimum equalizes marginals, H'_w = H'_m. For hit curves concave through the origin,
    average ≥ marginal, and the region with the larger average−marginal gap is over-allocated;
    the window is an LRU serving tight reuse, so it saturates fastest and has the larger gap.
    That derives the documented give-back ("frequency-optimal traces give back ~0.5–2.5pp") and
    its direction. It also says what the goal-metric layer is: not a patchwork, but the
    correction term for a bias with a closed form. Terrain shape B is simply non-concavity, where
    any first-order matching rule rests self-consistently — F-4 is the definition, not a surprise.
  3. The ladders imply a duty cycle, and the duty study measured the real one.
    Worst-case arithmetic (a 17-sample walk against each rung) overstates the machine 2–4×: over
    6193 density samples on the 49-cell battery the mean armed excursion is 4.2 samples
    (starvation) and 7.7 (audit), and the occupancy split is steer 42.2%, park 25.9%, audit
    excursion 19.0%, starvation excursion 5.9%, blind-corner hold 6.1%, undo 1.0% — the
    machine commands nothing (32.0%) more often than it explores. (R4-F1's "6% → 43%" shieldtrap
    occupancy was the round-3 clock; at the shipped tree that cell reads 13.4% park + 13.4%
    walk.) A crash-prone workload sustains
    audit duty indefinitely (~11% at cycle ≈7.7+64: crashnoise_a12, whisper_mod_a12,
    h4c1_attack), because the wait reaches 512 only through completed deepest-rung failures
    while a crash deliberately keeps the ladder cadence — R4-F1's shape, structural. Two
    refutations from the same study: the constants are not derivable from one declared duty
    number
    (a declared audit band recovers two of the four), and no duty or dwell bound can
    see the deferral defects
    — H4-C1 and adv4-F2 violate the schedule's provenance, not its
    magnitude (maxOverdue measures 0 through the whole 130-sample H4-C1 pin). What the duty
    budget does generate is the C2 progress clause (§6).

Two caveats so the map is not over-claimed. Morse's hysteresis lemma bounds the switch count
only for monotone or exponentially discounted monitoring signals; Rates.smoothed is a
symmetric EMA whose Anchor.rate re-syncs on-anchor, so the finiteness bound does not transfer
as written (Hespanha–Liberzon–Morse's discounted monitoring signals are the standard repair, and
would give a stated bound on anchor churn). And "scale-independent hysteresis" in that paper is
the multiplicative form μ_p(1+h) < μ_q; the rail is additive with a noise-adaptive margin —
related, not the same.

The map also predicts the rail/audit split the F1 study found by measurement: in dual control the
cautious branch is conservative in the loss metric while the probing branch is optimized
for information, so the two want opposite pricings and must not share one margin. That has now
found three instances, the last of them inside a single walk: the rail's margin against the
audit's confirming streak (F1), the starvation probe's bar against the audit's (adv3), and the
audit walk's own crash abort against its reversal (§4). The third is the sharpest
reading of the principle, because the two exits are five lines apart in the same walk and shared
one threshold: the crash abort is the cautious branch, priced on the loss it is measuring, while
the reversal is the probing branch, which must not turn around on evidence it cannot distinguish
from noise. Where a proposed change makes one bar serve two questions, this is the shape to check
for. One open
direction comes from the same lens: a detrended confirm reference (both
Walk.beatBase and Anchor.freshLeft exist because the reference is an un-detrended level, and
"a trend clears any raw streak" is what detrending removes by construction).

The map's other two suggestions were built and refuted by the derived-guard study
(§5). Freezing the walk's deviation reference at arm is wrong because that feedback is
load-bearing, and a within-sample confidence interval is both too small to bind and answers the
wrong question for the position jam. Read those graveyard entries before proposing either again:
a formalism that names a mechanism correctly still does not license changing it.

3. The adversarial cases (the ones that must always be re-run)

The generators, run commands, and per-family verdicts for this section live in the committed
/climber-gate skill (.claude/skills/climber-gate/); the traces are deterministic and
regenerate on demand, so only the generators are preserved. New families are found by
/audit-regret, which searches a compositional workload generator for cells where the machine
does not close the gap to its static ceiling and classifies each failure (wrong equilibrium,
slow convergence, masked signal, insufficient exploration, oscillation, memory, irreversible
damage, aliasing, premature commitment, tier discontinuity, structural) against the layer that
owns it; a confirmed family lands here and in the gate table.

  • corda + 5×loop + corda phase-shift stress (real traces, bundled): run at
    512/513/1024/2048/4096/4097/8192; no cliffs at either tier boundary; near static ceiling.

  • mixture family (gen.py mixture, seed 7): steady Zipf
    hot-set (defends main, feeds mainDensity) + twice-accessed items at reuse distance
    D ∈ (2%, 25%]·max. No phase structure. Pure density pins at the floor for 100% of adaptations
    and lands ~28pp below LRU — at 8192 and any scale where D falls in the band (the trap is
    scale-relative because the floor is 2%·max). This is a plausible production shape (popular
    content + session-scoped re-reads) and the reason a tier threshold alone cannot make density safe.

  • phases family: alternating loop-over-0.9·max and pair-reuse-at-D phases; d050 (D=50%·max)
    traps pure density at 8192 AND 32768. It is bimodal with a 14pp run-to-run range (the
    admission tiebreak picks the basin), so §6's ±0.1–0.8pp noise model does NOT hold and
    single-run numbers here are uninterpretable; the trace-start alignment offset alone is worth
    up to 12.5pp
    , frozen for the run because the sample grid never dithers (no aliasing comb at
    commensurate ratios — 38 ratios × 12 reps, mean +0.62pp mixed sign). What is real is the lag
    limit
    : phases shorter than ~2 sample periods (~8·max requests) are untrackable. Current
    classification: d050 is one of the three genuine steady-state pins, −8 against
    LRU converged, its grid-locked cadence constructed-only.

  • deadphase: hot-set + pure one-shot scan bursts. Proves exploration during dead samples is
    ~free (admission shields main) — all variants sit at the ceiling. But the safety is a knife
    edge on the ε symmetry, not on the dead sample
    (failure atlas): a fully dead sample
    gives ln(ε/ε) = 0, a no-op; break the symmetry with as few as six window hits and the same
    sample yields err = +17.4 and the full 30%-of-max step. A rider of 380 requests in 1.97M
    (0.019%)
    riding a victim's own scan phases costs the victim 8.3pp and drags the window from
    0.03 to 0.48; at 0.4% it pins the ceiling for −9.6pp. Delivered through armProbe's
    refractory fall-through, which suppresses the probe but not the density arm. Reactive tier at
    4096 on the same trace: −0.44pp.

  • widepin: whole-working-set alternation (pairs at 0.6·max ↔ loop over 0.85·max). A fixed
    window wins by never moving; every online climber pays here. Re-classified: the
    whole-trace deficit is mostly warmup (it converges to a 77.9% window against an 80%
    optimum, final third −3.7 vs LRU), and the row is seeded-only — its unseeded distribution
    is a 50/50 basin coin whose old bar sat at its own mean. Bar: no per-seed drift, seeds 1–8.

  • Thin-signal floors (w50@123038, S1/S2/S3, DS1): frequency-ish traces whose floor window
    earns wh/n in the same band as a trapped window (w50: 131–437 hits per 492k sample vs trap
    strays ≤57 per 32k). No resident-only threshold separates them — this is measured, not
    conjectured, and it is why probes+adjudication exist instead of a smarter starvation predicate.

  • lowmix (gen_attacks.py, seeds 7/11/13): low-HR (2–7%) bistable shape whose probation
    concentrates the reuse band while the good state is an LRU-ward diluted window — the marginal
    verdict's named trade (the frozen baseline false-vetoes the escape that the rejected
    diluted-average form confirms by seed-luck). Sentinel: no drift below the recorded values.

  • demoflood (demoflood.py): protected-saturating hot core + walk-reachable band — the
    constructive proof that live-probation adjudication is an absorbing false-veto and the
    pre-walk freeze is earned. Gate row: frozen-v5a must stay far above the live-variant's pin.

  • whisper + window-irrelevant modulation (whisper_mod.py) and mixmod
    (gen_adv.py mixmod): the F1 dose instruments — a hit-rate modulation that provably does not
    change the optimal window (pin-sweep verified) must not turn the goal-metric layer off. The
    deviation-priced confirm failed this dose-responsively; owned by the raw-sample streak
    confirm and the fresh-park shield, with the deep a0.12 dose (whose scatter crosses the
    audit's crash bar) closed by the crash-streak time persistence — the sentinels now sit
    within half a point of LRU. mixmod reads −2.4 against the reactive arm and is
    constructed-only. esc_jam and tenant_s10 ride along as
    jam-family and co-tenant-family sentinels for the same layer.

  • shieldtrap / climbtrend / loopcliff (gen_adv.py, round-2 instruments promoted to gate
    rows by round 4): regime-change-after-confirm, trend-driven misconfirm (plus the flat+wave
    saw_p40 variant), and the no-cliff invariant at the structural misconfirm landing.
    shieldtrap and saw_p40 are the audit-amplification sentinels (R4-F1): the round-3
    clock repairs multiply audit reachability — worth +10.4 on the attack rows, giving back
    −1.0..−2.1 on these already-below-LRU synthetics (audit share of the run 6% → 43% on
    shieldtrap, which also turns bimodal) while twelve real density-tier cells move ±0.07.
    The rows exist so the next audit-schedule change is measured against the cost side too.
    Part of that give-back was never the schedule's: the audit-bar split recovers
    +0.79/+1.75/+1.17 (N=8: 78.06/77.19/77.44 → 78.84/78.94/78.62, re-based in the gate table) with
    saw_p40 at 0.00, because AUDIT_BAR_FRACTION was pricing the walk's reversal at a median
    0.28σ here and killing walks on samples the machine itself calls noise. Attribute the remainder
    to reachability, not all of it.

  • posjam (gen_jam.py, round 3's position-jam instruments): the whisper base with the
    whisper dose modulated at the sample cadence instead of constant — dose-matched and
    provably window-irrelevant (LRU and every static window identical to the flat control). The
    modulation makes the density arm command super-band steps, which starved the hard-reset
    audit clock forever (66.9 → 56.1 = the audit-free pin, −8.9 below LRU; scale-invariant at
    16384). Owned by the decaying clock and the audit-owned schedule (the j50 ±50%-jitter and
    d25 period-2.5 rows recover to 66.4–66.5); the sample-aligned every-sample variant (d0)
    still jams — it needs ±¼-sample grid alignment (the D4 exposure class), is a deterministic
    9.3pp regression against the reactive arm, and is constructed-only, priced and parked
    (the stillness-measure study stays deliberately unspent). A slow
    square duty re-creates the starved clock below every-sample motion (the Sol
    round's entry_duty, a dose note on the posjam_d0 gate row); the loss there is the
    rider's class-3 mask, not the jam.
    The optional audit-regret/specs/bandrider.json control uses a finite-distance rider
    (pairs at 0.15·max) with a sine-modulated share, a steady far band at 0.67·max, and a
    Zipf core. At maximum 8192, workload seed 1093 and 256 continuous samples, admission
    seeds 1–8 give hybrid 51.44 (51.36–51.52), bit-identical to noaudit, against
    62.01 for reactive and a 64.94 best tested static reference. All eight default-start
    histories have no audits after one early starvation confirmation. The later 128-sample
    suffix still loses 12.92pp to the same fixed 45% reference, so the deficit is not only
    startup. Holding probation at its live 1622-entry allocation gives 64.78–64.90 at
    43/50/60% windows; actual adaptive starts at 43% and 60% give 65.28 and 65.37. These are
    different initial histories, not frozen decision counterfactuals. The observed small-window
    orbit is path-dependent. Eleven one-factor neighbors at 128 continuous samples retain 13.4–13.9pp gaps at one
    admission seed (amplitude, period, phase, distance, scale and trace seed); a lower modulation dose
    escapes later. Its amplitude ladder mixes delayed escape, unsuccessful audits and audit
    absence, so a hit-rate threshold does not identify a mechanism boundary. This is another
    class-3/class-5 dose beside entry_duty, not a new failure class or mandatory gate bar.
    No individual missed audit's benefit was measured.
    The audit-regret/specs/stillness_starve.json dose is the class at its extreme: a trickle rider
    and a two-reference band, square-modulated together, keep 63–90% of samples moving, so not one
    audit arms in 128 samples, the calibration audit included. Seeded 1–8 at 8192 it reads
    51.16 ± 0.13, identical to noaudit, against reactive's 56.08 and a 60.0 static peak, at every
    period from 2.3 to 9.0 samples. The shrink (stillness_starve_min.json, the band dropped) keeps
    the mechanism, and holding the rider steady arms four audits. Another dose, not a new family.

  • crashnoise / mixnoise (crashnoise.py, mixnoise.py, adv3 rows): the whisper and
    mixture bases under a mean-centred, RMS-normalised rate modulation whose amplitude is
    the dose — the walk's exit-bar instruments. The two interior exits test different statistics
    (crash abort = level vs the rate frozen at arm; reversal = first difference vs the previous
    sample — §4), so the dose ladder is a threshold, not a gradient: nothing moves under the
    bar, the cell falls off where the scatter crosses it. mn8_sine_a10 was the defect on the
    starvation-probe path — the wave crash-cycled every up-probe one stride off the floor,
    severing the mixture trap's only escape — healed by the probe-side pricing (51.6 → 60.7,
    +5.6 over LRU). cn_sine_a12, the audit-side sentinel, is bimodal (its "deterministic"
    record was an N=2 artifact) and healed most of the way by the crash-streak time persistence
    (−2.8 → −0.8 vs LRU); the residual is the crash abort's robustness price.

  • whisper (whisper.py): a mixture base plus a ~0.2% immediately-re-read trickle sized to
    keep the window's sample hits JUST above the starvation bar, so no blind corner is ever
    declared and no probe arms — the sharpest F4 instrument (pinned −9pp below LRU at 8192, −12
    at 16384, knife-edged exactly on MIN_SIGNAL_SHIFT; the quarter-dose variant drops below
    the bar and the marginal verdict escapes where the average form stays pinned). Owned by the
    equilibrium audit: 66.8 against LRU 64.6 on every seed, identical under both verdict forms
    above the bar.

  • shallowmoat (spec audit-regret/specs/shallowmoat.json):
    a zipf over 1.5·max that fills main and gives the window only stray hits, a two-reference band
    6,200 requests apart caught only past a ~58% window, and a one-shot scan that keeps probation
    thin. The static curve is a valley 2pp deep and 57% of the cache wide with a 19pp prize behind
    it, so no walk crashes and no first-round walk reaches it. Seeded 1–8: 27.4–28.5 against a
    42.0 ceiling and LRU 39.4, and the same product planted at 70% holds 42.0. Two basins, one
    outcome: the starvation up-probe adjudicates at 4×bar hits three or four strides out, confirms
    against the thin frozen probation, Ladder.reward resets the rung, and the density arm reverses
    the window in the same sample, so the ladder alternates 1↔2 and no commitment depth is ever
    reached (absorbing at 127 decisions); or the calibration audit confirms on the trace's warmup
    trend at 32%, one stride short of the cliff, and parks. Reach (class 4) through a successful
    verdict erasing the ledger (class 6). Splitting the band over a spread of reuse distances keeps
    the gap (26.8 / 29.0 at N=8) and adds the average law's rest-point give-back from above. The
    reactive arm beats the machine on 6 of 8 seeds (+2.0 mean).
    Reach was repaired the same day (§5): a confirm the density arm reverses now
    deepens the ladder instead of resetting it, and the wedge seeds cross the cliff at s17 (30.7 /
    30.0 / 30.6 against 28.5 / 28.4 / 28.5). The row still reads far below the ceiling because the
    found position is not kept: after a walk from the floor, density's rest point on the far side is
    at or below the cliff (main holds the protected core, 3.7 hits per entry against the window's
    1.2), the guard rail cannot catch the fall (the fall inflates the deviation its margin is priced
    from), and the deep walk's confirm at the top is itself reversed, so nothing parked. Retention
    landed: a reversed confirm at the deepest commitment that the goal metric
    confirms parks as an audit's does, and the park survives its own audits' crash-scale moves; the
    wedge seeds read 36.5 / 36.4 / 36.3 (repeat 2: 37.1) against the 42.0 ceiling, the residual being
    the top corner's periodic down-audit crashing at the cliff. Two facts from /audit-regret round 2
    : the basin is decided by the first sample's window hits against the bar (30–31 hits arm a
    probe at s1 against a fat frozen probation, which fails, basin B; 32–34 leave the floor sighted until
    s3, when a probe against a thin one confirms, the wedge), so a regenerated trace reshuffles which seeds
    cross; and both repairs live on the starvation ladder, so a rider that keeps the floor sighted removes
    them: veilmoat (the same trace plus a k=8 burst re-read 40 requests apart at 0.06% of traffic, worth
    0.3pp) arms no starvation probe on any seed, and every seed then takes the audit path (26.84 ± 0.34 at
    64 samples on 8 of 8 against a paired unmasked control of 4 of 8 crossing; the rung-32 up-audit crosses
    at ~s88, 32.02 at 128 samples), and after the recorded top-corner loss the guard rail's veto and
    density's slide form a ~17-sample limit cycle with no re-escape (32.45 ± 0.07 at 256 samples, where the
    unmasked geometry re-escapes through the deep starvation walk to 34–40). The mask holds from ~3e-4 of
    traffic (below that the ladder is the parent's own lottery); a dose note, not a family. A park's first
    audit following its walk turns the basin-B seeds' second audit up from the 32% park
    into the cliff crossing at s52 (27.4 → 30.1 on the 64-sample row, 32.2 → 34.0 at repeat 2; the wedge
    seeds identical) and does the same for veilmoat (26.84 → 30.54 on 8 of 8, 32.02 → 33.93 at 128
    samples, 32.45 → 33.11 at 256); the retention residual after each escape is unchanged.

  • absolve (specs audit-regret/specs/absolve.json and
    absolve_p8.json): a lure population (pairs 1,300 requests apart, 10% of traffic) pulsed on and off
    with a period of 16 samples inside a wide flat valley, a far band (pairs 6,000 apart, 45%) caught only
    past ~54% of the cache, and a zipf core in main. Static: 18.8 to 10%, a +5pp shelf from the lure's knee
    to 50%, 52.5 at 70%; LRU 51.5. Seeded 1–8: 22.59 ± 0.18 at 64 samples, 24.77 ± 0.11 at 128, an escape at
    s144–159 (36.4 at 256 on seeds 1–4), reactive 25.5 and noaudit 22.9 trapped as well, and the same product
    planted at 70% holds (51.07 ± 0.21). Two mechanisms by trace length. Through 64 samples it is the audit
    tier: the calibration audit's level-test confirm at 32% on a phase step (the lure switching off raises
    main's hits by 5pp against a stale reference), a 32-sample park, the down-first alternation and its crash
    when the lure returns mid-walk, the undo's arrival discarding the anchor: shallowmoat's basin B at
    another dose, on every seed. From s60 a cycle paced by the lure: the off phase blanks the window and the
    corner arms a ×1 walk, the on phase confirms it at 2,200–3,400 on the lure's own hits (the verdict cannot
    attribute the watched region's hits, and the lure is dense per slot and worth nothing above its 8% knee,
    flood_j100's shape pulsed), density rests on the lure at ~37% then slams the window home when it goes
    off, and the ladder gains one rung per period because it gets one walk per period, with the kept
    confirms resetting it to 1 (Ladder.reward); the escape needs a walk longer than any first-round one
    (six ×1 strides from an arm caught mid-slam at ~1,650, or the rung-32 ×2 walk) and comes ~140 samples in.
    The average law also abandons a partial catch of the band at 4,300–4,400 (window density 1.30 against
    main's 1.59). At a period of 8 (absolve_p8, 256 samples) every confirm is kept, the rung reads 1 or 2 on
    223 of 255 samples and never reaches 32, the audit clock never fires (the position never stills 16
    samples), and the cell is absorbing: 27.92 ± 0.19 against a 57.4 ceiling and LRU 55.6. Robust to the
    grid (period 12: 26.35 ± 0.16 at 128 samples; a phase offset: 28.52 ± 0.86; period 20 a 2-of-8 escape
    lottery), to the band distance and to 16384 (18 of 18 neighborhood cells at 32–34pp at 64 samples), and
    a knife edge on the lure's share at ~1.6% of traffic, where the calibration audit's direction flips from
    up-from-the-floor to down-from-an-interior-position. Reach (class 4) through the verdict's attribution
    (class 3), density's chase (class 5) and the ladder reset (class 6); the period-8 form is class 6
    outright, the deferreward shape (§5) made deterministic. Repaired: the starvation ladder prices a confirm at or
    short of the farthest window its walks have confirmed as a completed experiment, so the period-8 form
    escalates 1 → 64 over six periods and the rung-64 ×4 walk crosses the valley in two strides (27.95 →
    46.29 at 256 samples, above the reactive law's 45.28); and a park's first audit follows the walk that
    confirmed it while the claim stands, so the period-16 form's second audit walks up from the 32% park
    into the band instead of down into the lure's knee (24.77 → 41.46 at 128 samples; 36.45 → 46.89 at 256;
    period 12 and the phase offset 26.4 / 28.5 → 44.0 / 43.1). Period 20 stays a 2-of-8 lottery. What
    remains after each escape is the recorded top-corner residual (the down-audit crashing at the cliff and
    the undo's arrival discarding the anchor).

  • ghostclaim (spec audit-regret/specs/ghostclaim.json): two
    phases, a zipf core with a band 2,000 requests apart and a scan for 28 samples, where density rests
    at ~43%, then a sleeper population 6,400 apart at half the traffic, caught only past a ~64% window.
    Static: 19.9 at the start, 54.1 at 70%; LRU 51.8. The stale-claim family's away-anchor case: the
    calibration audit's down-walk re-syncs the anchor's claim to phase 1's rate as it passes 20%, density
    holds the window at 43%, and the shift lands with the window still and off the anchor, so the
    stand-down keeps the claim (the carve-out). Seeded 1–8: 41.88 ± 0.17 at 64 samples (the s37
    up-audit sits at the top for ten samples at +30pp and fails at budget against the claim), 31.24 ± 2.10
    at 128 (the same claim then vetoes the window to the phase-1 anchor and its hold, the down alternation
    and the deepest-rung wait pin it), reactive 37.96 (bimodal), noaudit 30.30. Knife edges by
    construction: the stand-down's band (a phase-1 band share of 0.16 lands the shift on the anchor and the
    claim is discarded) and the prize's rate against the claim (a sleeper referenced three times clears it).
    Class 6 with class 4 as the pin. Repaired: the audit's walk is measured against the smoothed rate it leaves rather than the anchor's
    claim (31.24 → 48.45 at 128 samples, above the reactive law; the witness 41.88 → 46.86; cp_w100 +2.0
    on the corpus). The discard shape died on the moat rows (§5). What remains is the shift landing on the
    audit's arm sample or during its walk (ghostclaim_p35..p40), where the walk crashes on a
    contaminated base and the stale veto's hold pins the machine, and an audit arming inside the smoothing
    horizon after a shift, which measures against a blend of the regimes.

  • crestpast (spec audit-regret/specs/crestpast.json): a
    uniform bulk over 1.2·max at 72% of traffic (main's population, its hits linear in main's capacity)
    and a two-reference band 1,100 requests apart at 25%, caught by an 8% window. Static: 45.8 at 1%,
    44.7 at 5%, 62.5 at 8%, 64.4 at 10%, then a decline of ~0.9pp per 6.25% stride to 54.3 at 80%; LRU
    52.3. A crest 17pp high, one stride from the floor, with a cliff on its near side and a slope on its
    far side. The calibration audit crosses the crest on its first stride and confirms on its fifth
    (AUDIT_CONFIRM_STREAK completes at stride 4, AUDIT_COMMITMENT holds the verdict to stride 5),
    so it parks at 33% earning 3.2pp less than the crest it walked over; the park's audit walks back
    down, has the crest under foot at its fourth stride and its fifth on the cliff side, where the level
    test crash-aborts before auditEnding can be reached; the undo's arrival discards the anchor (the
    C2 shape) and density's average law, which reads the caught band as a dense window against a thin
    main, drifts the window up and away from the crest until the next audit fails at the corner and
    the rail vetoes back. Seeded 1–8: 57.93 ± 0.04 at 132 samples against 64.9 at 10%, reactive 46.5
    and noaudit 46.2 both pinned at the floor (the hit-rate law steps into the 1–7% decline and never
    crosses; density steers the sighted window down), and the same product planted at 10% leaves the
    crest (62.4). The verdict's position is the mechanism: the confirm lands at stride max(k + 3, 5) for
    a crest crossed at stride k, from either side, so the two endpoints the machine visits (the floor
    and the floor's fifth stride) can neither of them confirm the crest, and what the machine holds
    between audits is the average law's rest point above it. Planted at 40% the down-audit's fifth
    stride lands on the crest and confirms there, and the row still reads 59.7 (gap 5.0): a confirmed
    crest is lost after one audit cycle (the corner audit's fail, the veto, the second park's crash,
    density's drift), so the retention half is the larger part of the steady loss and the verdict's
    position decides where the first park lands. Each stride's sample is an arrival transient, so on
    most cells the walk's best sample is one to two strides past the static crest, which bounds what a
    verdict at the walk's best could recover.
    The far side's slope prices it: bisecting the bulk's size finds the knee at 0.68·max, main's capacity
    at the park, below which the bulk still fits from the parked position and the overshoot lands on a
    shelf (demoflood's recorded overshoot, which the C2 discard there accidentally corrects because
    density's rest point sits at that crest). Robust across the band's distance (800–2,200 requests),
    the bulk's size (1.0–1.5) and 16384 (ten cells at 4.4–8.0). Class 9 (the audit verdict,
    Walk.isConfirmed) with class 1 holding the loss and the C2 discard handing it over. With the crest
    beyond the fifth stride (the band 4,000–4,400 requests apart), the same verdict parks the window in
    the valley on the cold reference instead, regimeramp's recorded misconfirm at ten times its
    sentinel's dose (s_fatbase: 29.6 against 44.3 at 45%, LRU 37.2, at 72 samples).

  • hazefloor (/audit-regret round 3's note, rowed by round 4 at 320 samples; spec
    audit-regret/specs/hazefloor.json): a uniform haze over 2·max, a zipf core, a band 7,200 requests
    apart caught only past ~60% and a scan; static flat 33.7 → 32.5 to 50% then 52.3 at 65% (a 60/65%
    re-sweep), LRU 49.2. The calibration audit crosses and parks at the top by s24, and the rest of the
    run is the top-corner residual by itself with a ~105-sample period: the corner's audit is forced down,
    crashes at the cliff, the undo's arrival discards the anchor, density slides off the cliff, the
    floor's ×1 walks fail against the haze, and the rung-16 audit re-crosses. Seeded 1–8 at 320
    samples: 41.09 ± 1.80 (38.3–42.0), 8pp below LRU, against the reactive law's 36.5 and noaudit's
    37.5, so the layer earns its keep and loses most of it every cycle. A sentinel with per-seed bars,
    not a pass bar; the row exists so a change to the corner audit, the C2 discard or density's slide is
    measured against it.

  • parkveil (specs audit-regret/specs/parkveil.json at
    240 samples and parkveil_min.json at 60): a zipf core (0.5·max, α 1.05) and a two-reference
    band (pairs at 0.45·max) whose shares alternate in a square wave at period 13 samples, plus a
    10% one-shot scan. The fixed mid window wins by construction (ceiling 66.82 at 50%, an interior
    plateau wholly above LRU 66.04; the product planted at 50% holds 67.03), and every adaptive arm
    sits below LRU: seeded 1–8 the machine reads 53.13 ± 4.09 (a 6-vs-2 basin split), noaudit
    55.73 ± 0.55, the reactive law 59.91 ± 0.92. Density's rest point alternates with the mix (the
    ratio's within-sample phase immunity holds for rate swings, not mix swings), each rec phase
    blanks the window into a blind corner, walks crash on the phase boundaries or confirm on the
    flips as wedges (the ladder escalates 2 → 64 as designed), the rung-64 walk parks at the top
    corner as an audit-grade confirm earning within ~2pp of the plateau, and the park survives
    exactly one fresh-park shield: flips inside the shield are absorbed, the park's own audit crash
    returns to it (the cover), and the first post-shield flip fires isWorkloadShift,
    whose stand-down discards the claim at the anchor and re-seeds the goal metric, so the position
    is re-derived through the full ladder every cycle. The guard rail never fires on any seed: the
    phase swing holds the deviation at 0.21–0.34 and the veto margin at 62–101pp. The cycle is
    arm-independent (noaudit parks at s134 through the ladder alone and re-parks more reliably;
    the audit layer's margin here is delay from a calibration flip-misconfirm's reward reset, plus
    variance), so the loss lives in the shared stand-down and shield machinery. The last-third gap
    is 6.0 of the whole-trace 13.7 (half the headline is one-time ascent); a ~2pp per-cycle
    re-derivation cost and a 2-of-8 lost-seed tail persist. Robust across the period axis (gaps
    27/27/7/27/12/17/12/13 at periods 2–32, a resonance structure with one narrow escape at 6),
    amplitude, band distance (deepens to 22.1 at d=0.6), 16384 (21.2), phase offsets (6.7–20.2,
    none clean) and trace regeneration (~2pp milder). Class 5 (the chase; controller) with class 6
    (the discard; recovery layer); the census C3 observation's first cost-bearing cell, and the
    mirror of flashpark (there the shield holds a wrong park through a regime end; here the
    shield's expiry ends a right one).

  • latebloom (specs audit-regret/specs/latebloom.json at
    262 samples and latebloom_min.json at 187): a zipf core (0.15·max, α 1.0) at 60% of traffic
    and a scan for 112 samples, flat within the audit's level test at every window, then a
    two-reference band 7,000 requests apart at a third of traffic for 150 samples, caught only from
    a 50% window. Static: 52.4 from 1% to 40%, 66.4 at 50%, 66.2 at 70%, 65.9 at 80%; LRU 64.5.
    The prelude spends the audit schedule: the calibration audit parks the floor on the cold-start
    lag, the park's two audits walk their full budgets to the 80% cap on flat terrain and end
    failed rather than crashed, and the second failure at rung 64 doubles the clock to 128
    (AuditClock.reschedule). When the band arrives the floor's rate drops 15pp and the stand-down
    fires, but a stand-down leaves the audit rung and the clock's wait untouched, so nothing is
    rescheduled; at the base's alignment the arrival lands inside the undo's retreat, where
    isParkTest covers it, and the held floor survives the change outright. Either way the
    machine sits at the floor against its own law (rest_err +1.3) until the 128-sample wait runs
    out and the next audit catches the band in two strides. Seeded 1–8 the base reads 54.90 ± 0.04
    against the 66.4 ceiling (gap 11.5, 18% of the prize, 123 samples parked), noaudit 53.39
    pinned at density's ~19% rest point (the core's head keeps the window sighted at ~4,000 hits a
    sample and worthless at the margin, and the band is a +19pp step between 27% and 51% that a
    marginal law cannot see) and reactive 53.44; planted at 50% it reads 57.85, since density
    slides off the flat prelude before the prize exists. The loss is the latency and nothing else
    (gap ≈ (confirm − arrival)/n × 23.9pp fits every cell within 0.5pp), and the phase-1 ladder is
    the family's shape: an arrival during an audit's cap-sit is confirmed at once (56/104 samples:
    0.9/0.8), one on a walk's early stride crashes it and is found at the next wait (48/96:
    3.5/4.4), one just after a budget failure releases the park by the stand-down, lets density
    rest at ~19%, and the next audit alternates down from that interior position and fails into
    the 128 wait (64/80: 16.7/15.4, found at ~s247 when the horizon allows), and one during or
    after the retreat is found ~125 samples late (112–132: 9.8–11.5). Over arrival times the mean
    is 7.2, the median 6.2, the worst 17.1, and about a tenth are found at once; the base is the
    76th percentile. Identical at 16384 and flat in the band's distance down to 3,500 requests,
    where the held floor still catches nothing although density's rest would; the minimal witness
    (187 samples) is the never-found form, 55.78 ± 0.02 against noaudit 55.79. Class 4 (the
    exploration cadence, auditWait, and chooseDirection's side choice from an interior rest
    point) on a class-3 terrain; the retreat cover's hold is incidental above the family's
    boundary. Nearest recorded: H4-C1 and metronome, which reach wait 128 through crashes on
    trap terrain; this reaches it through the designed path and prices a detected regime change
    that reschedules nothing. The latency face of that residual, whose
    re-arm was priced dead (§5's re-arm entry: the stand-down alignments' gaps fall to
    5–6 and the price lands on the F1 sentinels).

  • mainsat (/audit-regret round 7; spec audit-regret/specs/mainsat.json, 72 samples): the
    main-side masked signal, the sighted lane's directed slot for seven rounds. A zipf core over
    0.45·max at 30% of traffic (recurrence ~12k requests, longer than the window's residency at
    every window, so main holds it and the window never takes it), a band 5,200 requests apart at
    18% caught from ~50%, a 52% scan that keeps the window's residency short enough for the core to
    stay main-resident even at 80%, and a gap-1 trickle at 0.3% that keeps the window sighted.
    Static, dense-swept: 20.3 flat to 40%, 27.2 at 50%, 32.0 at 52%, 35.8 at 58%, 35.4 at 62%,
    34.6 at 70%, 33.5 at 80%; LRU 30.5. Main's 7,000 hits a sample are real and worth nothing at
    the margin (main holds 8,028 entries for a 3,686-key set), so steeringError commands a
    down-step every sample, no probe can arm (a dense main is not a blind corner by design), and
    the raw law pins the floor: noaudit 21.41 ± 0.48 against the 35.8 ceiling. The equilibrium
    audit is the only explorer and does the work: the calibration audit confirms a real cold-start
    gain at 32% (+3.3pp over the floor while main warms faster under the larger window; the band
    is five strides past the floor, beyond its reach) and parks for 32 samples, the park's audit
    walks up through the band and confirms at s48 on the peak (six seeds at 56–57%, two at
    68–69%), and the loss is a ~48-sample approach at any horizon: 28.04 ± 0.18 at 72 samples (gap
    7.8), 32.63 ± 0.30 at 160 (gap 3.7, ~1pp of audit duty permanent). Planted above the cliff the
    product does not hold: density slides the window off the cliff at ~49%, the rail returns it,
    and on the landing sample the rate recovers by 11pp, which isWorkloadShift reads as a
    workload change, so the stand-down discards the claim with the window on the anchor and the
    slide repeats every ~19 samples with the anchor creeping down (planted at 70% over 160
    samples: 29.99, ten vetoes, closed −5.2; at 55%: 27.95). Implemented as the return
    cover (isReturnTest): a return's landing and settle samples wait for the retest, which
    breaks the cycle (160 samples: 70% 30.06 → 35.30, 55% 26.13 → 35.08, two vetoes each; the
    pre-cover baseline sat below LRU on both), noveto reads 33.75 / 30.82 there, and the cover is
    bit-identical on the unplanted path, where the anchor is a park and the rail never fires.
    Release posture is HOLD on the repeated-real-trace recheck; the planted
    synthetic prize remains valid, but the original one-pass gate was not the full price. Class 3
    owned by the audit layer, as whisper is on the window side, with
    class 6 on the plants: the C2 arrival-discard shape on the rail's return, which the
    re-price found unreachable because the rail never fired across the battery. Rowed as the
    main-side mask's sentinel.

  • sidecliff (spec audit-regret/specs/sidecliff.json, 96
    samples): a zipf core (0.15·max, α 1.0) at half of traffic, a near band 300 requests apart at 8%
    that the 2% floor already catches in full, and a far band 7,000 apart at 30% caught only from a
    55% window. Static, dense-swept: 36.4 at 1%, a 42% shelf from 5% to 40%, 44.4 at 50%, 63.4 at
    55–65%, 62.3 at the cap; LRU 61.6. The near band's fixed hits spread over a growing window pin
    the average law's rest point at ~14%, sighted and worthless at the margin, so noaudit and
    reactive sit there (41.85 / 41.80) and the audit layer is the only explorer. The calibration
    audit arms at s7 with AuditClock.down still at its initial value and a stride of room below the
    rest, walks down, loses half the near band at the floor, reverses and fails through its own base
    (arung 32, auditWait 32), and the machine then holds the shelf for 33 samples until the
    rung-32 up-audit crosses the step at s47 and parks at the cap at s50: 52.51 ± 0.03 on the parent
    cell at seeds 1–8 (identical event sequences), a gap of 10.9 that is latency alone (5.32 at 192
    samples with the prefix identical; trace seeds 11 / 13 read 8.57 / 9.02). Two sibling
    constructions from the other lanes price the shape below the rest: s_faderail (a trickle-pinned
    rest at 13%, the down walk wall-sits the floor for fourteen samples and fails at budget, 46.12 ±
    0.06 against 55.02) and s_sidebet (a starvation probe finds a mid band at 28% and plants a claim
    without a park, so the follow rule has nothing to follow and the calibration audit still opens
    down, crashing at the mid band's cliff with a wait of 16; 45.31 ± 0.15 against 53.30). Planted at
    the peak the product holds the cap (61.35), so the whole loss is the approach. The opening side
    from an interior rest point is the latency-face residual and absolve's
    entry names as a knife edge; this is its stationary witness at the opening rung, with no prelude
    and the prize present from the first sample, filed under class 4 with that residual (the
    signature reads class 2, walk-paced). The symmetric cells, a prize below an interior rest where
    down-first is right (crestpast's drift, cp_w097), are the other side of the same coin. The
    guard rail also loses here rather than helping: noveto reads 60.62 against 59.28 on all eight
    seeds, and the retest is what bounds the loss, since noretest takes seed 2 to 51.63
    (/climber-minimize, §5).

  • jumpslide (spec audit-regret/specs/jumpslide.json, 130
    samples): a hot set of 0.5·max keys replaced wholesale every 13 samples at 52% of traffic, a band
    5,600 requests apart at 30%, a scan at 18%. Static, dense-swept: 35.5 flat from 1% to 40%, 50.3 at
    45%, 53.4 at 55%, 54.8 at 65%; LRU 49.5. Between jumps the hot set is main-resident and the miss
    fraction ~0.4, so the band is caught from ~43%, a residency knife edge (catching it lengthens
    residency, losing it shortens it) that collapses in one sample. Seeded 1–8 the machine reads
    38.70 ± 2.79, noaudit 44.23 ± 4.07 and reactive 41.21 ± 3.64, every arm 5–11 under LRU. The
    route: the calibration park's 32-sample expiry lands two samples after a jump, the recovery
    stands it down and the s42 audit goes down to the floor, so the starvation ladder takes over; a
    first-round probe confirms at 43.2% on the stride where the band is first caught, since 4× the
    bar is first earned exactly there, at the catch region's near edge, and a seven-stride confirm is
    under PROBE_COMMITMENT_DEEP, so no park rule applies and the edge position is handed to density
    with a claim planted and nothing held. Density's rest point is below the edge (0.7–1.0% a sample
    down); each jump lands the fresh set in the window and kicks it up 7–8%, and the drift returns it
    to the edge as the next jump lands: seeds 4 and 8 hold that sawtooth at hr 60–62 between jumps,
    while a landing under ~48% lets the drift cross the edge first, the band collapses, density sends
    the window to the floor, and the blind corner re-climbs seven strides of wh 0 or a jump crashes
    the climb. The rail never fires, since a one-sample collapse inflates the deviation its margin is
    priced from, shallowmoat's sentence, with or without jumps. Period 17 reads 5.72 (the calibration
    audit goes up instead and parks audit-grade), 26 8.2, 6 / 11 18–20, 40 15.0; trace seed 11 16.7;
    260 samples 14.8; a static uniform hot set in place of the jumps 52.27 against 61.63 (the slide
    released there by a stand-down on the sample after a capped retreat's landing, which the retreat
    cover does not span); planted at 60% the product holds. What the record did not have: a
    first-round starvation confirm's verdict position is the near edge of what it found, by
    construction, and nothing parks it; class 9's second clause feeding class 1 (the retention
    sentence, the parent) and the class-5 sawtooth, with class 4 on the re-climbs. A sentinel row.

  • lowbar (spec audit-regret/specs/lowbar.json, 96
    samples): a uniform bulk over 2·max at 42%, a band 6,000 requests apart at 34% that only a ~68%
    window catches, a scan at 24%, and a gap-1 trickle at 0.4% that keeps the window sighted so no
    probe arms and every seed takes the audit path. Static: 13.0 at 1% declining to 7.8 at 65%, 32.5
    from 70% to the cap; LRU 30.6; a 9% rate. The audit's crash bar is AUDIT_BAR_FRACTION of the
    rate frozen at the arm where that is under the 5pp cap, 1.37pp here, and the calibration walk's
    cumulative drawdown along the bulk's decline crosses it at the tenth stride, one short of the
    catch point; the tolerant retry's three sub-bar samples run out at 44%, the rung-32 walk's at
    63% with its next stride on the plateau. Seeded 1–8: 13.40 ± 7.48, seven seeds pinned at
    11.3–12.1 under the 13.0 start (three crashed walks' duty) and one at 26.3 whose tenth stride
    cleared the threshold by 0.07pp where seed 1's missed it by 0.20 (trace seed 11: 26.30 / 11.61);
    noaudit 12.72 and reactive 12.49 are the terrain's floor at every window short of the catch
    point, the same loss as the 17pp under LRU. With the crash fraction at 0.25, or the absolute 5pp
    bar, three of four seeds arrive at the first walk (26.3) and the fourth takes the reversal exit.
    The moat family's mechanism (the abort at the −bar contour one stride short of the far bank, the
    persistence a stride short on the retry) at a fraction-bar dose: the bar set by the rate rather
    than the terrain, and a sub-bar region wider than the persistence. s_nearbasin is the same
    exit at a 16% rate behind a calibration park (about 7 of its 23; the crossing seeds clear the
    level test by 0.02–0.03pp, and nocal recovers only +0.35 because its own first walk crashes on
    the same bar). The first measured cell on which the fraction's level is not a tie (the record
    holds it inert at 0.10 and 0.25 on the sub-third holdout and on arc_S3's plateau); its cost
    side is unmeasured where it binds (the lowmix rows, arc_S3 and the thin-signal floors,
    cp_w050, a fresh sub-third holdout), and the carve-out the cell resembles is closed (the
    split), its deviation floor dead. Narrow by construction: 5,500 apart catches at the
    tenth stride, 6,600 passes the cap; at 16384 the band fits at 40% and the cell reads 4.8. Class
    4 (reach), the walk's exit pricing; a sentinel row beside the moat rows.

  • moatweather (/audit-regret round 9; spec audit-regret/specs/moatweather.json with its
    dose-matched and peak-level controls, 96 samples, trace seed 11): the calibration audit's value
    reverses sign under a level change that lands on its first strides. A zipf over 1.5·max at α 0.95
    (50%) fills main, a band 6,400 requests apart (30%) is the prize, an 8% scan keeps probation thin,
    and the attack is a fourth member, a pure one-shot scan at 12% of traffic square-modulated at
    period 3 samples with amplitude 1, so its share steps between 0 and 24%. Static over 22 windows:
    30.4 at 1% declining to 28.8 at 52%, a flat region spanning 1.6pp across 51 points of window, then
    a 23pp step to a plateau, 51.80 at 64% and 51.19 at the cap; LRU 50.2. Reaching the plateau from
    the floor takes ten consecutive strides. Against the unmodulated control the curve is the same
    within 0.9pp over the whole operating region, the same argmax and the same plateau within 0.5pp;
    only the 55–58% transition differs, three times the machine's window away and in the direction
    that would help a walk. Seeded 1–8 the machine reads 31.20 ± 0.31 against the control's
    47.30 ± 0.54, 16.1pp on 8 of 8 seeds. Undosed the calibration audit is the whole answer: it walks
    ten strides across the flat region while the rate moves 0.294 → 0.302, catches the band on the
    eleventh and parks at 65%, where noaudit must first escalate the starvation ladder to rung 64
    and does not arrive before s47. Dosed it crashes on its first stride (a 5.57pp step against
    min(RESTART_THRESHOLD, AUDIT_BAR_FRACTION · base) = 4.87pp), its retry's bold driver reverses on
    a step and its raw-sample streak confirms at 675 entries on terrain flat within 1.6pp, and that
    confirm's bridge write resets starvation.rung 32 → 1, so the ladder never reaches the rung-64 ×4
    walk that crosses. nocheapen recovers +1.3 of the loss, so the bridge write is the smaller half.
    The audit is worth −10.2 / −8.0 / −1.6 on trace seeds 11 / 7 / 13 and +7.5 undosed, with seed 7
    bimodal (6 of 8 trapped) and the other two unimodal; the dose is a bar crossing, the audit's value
    passing zero in 0.5–0.6 and stepping to −10 between amplitude 0.85 and 0.95 on both instances.
    What the mechanism needs is the coincidence, not the noise: the same square wave at period 24
    costs −9.4 when its first flip lands on the audit's first stride and +0.1 when a quarter-period
    shift moves the flips six samples away, and period 3 is the period that makes the coincidence
    unavoidable at every phase. Two controls separate it from the terrain: the scan held constant at
    the attack's high level of 24% reads 42.97 ± 0.16 with the audit earning +6.8, and moving the
    weather to samples 0–19 alone (21% of the trace) reproduces 79% of the loss while the same insult
    moves nocal by 0.1. The second axis is the traverse length: with the band 3,200 requests apart a
    first-round walk catches it and the cell is solved (gap 1.4), 4,800 costs 14.4 and 8,000 19.4.
    Doubling the horizon widens the loss (33.27 against nocal's 45.07) and 16384 keeps half. Class 9
    (the abort and the verdict both decided by an exogenous step against a frozen reference) paid as
    class 4, with class 6 as the secondary channel; owned by the recovery layer, responder
    Walk.crashBar's audit arm and Walk.isConfirmed's streak. b_moatnoise, the blind lane's
    independent construction (a sine modulation of a resident zipf on a gradient terrain), reproduces
    the escalation block but not the reversal, since the calibration audit earns +1.7 there. Nearest
    recorded: the F1 dose instruments, whose rows sit at LRU parity on shallow bases and whose largest
    audit-layer cost was flashpark's −3.8, and shallowmoat/lowbar, whose terrain this is; what
    is new is a cell the machine solves undosed and loses entirely dosed, and a coincidence axis the
    record does not price. No arm recovers it: nothing tested exceeds 41.8 against the 51.8 ceiling
    under the modulation, so deferring or removing the audit is not the repair.

  • ramp_retest (spec audit-regret/specs/ramp_retest.json, 72 samples): a zipf core over
    0.58·max at α 1.08 and a two-reference band at 0.31·max. The core's weight is 0.4 while the
    band's weight rises linearly from 0.0304 toward 0.7296, so an initially valid high-rate anchor
    becomes obsolete. At 8192, seed 1's rail returns the window from 4,123 to 1,055 at s24–25
    against a frozen claim of 0.8422. The two arrival samples score 0.4032 and 0.3910; at the
    completed retest, smoothed rate 0.5939 exceeds claim − 3·deviation = 0.5038 because the
    arrival raised the live deviation to 0.1128. The retest accepts the claim, the window holds
    at 12.9% for s28–38, and the s39 audit recovers the band at s44. The pre-return deviation
    was 0.0239, which would give a 0.7705 cutoff at that same measurement. This is the return's
    own change widening the tolerance that judges it, at the completed retest rather than the
    walk's crash bar.
    Seeded 1–8: hybrid 61.48 against the best tested static point 68.54 at 26%, LRU 65.43,
    reactive 39.28 and noaudit 45.69. Removing the rail earns +5.54pp, while forcing completed
    retests to reject earns +2.25pp (2.18–2.30 on all eight seeds). The latter keeps the return
    and its observations unchanged through the completed verdict, then releases the hold and
    re-seeds the goal metric. It prices that intervention, not the whole gap or a viable repair.
    The same intervention earns +3.87pp at 16384 and +4.75 on trace seed 13; trace seed 11 takes
    a clean route and is unchanged. A 32-sample stationary prelude preserves the interaction
    (+6.57pp), while repeating the ramp twice reduces its price to +0.25pp. This is a finite
    recovery delay, not a permanent pin. The measured neighborhood is conditional: at 8192 the
    amplitude ladder changes from a correct rejection at 0.86 to a passed retest and eleven held
    samples at 0.88; at 16384 that passed-retest route already exists at 0.84. The rail-path cost remains at distances
    0.25–0.37; a 56-sample ramp triggers no veto, and the 88-sample ramp at 16384
    rejects correctly. A gap-only bisection at much smaller amplitudes instead finds the known
    audit-approach cost, with the retest working, and is not this boundary.
    Class 6 (an obsolete claim surviving its retest), with class 3 (arrival-induced masking) and
    class 4 (the held position waits for the audit); owner Anchor.retestFails with Rates' live
    margin. Nearest recorded: ghostclaim's obsolete return and sidecliff's rail cost, where the
    retest limits the loss. Here the existing retest is reached and passes because of the
    arrival's tolerance increase. The two size cells are finite-horizon sentinels. No production
    rule was proposed or priced against the corpus; unconditional rejection would also reject
    the valid returns the rail exists to defend. The best-sample rule adds a basin selector on this
    terrain: in an independent construction (round 10's blind lane), two basins identical through s33
    split when the recovery audit's first sample read 0.24pp above its second, so
    Walk.rememberBest parked on the catch region's near edge instead of one stride inside, and the
    ramp eroded that edge under the fresh-park shield (parked 56.7 against 73.6).

  • deadpark (spec audit-regret/specs/deadpark.json, 120 samples, trace seed 11): the moat
    terrain the calibration audit solves (a zipf core fills main, a two-reference band is caught
    only past a ~55% window; static 30.6 flat to 50% and 55.0 at 60–65%, LRU 53.1), with two
    samples of pure scan at s36–37, so s37 is dead. The audit parks at 72% at s21 against a density
    law that wants a smaller window. On the dead sample route tests hasBlindCorner before
    anchor.held, and the audit confirm's bridge write zeroed refractoryLeft, so a starvation
    down-probe arms from the park inside its fresh shield (the shield guards isWorkloadShift
    only). The next live sample confirms the stride by average density; a first-round starvation
    confirm is not audit-grade, so keepConfirmedPosition re-plants the claim at 66% with the
    smoothed rate the dead samples dragged down and releases the hold, and density walks the window
    to the floor. Seeded 1–8: 38.43 ± 0.22 against the no-burst control's 48.90 ± 2.59. An arm
    that holds the park on a blind corner (parkblind) reads 50.80 ± 0.34, unpriced on the gate
    battery and on the phase-alternation rows where §5's arming guards lost (shiftdead). One
    percent of live traffic in the burst defeats the trigger, and after the shield the recorded
    stand-down applies instead. Class 6, class 4 in the recovery. Real corpus: one blind-corner arm
    from a held park in 10,538 density samples (ConCat), which failed. Constructed-only; a sentinel
    row.

  • needle (specs audit-regret/specs/needle.json and needle_wide.json, 288 samples): a narrow
    static peak with a loss on both sides. A loop (a cyclic scan) over 0.55·max at 28% of requests
    (period ~16k requests, longer than the window's residency at every window up to the cap, since a
    54-58% one-shot scan holds residency near W/0.7 requests, so only main can hold it, by frequency,
    and only while main has room: window ≤ 45%), a two-reference band 0.57·max apart caught from ~41%,
    and the one-shot scan. Static, dense-swept at 1%: flat within 0.4pp from 1% to 40%, a peak at
    44-45%, then ~-0.5pp per 1% of window to 17-21 at the cap; LRU 8-11. The product held at a fixed
    window matches the reference within 0.25pp over 21 (cell, window) pairs, 0.12 around the peak. An
    audit confirms on four consecutive samples above its frozen reference plus 1pp ending on or after
    the walk's fifth sample (AUDIT_CONFIRM_STREAK with AUDIT_COMMITMENT) and nextStride never
    commands zero, so the machine keeps a peak only where the curve stays above the reference across
    four consecutive stride positions reached late enough in the walk. The calibration audit parks at
    32.0% on every cell, seed and size, so the park's up-audit samples 38.2 / 44.3 / 50.6 / 56.7 /
    62.7%, the first stride lands just under the catch point, and the verdict is one reading: whether
    the 62.7% sample, three strides past the peak, clears the bar. The band's share sets that margin
    (-5.70 -4.54 -3.67 -2.08 -1.25 -0.99 -0.54 at shares 0.04-0.10, +1.08 at 0.12), a gradient that is
    a threshold in the outcome. Narrow (shares ≤ 0.09): at 0.08 the rung-16 audit reads 33.89 /
    32.83 / 30.13 against a 28.58 bar, misses on the fifth, and walks on up the far slope to a crash;
    the rung-32 one reads 34.11, overshoots, reverses, and re-crosses the peak at 27.28 because its
    far-side excursion evicted main's loop, then fails through its base (audit rung 64, wait 128, then
    256); at 576 samples the rung-64 one strides 25% across it (57.0% above, 80.2% a crash). 27.15
    against 33.73 at 44%, reactive 26.98 and noaudit 26.88 pinned as well (the flat approach has
    no gradient for either law, so the arms read the flat's rate from different windows), closed ≤
    0.05 while the gap grows 3.65 → 7.71 with the prize; pinned on 12 of 12 admission seeds (one
    decision path: the seeds move sample rates by up to 2.6pp and flip no decision), at 7 of 7
    alignments reached by planting the start so the calibration parks at 33-38% or not at all, on
    three trace seeds, at 16384 and at 576 samples (27.31 against 34.51). A run of four is collected
    and voided where the walk enters the region on its first stride (the band 0.52·max apart:
    38.2-56.6% above, 62.5% not), and the same four positions confirm when reached later in a walk, so
    the region's width alone does not decide it. A share of 0.10 is a 3-in-12 coin (a third pass
    clears the bar by 0.13pp) and is not a row. Wide (0.11-0.12): the audit confirms on its fifth
    sample on the far slope and the best-sample rule returns it to the peak, where it parks at the
    ceiling's rate; the park's next up-audit is the tolerant retry and rides three below-bar strides
    up the far slope, the restored park reads 24.5 against a parked 38.0, and the recovery's second
    sample (a +8.6pp first difference) lands one sample after RETREAT_COVER, so isWorkloadShift
    reads the machine's own recovery as a new workload and the stand-down discards a correct claim on
    its anchor; density then slides off (its rest point is the floor, main's hits being real and worth
    nothing at the margin below the knee) and the starvation ladder cycles near-edge confirms 1 → 64
    without a park. 28.56 against 36.37 at 45%, noaudit 26.15, and pricedshift 34.45: the two
    arms are identical in every dumped field until the one sample where the shipped machine stands
    down, on every seed checked, so +5.9 is that decision. The same responder fires on the narrow face
    at a share of 0.04 and costs nothing, the park it discards being on the flat. At 96 samples the
    wide face reads solved: the discard lands at ~s116. At the restored park the deficit is -13 then
    -4.5 after the up-audit's crash and -1.2 after a down-audit's (4 of 4 each, parked sd ~0.2, ~10:1
    on every seed of both wide cells): class 7's first synthetic reading above scatter, and small by
    itself. Robust to the band's distance (0.52-0.62), the loop's size (0.50 flips the narrow cell
    wide by moving the knee out, the fifth sample clearing by +0.44), two regenerated traces and 16384
    (14 of 14 neighborhood cells at 4.9-8.2). Class 4 in the verdict's geometry (narrow), and class 7
    feeding class 6 (wide), both owned by the recovery layer; class 1 is the co-requisite of each. The
    narrow face needs narrowness only, the run breaking where the far slope has merely returned to the
    flat's level; the far side's depth is what class 7 and the wide face's route need. Nearest
    recorded: crestpast, whose far side declines slowly enough for the fifth sample to clear, and
    every far-band family, whose plateau runs to the cap where a walk wall-sits for its streak. Round
    8's pinnacle (record only; its workspace is gone) overshoots by one stride on arrival, which the
    narrow face does not. Constructed; the narrow face's prize is bounded near 8pp by the generator (a
    steeper far side lets the window hold the loop itself), and no real instance is claimed. The
    ten-point anchor grid has no point between 40% and 50% and under-reads this ceiling by 1.2-3.6pp;
    sweep it densely.

Terrain classes, and what reachability means

The families above are shapes that break a climber. These are properties of the terrain
itself, which is a different axis: they classify the hit-rate-versus-window curve rather than a
trajectory over it, and they are what decides whether a constructed trap is worth chasing.

A non-convex window response is a real class, and on it the rest point is the deficit, not the
travel.
Sweeping static windows, some cells have a trough deeper than 0.5pp on the way from a 1%
window to their optimum, and several are real traces rather than synthetics. The average law's zero
crossing then sits in the wrong basin: on two deep-trough cells it rests at a 1.4% and a 6.9% window
against 80% and 90% optima, and the rest error is 75% and more than 100% of the whole deficit — one
of them runs above its own static value at that rest point. The unimodal control is what makes
this trustworthy: there the average law rests 1.9pp of window from the peak for zero hit-rate loss.
So "the climber holds a small window because it has not travelled" is wrong on this class; it holds
a small window because that is where its steering law rests. This is invisible from the canonical
published curves, none of which is non-convex, and it is the terrain half of the marginal-steering
thread (§5).

Classifying a large loss corpus by whether a fixed window could have won it: two are
structural (no fixed window wins, a real W-TinyLFU limit), none are ceiling-bound, and the
rest are steering — a reachable fixed window ties or beats the competitor and the climber misses
it. Over 80% of the conceded margin is recoverable inside the reachable window range, so raising the
structural ceiling wins nothing. Terrain is concentrated: most of the steering cells are block I/O,
the family with flat (2–6pp) and sometimes non-convex responses.

Where the real-trace loss goes, by mechanism. /audit-regret's teacher splits the climber's
costly samples (those losing at least 1pp to that sample's best static-window copy) by the mode
the machine was in. Over seventeen real density-tier cells at one seed, each weighted equally, the
split is steering 46%, audits 22%, parks and holds 15% and probes 13%, and the families differ.
On cloud-physics (nine cells) steering is 66%: the average law resting at the wrong window, its
error pointing away from each sample's best window on most costly samples, with parks holding a
wrong window on the w097 family (15–40% of its loss). On the ARC cells, whose optimum is the 1%
floor, the exploration machinery leaves it: audits 33% and parks or holds 27% on S3, audits
37–45% on P1 and P8, and probes 65% on DS1 at 1M and 2M. The two problems want different
repairs, the rest point on block I/O and the exploration price on floor-optimal storage traces.
DS1 at 4M is excluded, since its sampled copies score below the climber there.

Do not tune the audit schedule for this class. The equilibrium audit exists for a sighted false
equilibrium, which is exactly this shape, and its cycle costs more samples than these short cells
have — which invites the conclusion that the clock is too slow. The ablation refutes it: against
noaudit the mean delta is under 0.06pp, the significant cells split evenly between cost and help,
one cell of twenty-two moves more than a point, and the mean flipped sign as cells accumulated,
which is what noise looks like. The layer is neither the cause of this class nor its cure, and
shortening its schedule would buy a measured zero while disturbing a constant calibrated on the gate
battery.

Low audit exposure only bites where the terrain has something to find. This is the rule that
decides reachability, and it is why several deterministic constructed regressions are parked rather
than chased. The stillness-starved traps (posjam_d0, bandtrap2, phases_d050) arm zero or
almost zero audits, and the reachability scans come back clean on every real cell. The nearest real
approach is always the same cell, corda, which calibrates once and never re-audits — the known
blind hold — and it costs nothing there, because its terrain is flat and it already scores within a
third of a point of its ceiling. A cell that never re-audits on flat terrain has lost nothing.

High per-sample scatter and wide terrain are anti-correlated in practice, which is the
structural reason the modulation traps are hard to reach. Every real cell with scatter at trap
strength has under 3pp of terrain; every cell with wide terrain has scatter well below it. The
constructed traps hold both at once, and no real cell in the corpus does. The one real cell carrying
trap-strength scatter on wide terrain has an optimum that moves — which makes its scatter
window-informative, precisely the case the goal-metric layer exists to answer.

LLM prefix-cache traces hold both, and the machine climbs on them slowly (the Qwen Bailian
traces, read by bailian; /climber-gate's prefix-cache row). A sample is a few hundred requests
that each re-read a session's whole prefix, so on Thinking@16384 the per-sample rate swings between
14% and 60%, while the static 1%-to-80% span is 4.4pp there, 6.9pp at 65536 and 18pp on
To-C@185364. The response rises toward the 80% ceiling on every such cell, a window planted at 80%
holds there (Thinking@16384 36.99 against LRU 36.26, To-C@185364 43.97 against the static 43.96),
and the MiniSim leader reaches the 80% value within one pass, so the loss is steering into that
basin, not missing information. The window earns nothing until it spans one inter-turn reuse
interval (about a fifth of the cache on Thinking@16384), so it starts starved, and three things
keep it there. Growing the window demotes protected's coldest entries and hands the window
probation's coldest, so the hot set stays in main and keeps hitting there: a walk to 80% scores
what the planted run does at the same point in the trace, but its density error reads shrink until
the hot set migrates (on Thinking@16384, a median of −3.9 at windows of 40% and up on a seed whose
walks stayed 23 samples there and +0.3 on one that stayed 140, against the planted window's +0.6).
On the static anchor the average law is bistable there, negative from 1% to 40% and positive from
50%, where the window takes over main's hits, so the floor and the ceiling both attract and a climb
confirmed below about 45% is reversed. The marginal form crosses up near 9% and rests near 71%
instead; on Coder@65536 the average law rests at the floor or near 40% (1.1pp under the peak) and
the marginal form at the ceiling, and on the To-B control, whose average law rightly rests at the
floor, the marginal form rests near 13% for 0.2pp. And probes and audits are judged across phase
swings several times the window's effect, so most fail and their backoffs escalate.

Two properties matter when interpreting these comparisons (§1). A prefix lookup stops at a
request's first missing block, so only the leading run of hits is usable, while the simulator
counts every block hit. LRU's usable and block hits were equal in the studied replay; that is
not a universal LRU guarantee. A small window streams a long request through TinyLFU's admission
one block at a time, and a rejected shallow block can strand the hits behind it. The shipped
climber's usable hits run 0.8–2.8pp below its block hits in these measurements (To-C@185364 23.4
against 26.2; 0.5–1.7 at an 80% window). The source study's
workload-aware policy ranks blocks using request
category and turn, idle time and prefix depth, preferring deep blocks for eviction on a priority
tie. These fields can be recovered from the released request records, but the flattened generic
access event and cache maintenance stream do not carry them. That richer input is distinct from
the key-only steering loss above.

A bounded information study did not qualify a generic trigger. Across sixteen cells and eight
sampling seeds, two 64-key banks predicted reuse over the next C block events, where C is the
declared cache capacity, using a fixed learner and an authentic demand-fed aged-frequency sketch. On
the seven primary cells' held-out key Brier, bounded reuse history improved 1.63% relative to age
plus frequency (AF), but lost 0.68% to an equally deep AF-child control with no new feature.
Type/turn improved 0.40% over AF child, below the 1% bar, and failed request-weighted and control
checks. Frequency alone had lower primary key Brier than any tested extension. The observer and its
core learner occupied 31,872 native bytes per replica, excluding the shared sketch and offline
evaluator; no production CPU or JVM-size claim was tested. All six history/metadata extensions
failed the preregistered gate. This is a limit of that finite learner, budget and horizon, not proof
that context or inexpensive inference is useless. Prediction or ranking gains do not establish a
harmful-mode trigger, a window direction, benefit from invoking MiniSim, or a qualified policy
change.

On these traces the large cells' loss is mostly sample budget, and the HBM-size cells' is not.
A pass holds 8.5 samples on To-C@185364, 12 on Thinking@65536 and 49–59 on Thinking@16384 and
Coder@65536, so one pass of a large cell is warm-up: To-C@185364 reads 26.2 against LRU's 45.8.
Replaying the trace lengthens the run, but replaying its keys also lets the frequency sketch
remember every key from the previous pass. LRU reads the same on every pass, yet that memory lifts a
static 1% window by 4.8pp on To-C@185364 and keeps the climber small: under plain replay To-C@185364
closes only to about 6.5 below LRU from the fifth pass, and Thinking@32768 replayed four times reads
32.95–36.97 across eight seeds, an apparent escape lottery. With fresh keys each pass (each pass's
keys mapped onto new ones) the climber reaches LRU or the static 80% window within two to four
passes on every cell (passes 2–8: To-C@185364 41.3, Thinking@32768 38.8, Thinking@65536 41.6,
Coder@262144 53.4, against LRU's 45.8 / 39.6 / 42.3 / 55.4), as the 20-day AgentX corpus, scheduled
at a steady 16 sessions, converges to within 1.8pp of LRU at every size from 8192. The HBM-size
cells climb slowly even with samples to spare: Thinking@16384 reaches LRU's 36.3 only on its fourth
fresh pass, about 150 samples in. Under plain replay the reactive law looked the better of the two
given time (+1.3 on Thinking@32768 ×4, +4.4 on To-C@185364 ×8); on single passes it is −1.7 to +1.2
against the shipped machine, and −7.2 on Coder at 699051.

The tier boundary. At the 4096 switch the discontinuity is the policy, not the terrain: on a
constructed straddle one entry of capacity moves the answer by several points while LRU steps zero
and both single-law arms are continuous. On real traces the sign inverts, so the cost is borne
below the boundary and the reactive tier is the loser there. Half the cliff is not in the steering
law at all — the tier gate switches the sample period along with it. The placement is set by the
worst case rather than the mean, and it currently has a single defending cell; if that trace were
ever judged unrepresentative the interval below the boundary reopens at once.

What the boundary also gates is the layer, and that is worth more than the difference between the
two laws. A terrain whose response is flat then a cliff carries no gradient at all in the region
a climber searches from its start, so no local law can cross it and only the equilibrium audit can —
and below the threshold there is no audit. Constructed at a maximum of 4096, the hybrid, reactive
and noaudit arms are one code path and read ~23 with an 11pp spread across seeds, while the
density machine forced at that same size reads 45.8 ± 0.02 against a static ceiling of 48.2. The
tier gate, not the workload, is worth 23pp there. That cell is constructed and its realism is
unestablished: it bounds what the placement can cost, and is not a case for moving it.

Path-dependent rest points. At least one real cell settles somewhere different depending on
where it starts, and not because it is sample-starved. cp_w050@123038 holds 54 samples; from the
shipped 1% start it settles at a 20–21% window, and from a planted 30% start it settles at 14–15%
for +4.65pp on the same trace, landing 1.6pp off its ceiling where the shipped start lands 6.3pp
off. The trajectories name the mechanism: from 1% the first command leaves the optimum, because at
a 1% window the window is trivially the denser region, so the law commands a large upward step and
the run then oscillates for the whole trace, with both of its audits aborting during low-rate
portions of the trace. From the 30% plant a down-audit instead runs four clean strides,
confirms at a 2% window and parks. The descent machinery is fine here; the ascent out of the shipped
start is not, and the audit layer that should catch it crashes on the trace's own phase structure.
One cell of ten, reproducible in both directions, not traced to a defect. The September 2026 replay
does not support a short-exposure explanation: the first audit's launch and crash both contain
492,152 observations, while the hit rate falls from 48.8% to 4.0%. Granting that first crash the
existing bounded retry saves 1.50pp over the next 64·max requests and 0.99pp over 128·max on
admission seeds 1–8, but the walk still aborts two samples later and returns to the same base.
Suppressing the retry's two extra output strides retains 0.92pp at 128·max. The gain does not
require those strides, but later observations and decisions also change; this does not isolate
a timing-only effect or demonstrate a false rejection.
The same delay loses on the cp_w081 and planted shallowmoat controls. A pre-existing rate
decline at arm does not distinguish the useful continuation from costly ones after invalidation;
no production rule was established. These are finite-horizon, request-indexed replay comparisons,
not a generalization to unseen workloads or a change to the crash policy.

Descent has to be measured deliberately, because no natural row exercises it. Every gate row and
every real-corpus cell starts the cache where the product starts it, at a 1% window, and on the
frequency-favorable traces that is already the static optimum — so those rows pass without the
climber having to move, and a machine that could not descend would read clean on the whole real
corpus. Planting the initial window is the instrument. Measured, the descent is the density law
unobstructed at 4–6% of the maximum per sample: the log-ratio error saturates near 1.5–2 nats
even at a badly wrong window, so the step never approaches its 30% cap, and a full walk down from
80% costs 13–16 samples. Recovery is therefore a function of the sample budget and nothing else —
replaying a trace four times takes an 80% plant from −16.05 to −3.63. Against the reactive law the
density tier is the better descender (its period is 4× the maximum against reactive's 10×, and its
step is proportional to how wrong the position is rather than a fixed decaying 6.25%), though
reactive holds the higher level at the shipped start on these cells. Two observations recorded
alongside: a failed probe undoes a correct descent in one capped move, which is deliberate and which
the cells absorb; and what a plant prices is the walk back after a regime change, which is tens of
millions of requests for a large cache.

4. The shipped design (the probe machine, >4096)

Non-starved samples are the pure density step. The additions:

  1. Starvation bar: a region is starved when its sample hits < max(4, requestCount >> 10)
    (0.1%, floored at 4 — the floor binds whenever the sketch's entry-denominated cap makes the
    sample shorter than 4096 requests).

  2. Blind corner gate: probe only when the starved window is small (window starved at ≤max/4 →
    probe up) or the sample is dead (both starved; direction away from the nearer bound, deciding
    at half). A starved main beside a large window arms nothing: the equilibrium audit owns that
    terrain (the upper-corner probe, main starved with window ≥3·max/4 → down, was deleted
    §5's corner entry; the cornerprobe harness arm restores it). A large region
    earning nothing is visible to density and must not trigger a probe (probing "for" a
    scan-filled main destroyed corda_large).
    The gate outranks the goal-metric branches, which is right for the guard rail (it adjudicates a
    shortfall on the starved sample) and was wrong for the audit (it adjudicates over the samples
    that follow): a blind corner that never clears served its whole refractory motionless while the
    clock said the position was due, since refractoryLeft is armed by a starvation walk's
    undoProbe and decremented only inside the hold (an audit's undo left it alone from
    before that every undo re-armed it, which deferred the corner's next probe by the
    whole rung after an audit that was not the probe's doing). A due clock now pre-empts that hold, a sample the machine
    was otherwise spending on nothing.

  3. The walk: bold-driver seeded at the 6.25% restart magnitude in the probe direction,
    scaled by the refractory rung — ×2 at rung 32, ×4 at rung 64, capped at the 30% max step
    (a bound the current tables sit under — ×4 · 6.25% = 25% — so it binds only under retune)
    (2026-07 study: deep rungs previously bought permission to walk deeper but not speed, so
    escapes crept; rung-scaled strides punch through deep stray walls the flat seed stalled in —
    straywall2 +5..+11 across seeds and scales, d050 escapes fuller, corpus 205/205 ties since
    natural workloads never reach the deep starvation rungs — the audit ladder shares this ×4
    stride scaling and is routinely deep on real traces (audit.rung = 64 on 5 of 12
    real density cells, auditWait = 128 on 4), so an argument from rung unreachability must say
    which ladder it means; the full return command on failure is unchanged, which is what
    separates this from the rejected v7 travel-budget family);
    direction flips only on |ΔHR| ≥ the reversal bar, which is not the crash abort's (see the
    crash-abort ending): priced for starvation probes, and for audits
    min(5pp, AUDIT_BAR_FRACTION · max(baseHitRate, noiseBand)) — so plateau crossings survive
    workload jitter.
    Walks honor the 2% floor (via an else-if after the reversal check, so an undo-to-base wins over
    the floor clamp), and carry a sample budget of 16 in Walk.samples, which counts up from
    the arm (it once borrowed the refractory countdown; the two uses never overlap, but
    sharing one field made the walk's depth an inverted expression at each use and forced both
    oracles to bound it by mode) so no walk is unbounded — the review proved the crash veto alone cannot
    bound walks (below a 5% base rate the absolute form was unsatisfiable, which AUDIT_BAR_FRACTION now
    covers, and it stays blind to damage under its own bar). Two
    subtleties the code carries: the walk's anti-freeze re-seed (|step| < 2 re-seeds toward the
    probe direction) cannot be reached by decay in the density tier — the seed is ≥6.25% of a >4096
    maximum and sixteen 0.98-decays cannot fall below 2 — but the floor clamp assigns
    step = min(-0.0, floor − window) directly (the negative zero keeps a wall-blocked restart
    striding into the wall rather than flipping upward off positive zero's sign), so a
    floor-blocked down-walk zeroes its step, re-seeds into the wall each sample, and is
    re-clamped, burning budget in place (the stall note under State); and
    only samples routed to the blind-corner refractory hold decrement its countdown.
    A due audit takes the sample without decrementing it. The hold returns without a steering
    step (only the below-floor lift, item 7 — one definition,
    Reading.atLeastFloor, shared with the steering step so the two cannot drift apart). It used to fall
    through to the density arm, which is what the dead-phase rider exploited — a handful of
    window hits in an otherwise blank sample authorized the maximum step while the probe
    machinery was backing off.

  4. Walk endings (each maps to a verified failure without it):

    A failed walk's return commands sum to its distance from the base. Applied movement can finish
    short when a new sample replaces an unfulfilled transfer; later walks use that landing as their
    base. This approximation is retained after pricing reconciliation.

    • Crash-abort (hit rate ≤ probe-start − the walk-interior bar): undo to the probe's start.
      Starvation probes re-arm their refractory; audits leave it unchanged. The first crash keeps
      the owning ladder's rung unchanged because an exogenous phase shift can resemble probe
      damage, and increasing backoff would delay re-exploration. Consecutive crashes escalate that
      ladder at PROBE_CRASH_ESCALATION. A starvation probe's bar is priced against the workload's own
      scatter — min(max(5pp, 3·rateDeviationEma), 15pp), the adv3 study: the fixed 5pp sat
      below real per-sample noise and severed the blind corner's only exit (the dosed mixture
      trap crash-cycled one stride off the floor for half its run, −3.4 below LRU, healed to
      +4.8 over by the pricing), while the 15pp cap keeps the abort's second job — bounding a
      damaging walk's excursion — after uncapped pricing let walks roam the all-blind families
      at a measured cost (metronome −0.6, recovered by the cap). An audit's crash abort keeps the
      absolute 5pp
      : a priced audit bar lets audits survive to confirm and park more, which is
      the R4-F1 amplification dial (zigzag −10.5, widepin below bar, demoflood −5.2 under
      devaudit). Its depth stays absolute, but
      the threshold is a level test against the rate frozen at the arm, so where the whole rate is
      smaller than 5pp the test is unsatisfiable and only the budget bounds a walk doing real
      damage (arc_S3: 0.0442 → 0.0216 while the window marched to the structural ceiling).
      AUDIT_BAR_FRACTION of the starting rate floors it there. That is not a depth pricing —
      every dead candidate widened the bar so walks survive longer — and it binds only below a
      third of the rate the threshold prices, leaving every family in the graveyard untouched.
      The reversal does not share this bar: the two exits read different
      statistics, so the crash abort is priced by the level alone and the reversal by
      AUDIT_BAR_FRACTION · max(baseHitRate, noiseBand). AUDIT_BAR_FRACTION therefore carries two
      jobs, capping one exit and pricing the other, and the two share a fraction without sharing a
      bar.
    • Reversal-through-base: a bold-driver reversal that would cross back through the probe's
      own starting window found nothing — finish as a failed experiment (undo + ladder ×2) instead
      of walking out the other side (a ±5pp blip once converted an up-probe into an unterminated
      down-walk that pinned the window at a single entry).
    • Adjudication, when the watched region earns ≥ 4× the bar AND the walk has taken at least
      its committed depth (see 5). An up-probe confirms iff
      ln((windowDensity+ε)/(walk.baseProbationDensity+ε)) > 0 — the probed window must out-earn
      main's MARGINAL rate, the probation density frozen when the probe armed — while
      down-probes keep the average-density test (error·dir > 0; the window has no marginal
      substructure to price against). Why marginal: climbing donates only the protected
      allocation, whose squeeze demotes into probation and displaces probation's coldest entries,
      so probation is the boundary a window grow actually taxes; adjudicating against main's
      AVERAGE (the rejected simpler form — four simpler verdict shapes were measured, each trading
      a family, see §5) vetoes genuinely-winning positions because the protected core dominates it
      (ablation: trickle 56.1→69.5/73.2/73.1 across seeds, bandtrap2 69.05→72.55, widepin
      45.5→57.7, sample-aligned phases 46.9→60.1). Why frozen: the walk's own demotions enrich live
      probation (a protected-saturating hot core demoted mid-walk earns furiously there), and live
      adjudication is an ABSORBING false-veto (demoflood: 58.94 pinned with zero confirms vs 67.17
      frozen) — the freeze's own cold-start-transient baseline is bounded and self-heals because
      every re-arm re-snapshots. Confirmation keeps the position and resets the ladder to 1
      (control passes to the density arm in the SAME sample); anything else fails (undo + ladder
      ×2) — neutral verdicts must fail or density walks the window home and the probe refires (S3).
      A zero baseline (probation earning nothing at arm) means any ≥4×bar earnings confirm —
      intended, the opportunity cost of a dead boundary is ~zero (nullchurn stays harmless). The
      named trade, priced and journaled: on low-HR bistable shapes whose probation concentrates the
      reuse band while an LRU-ward escape needs a diluted start (lowmix family, 2–7% HR), the
      frozen baseline false-vetoes an escape the old diluted-average verdict luckily confirmed —
      v5a lands the bad basin 3/3 seeds vs ship 1/3, with ZERO real-trace echo across the 32-cell
      defended set. The walk's exit bars are settled: a starvation
      probe's exits are priced against the live scatter, the audit's crash abort keeps its
      absolute depth capped at AUDIT_BAR_FRACTION of the frozen rate, and the audit's
      reversal — a first-difference test, not a level test — takes the same fraction of the
      larger of that rate and the noise band; every widening of the crash abort is dead (§5).
      Four simpler verdict forms were measured and each traded a distinct family — see the
      local study archives.
    • Budget expiry: failed experiment (undo + ladder ×2).
  5. Escalating commitment — the resolution of the stray-exit dilemma. Stray and transferred
    hits scale ~linearly with the region's size while their density does not, so an absolute exit
    ends deep walks a stride short of a dense reuse band (making that trap absorbing), while
    removing the exit re-creates deep-walk displacement on thin-signal floors (w50/S-class, real
    traces). Neither rule wins both — four rule families were measured (§5). The shipped answer:
    first-round probes exit cheaply (commit 0), and each adjudicated failure lengthens the ladder,
    whose deeper rungs commit the next walk past the stray zone before the adjudication exit may
    fire (length 32 → commit 2 samples, 64 → commit 10). Confirmation is NOT exempt from the gate:
    early confirms on false mid-depth bands reset the ladder and neuter the escalation (measured:
    the long-trace escape fell 53.3 → 38.7 with the exemption). Short traces never reach the deep
    rungs, so thin-signal floors keep the cheap behavior exactly; steady deep-band traps escape by
    ~round 3 (≈100 samples) — an absorbing pin becomes a bounded, temporary dip (demonstrated:
    mixture_d050 scores 33.5 on a dip-length trace and 53.3 on a 3× trace as round 3 escapes).
    A confirm the density arm reverses in the same sample also deepens the ladder:
    the confirmed position is walked home at once and the corner re-arms, so a reset there restarts
    the ladder on every cycle of a dither that never reaches the band it is looking for. That was
    the majority ending of a starvation confirm (668 of 881 across the battery and corpus dumps),
    and pricing it as the completed experiment it is closes the reach half of shallowmoat and lifts
    bandtrap2 +4.1, trickle_s7 +2.8 and trickle_s11 +0.9 (seeded, 8 of 8 on each),
    strad_p8@4097 +1.9, widepin +1.7 (7 of 8) and phases_d050@32k +4.8 (4 up, 4 unchanged);
    phases_d050 +0.3 with two seeds falling to its low basin, the N=8 mean above its bar. The
    confirm's handoff and its zero refractory are unchanged, so nothing is undone and
    the escalation is fast: rung 64 in ~10 samples on a steady dither. Its price is on the
    thin-signal floors with a dither in a low phase, where a deep walk armed in a trough has no
    crash abort (its base is the trough, §5's v7): arc_DS1@1051635 −0.7 on a 10-sample trace and
    deadphase −0.2, accepted (§5 has the guard that removes both and
    what it costs), and on norank_rep_r6 one seed of eight (41.1 → 20.3): a reversed confirm on
    a rewarded ladder deepens it, so the fail that follows waits 16 samples where the reward's
    ladder waited 2, and the ×1 walk that reached that seed's escape re-arms 25 samples later.
    A reversed confirm at the deepest commitment that the goal metric confirms (the audit's own
    streak and beat-base test, Walk.isAuditGrade) is an audit in all but name and is kept as an
    audit's confirm is: it parks. Any other starvation confirm still hands to density,
    so the cheap re-probing that phase alternation relies on is untouched. The park's own audits
    are covered by it: a crash-scale move while an audit walks out of a park is what that re-test
    produces and its ending returns to the park, so it does not stand the park down
    (isWorkloadShift); a shift with no walk in flight still does. Measured on shallowmoat, where
    a deep walk finds a band density then dismantles (36.4 on the wedge seeds against 30.7), and
    on flood_j100 (+1.1 at N=8); bit-identical on the rest of the seeded battery and corpus.
    A kept confirm at or short of the farthest window the ladder's walks have already confirmed is
    a repeat: the machine found that ground and lost it, so a walk that only finds it
    again has not earned the reward, and the confirm keeps its handoff and its zero refractory but
    deepens the ladder as a reversed one does. The ladder remembers the farthest confirmed window
    per direction (Ladder.remember/isRepeat, kept and reversed confirms alike) and forgets it
    when a walk fails or crashes, since a walk that keeps nothing shows the terrain it was found on
    has moved. On absolve_p8 the reward had pinned the rung at 1 on every lure period (a
    first-round stride catches the lure's +5pp shelf, density dismantles it when the lure stops, the
    corner re-arms); with repeats priced the ladder reaches 64 in six periods and the ×4 walk
    crosses the valley in two strides, parking as an audit-grade confirm at the top: 27.95 → 46.29
    at 8 seeds. Bit-identical to ship on every other cell of the seeded battery (64 cells at seeds
    1–2, the movers at 3–8) and the corpus (arc_ConCat −0.03 over four seeds, noise): the census
    of the shipped machine (15 cells × 8 seeds) found no kept confirms at all on bandtrap2,
    trickle, deadphase, strad_p8 and shallowmoat (all wedges), and the kept confirms on
    rep_r6, widepin and phases_d050 are followed by crashes, which forget. The memory is not
    cleared on the anchor's discard: the confirm sample is itself the lure's on-step, and where the
    walk lands at the previous anchor that step discards the anchor on the very sample the confirm
    is judged (measured 41.4 against 46.4 on absolve_p8 seed 1).

  6. Refractory ladder: length starts 16 and doubles per adjudicated failure to 64 (the arming
    doubles first, so effective waits are 32 → 64; after a kept confirm the next failure waits 2;
    a confirm the density arm reverses doubles the rung without a wait).

  7. Below-floor lift: the density arm's clamp raises a sub-floor window to the 2% floor (the
    initial window is 1% and otherwise wedges under the documented floor). The blind-corner
    refractory hold honors the lift too — a workload whose every sample is blind never reaches
    the steering step, and the bare hold wedged the initial 1% window for the life of the run
    (adversary F4, metronome; enforcing the floor there costs the one thin-signal
    cell that was enjoying the wedge its documented price, w50 −0.5pp, siblings unmoved).

  8. setMaximumSize resets the probe state, the pending adjustment (a probe-scale residue
    must not crush a fresh partition), and sample.previousHitRate (a baseline earned under the old
    geometry inverts the seeded direction of the first sample under the new one, at
    restart magnitude), and the sample period is
    min(saturated 4·maximum, sketch.sampleSize) — the sketch's sample is entry-denominated, so
    weighted maxima cannot inflate the period by the mean entry weight (a weight-units period froze
    weighted adaptation entirely), and a near-Long.MAX maximum cannot overflow it. The sketch's
    sample can sit far below the maximum whenever the sketch was sized for something smaller: an
    initialCapacity hint sizes it at construction, and growing the maximum leaves the old sample
    in place until the lazy half-full ensureCapacity. The period then runs well below 4·maximum
    while the steps already scale to the new maximum.

    That used to be accepted as a transient that heals when the cache fills. The period does heal;
    the window it drove to does not. climb() therefore skips determineAdjustment until
    weightedSize() >= maximum() >>> 1, the same half of the maximum that sizes the sketch,
    instead of adapting the moment the sketch exists. A cold cache is not evicting, so its sampled
    hit rate does not describe the region split; before the gate, the compressed cadence read that
    signal often enough to walk the window across the partition. The cold branch discards a full
    sample rather than resetting every cycle: resetting every cycle is not available, since several
    pins record into the sample on caches that never fill, one of them
    (asyncBulkCompletion_doesNotRecordAccess) on ten in-flight futures that weigh nothing until
    they complete, so no maximum both opens the gate and holds the entries. Leaving the sample
    untouched is not available either, because it then grows without bound and breaks
    LocalCacheSubject's mirror of the period gate (HashClashTest reached 233,364 requests
    against a 2,572 period). WindowClimber.discardSample closes it on the same period
    determineAdjustment uses, so the invariant holds unchanged and the cold prefix a first real
    sample can inherit is bounded by one period. Pinned by
    BoundedLocalCacheTest.adapt_partiallyFilled_holdsWindow and its adapt_filled_climbsWindow
    pair.

    Priced 2026-09-08 against product.Caffeine, hit rate %, maximumSize with an
    initialCapacity of 1000 and of 10000 beside the un-hinted arm: arc/S3@400k 29.27 / 40.59
    against 41.04, arc/DS1@1M 8.76 / 8.97 against 12.48, arc/P3@100k 33.50 / 39.63 against
    40.43; metaCDN@250k, wiki1190@250k, cs@8192 and corda@8192 unmoved. The loss tracks the
    compression ratio 0.4 × maximum / hint rather than the hint, so 16× is free, 40× costs 3.4pp
    and 160× costs 11.8pp, and no cell at maximum 8192 can show it because MIN_SKETCH_SIZE
    floors the sample at 2560. With the gate every hint arm lands within 0.4pp of the un-hinted
    one. A patched samplePeriod returning 4 × maximum unclamped recovers the same loss and is
    inert un-hinted, which is what separates the cadence from the sketch's own density; the two
    share sampleSize in production, so no public API separates them. The setMaximum growth arm
    measured the same −3.4pp before the gate and is closed by it.

Shape of the code: determineAdjustment gates on the tier's sample period, dispatches to the
bound tier, and closes the sample; the density tier's route is a router — handed one
Reading (the sample's derived view: both densities, the rate and its change, the starvation
bar, the geometry every branch clamps against), it advances the always-on observers, then
dispatches down the priority lattice to one named branch method per outcome. Each branch owns the rationale that used
to sit inline as a comment block, and the conditions are questions (Reading.hasBlindCorner,
AuditClock.isDue, isBackingOff, Walk.shouldCrashAbort, Walk.isConfirmed,
Walk.canAdjudicate)
rather than expressions needing a comment to read. An ending is priced by the layer that owns it:
probeEnding asks the crash question of every walk and then hands to auditEnding or
starvationEnding. Reading order for the whole machine is the router first, then the branch it
sends you to; sectional comments (Exceptional Scenarios, Probe Walk) carry that order inside
the density tier, and the nested types read in dependency order: the Climber base class →
Sample → Reading → Step → the two tiers → Walk/Ladder → AuditClock →
Anchor/Rates → ProbeEnding. Behavior lives with the state it reads (Beck's tests, applied mechanically in the
2026-08 refactor sweeps); repeated quantities have one definition each — Ladder.stride (the
rung-scaled stride the walk and the audit's room rule both read), Reading.maxStep,
Reading.upperCorner (the wall line that the blind corner, the probe direction, and the
audit's direction rule all test), Step.restartMagnitude, Reading.cappedStride, and
sample.hitRateChange (the cross-sample difference the reactive law, the walk's bold driver,
and the stand-down's shift trigger all steer by) — because two spellings of one quantity is how
the room-rule drift happened.

State: fields on the package-private WindowClimber (reached through the climber field that
AddMaximum generates), eviction-locked, cold path only. The climber itself holds the
observation state (Sample), the carry-over adjustment, and the bound tier. The tier owns
its Step; everything density-only lives on the DensityClimber that tier names. The flat fields
are grouped into small objects — Sample, Step, ReactiveClimber, DensityClimber, Walk,
two Ladders, AuditClock, Anchor, Rates — each carrying the behavior that reads only its
own state. The
grouping is the write-owner map made structural: each row below is one object, so a cross-layer
write has to name the other owner to happen at all (landed object-by-object, each stage verified
bit-close against the pre-refactor tree by the paired battery). The tuning constants live inside
the class that reads them; the one exception is RESTART_THRESHOLD, kept on WindowClimber
deliberately because four mechanisms in four classes read it (the reactive restart, the walk's
audit-side bars, the stand-down's shift trigger, the audit direction's stale test) and giving it to
any one would misstate who owns it. Where a constant has one owner but a second reader, the second names it
(Rates.VETO_MARGIN_MIN floors the audit confirm; the fresh-park shield is
AuditClock.AUDIT_WAIT_INITIAL long), which keeps the sharing visible.

The tiers are types, and the density tier carries its own supervisor. Both extend the
Climber base class (climb over the open sample, and samplePeriod), and a (re)size constructs
the one its maximum falls in: DensityClimber.appliesTo is the tier boundary, written once in
resized, and construction is the reset, so a reactive-tier cache never allocates the density
machinery. ReactiveClimber holds the regime predicate, step rule, decay and period;
DensityClimber holds the proportional step and its period, and with them the probe machine, the
audit layer and the anchor, the exception machinery §2.1's supervisory switching arms when its
law cannot be trusted. Each strategy chooses its initial direction; Climber constructs its Step
from that direction and the maximum, so replacing the tier also replaces and re-seeds the step.
The two laws keep different verbs on purpose (the reactive one climbs, the density one steers),
and the two bold drivers (ReactiveClimber.climb and
nextStride) stay separate: same algorithm, but their bars, reversal conditions, and return
conventions differ, and §4/§5 record each difference as load-bearing.

Declined refactors, decided rather than deferred: no WalkMachine and no method object for
the router — the branches write refractoryLeft, undoRemaining, walk, the anchor, the audit
schedule and the other layer's ladder (the audit-confirmation exception), so either extraction
needs a back-reference and would move the cross-layer writes out of the class that owns them.
refractoryLeft cannot join Ladder (the shared type would give the audit's instance a
permanently dead field), and undoRemaining deliberately outlives the walk, which is why it is
a density-tier field rather than Walk state.

The write-owner map — which layer's endings may move which fields — is the boundary the
H4-C1/F2 repairs restored; keep new writes inside their owner:

Owner Fields Notes
Observation sample (a Sample: hits, misses, windowHits, probationHits, previousHitRate) the counters are zeroed at sample close; previousHitRate deliberately keeps a different lifetime, since it is the memory ACROSS samples that the reactive climber's direction and the walk's bold driver compare against — close(hitRate) carries it forward while zeroing the rest, and only reset (a resize) discards it. WindowClimber keeps thin recordHit/recordMiss/resetSample delegates because BoundedLocalCache calls them on the read and write paths
The active walk walk (a Walk, null while none is in flight: ladder, isAudit, down, baseWindow, baseHitRate, baseSmoothedRate, baseProbationDensity, samples, belowBarStreak, aboveStreak, beatBase), undoRemaining one walk at a time. It is an object rather than eleven flat fields, so "dead state while not probing" is the absent object rather than a comment, and a reader must hold a walk to ask it anything — armProbe is the complete constructor, endWalk clears the field, and the router keeps the ended walk in a local because the undo that prices it still reads the bases. The bases are final: frozen-at-arm is the property the verdict studies keep re-deriving (§4's "why frozen"), so the compiler now holds it. walk.ladder is the arming layer's ledger, which makes "an ending may only deepen the machine that produced it" a reference rather than a lookup; refractoryLeft is the starvation refractory alone and belongs to the row below
Starvation retry starvation (a Ladder: rung, crashStreak), refractoryLeft moved by starvation endings only (an audit confirm cheapens starvation.rung and zeroes refractoryLeft, the one journaled bridge write, spelled out at its site rather than hidden behind Ladder's methods; an audit's undo leaves the refractory alone)
Audit retry + schedule audit (a Ladder), auditClock (an AuditClock: down, waitSamples, stillSamples, lastWindow) moved by audit endings and the position-stillness clock only. The clock owns tick/isDue/restart/reset, so the stillness rule (a moving sample decays the run, it does not zero it) lives with the counter it governs rather than in a climber method. reset preserves the preferred direction, but a resize constructs a new clock with down = true; direction alternation does not survive the resize. AuditClock.reschedule is handed the audit ladder's rung by the ending that prices it, so the clock never reads the other layer's ledger itself
Goal guard anchor (an Anchor: window, rate, held, freshLeft, returning, returnLeft, retestClaim, settleLeft, shortfallStreak), rates (a Rates: smoothed, deviation) anchor/park/veto authority and the rate references. Anchor is the memory and its defense in one object because the layer's three invariants run between those parts, and it now holds them by construction rather than by assertion: a shield lives and dies with its park (park/hold/release are the only writers — an audit's confirm arms a shield, a rail veto holds without arming or spending one, since the shield's clock belongs to the confirm that armed it), a park defends only a planted anchor (discard takes the hold with it), and a return implies the park that follows it (beginReturn arms both). isAt/isAwayFrom give the band test one definition instead of three inline copies, and they are deliberately not each other's negation — an unplanted anchor is neither at nor away, and there is no claim to veto against. Rates owns the EMA pair and the two bars priced off it — noiseBand is the three-deviation width, vetoMargin is that floored at VETO_MARGIN_MIN — so the rail's margin and the starvation probe's walk-interior bar read one definition instead of recomputing VETO_MARGIN_SCALE * deviation apiece. The deviation is read LIVE, and the audit's confirming streak is deliberately not priced off it; both notes live on noiseBand itself. A stand-down that discards the claim re-seeds the pair (below): the event that invalidates a claim invalidates the reference the claim would be re-planted from, and the two are one layer's state
Motion out tier (the bound Climber, owning a Step: size), adjustment the single per-sample command. step.size and adjustment have different owners and lifetimes: adjustment belongs to WindowClimber and is drained by BoundedLocalCache across maintenance cycles as the transfer carry-over, so it changes at the cycle rate while the strategy's step changes at the sample rate. A resize constructs a new strategy with a freshly seeded Step

An audit confirmation is the exception to retry-state ownership: it sets starvation.rung to
one, clears starvation.crashStreak, and clears refractoryLeft to allow cheap re-probing.
A reset ladder carrying a live crash streak would re-escalate on the next crash. An audit undo
preserves the starvation retry state, including its current refractory countdown. Re-arming
that countdown would defer the next starvation probe for an audit's failure, as pinned by
WindowClimberTest.undoProbe_auditRetreat_leavesTheStarvationRefractoryAlone.
Starvation endings leave auditClock, audit.rung, and audit.crashStreak untouched;
audit endings never deepen starvation.rung.

Each layer's rung and crash streak live in its own Ladder. Apart from the audit-confirmation
exception, endings change only their own layer's retry state and schedule. This includes
non-crash endings: an audit's budget expiry leaves
starvation.crashStreak, and a reversal-through-base leaves whichever streak it does not own
(pinned by audit_budgetExpiry_leavesTheStarvationLedger and
walkStep_reversalThroughBase_leavesTheOtherLayersLedger). Both sites previously cleared
both streaks, which disarmed the other layer's two crash responses — the escalation at
PROBE_CRASH_ESCALATION and, for audits, the AUDIT_CRASH_PERSISTENCE tolerance that arms
at a streak of one. A starvation probe ending between two audit crashes therefore restored
the one-sample abort that the moat and the H4-C1 pulse train specifically need it not to
have: the crash-semantics fix was reachable around by an interleaved blind corner.
setMaximumSize additionally zeroes the sample counters and re-seeds the step direction; it
drops the walk, which is how the direction and bases that used to linger as dead state are now
discarded with it. A deep walk that reaches mainProtectedMaximum == 0 stalls (transfers have
nothing to take) while its budget keeps burning — measured 7 of 16 budget samples at the ceiling
in one blind-review round; benign but known. The floor-side mirror exists and rung-scaled strides
sharpen it: a rung-64 down-walk reaches the 2% floor in ~4 strides and spends the remaining ~12
samples standing still before budget expiry prices it as a failed experiment (2026-07 post-ship
review). Adjudicated as intended: the wall IS the probed extreme awaiting adjudication (main
earning ≥4× the bar there confirms), the budget must keep burning or a wall-sit is unbounded,
wall samples counting toward the commitment depth is what lets a wall position adjudicate at all,
and expiry pricing stays uniform because ladder escalation on walled families is load-bearing for
the deep-band escapes. That adjudication covers walks whose wall-ward direction is forced (a
starvation probe's direction comes from the starved region); an audit chooses its direction,
and auditDirection refuses one with less than a full stride of room — a window in
(2·floor, floor+stride] used to be sent into the floor with sub-stride room by the first
interior toggle, clamping on the entry stride and burning 15/16 budget samples motionless
before the information-free expiry doubled both the refractory ladder and the audit clock
(adversary round, F2: mixture d025@32768 spent 54% of the trace at its pin; the
room rule is worth +8.1/+9.7/+11.7pp on the mixture-audit family with every other gate row
unchanged). What was a real bug — a restart-scale improving sample at the wall
striding upward off positive zero's sign — is fixed by the negative-zero clamp and
regression-tested in WindowClimberTest (the wall-sit scenario replays the review's repro).

Why the tiers stay: the hybrid-all rejection at cs@563 (−1.27) rules out density at every
size, and the D2 study closes the weaker question of where the boundary belongs:
corda's own tier crossover sits between 2048 and 4096 (density − reactive reads −0.61 / −0.86 /
−0.50 / +0.43 at 512 / 1024 / 2048 / 4096), so the threshold is within one octave of the
sensitive trace's crossover even though the aggregate crossover is between 563 and 1024. The
placement is set by the worst case, not the mean (§3, the tier boundary). The weighted threshold
also remains in the weigher's units; the tested entry-count alternatives
did not establish a reason to change it.

The goal-metric layer (the F4 answer; shipped, then hardened).
The probe machine's trigger taxonomy is starvation-only, and a workload can hold the density arm
at a sighted false equilibrium — unstarved, earning enough to be believed, and wrong (F4:
whisper's trickle keeps the window sighted; the mixture family re-pins quietly at scale). The
layer that closes it: an equilibrium audit arms the walk machinery from a position held
still for AUDIT_WAIT_INITIAL samples — the clock counts positional stillness only, decays by
one on a moving sample rather than resetting, and its schedule is owned by audit endings alone;
the FIRST audit after a (re)size is a calibration probe at AUDIT_WAIT_FIRST = 4 — and is
judged by the goal metric, AUDIT_CONFIRM_STREAK consecutive raw samples above the
reference frozen at arm plus the one-shot auditBeatBase gate, because density holds the
equilibrium under test and would veto every walk away from it. A confirm plants the anchor
and parks without a parting density step, shielded from crash-scale weather for one initial
audit wait; the guard rail vetoes a noise-cleared sustained shortfall back to the anchor.
The margins are deliberately split (rail 3·dev; confirm run-length plus beat-base). Each layer
owns its ladder, crash streak and schedule, with the audit-confirmation exception described above.
Audit crashes price persistence in time (AUDIT_CRASH_PERSISTENCE on the retry) rather than bar
depth. A park's first audit follows the walk that confirmed it: the confirm ends a
walk on evidence of improvement rather than its exhaustion, so the ground beyond is unexplored
while the ground behind was just covered, and the alternation was sending that audit back through
it (absolve p16: down from the 32% park into the lure's knee, a crash, the undo's arrival
discarding the anchor; shallowmoat basin B and veilmoat: down from the calibration park toward
the floor it came from). It follows only while the claim stands, the park still held at the arm
and the smoothed rate within a restart threshold of its value at the confirm
(AuditClock.settle/chooseDirection); a rate that moved that much with the window still says
the workload moved and the walk's direction says nothing about the terrain. Unguarded, the rule
swept climbtrend_up's trend misconfirms wall to wall (−0.9 at N=8, its sentinel: the 6570 park
starves main of the growing hot set) and re-phased whisper_mod_p6's re-escape (−1.76, its
sentinel), and on moat_h5000 its far-slope walk (five strides down a 1.5pp-per-stride decline
before the 5pp bar trips) made the next audit the tolerant retry, whose undo evicted the band
(−1.75); the guard returns all three to ship. It also fires, incidentally, after a step confirm
whose smoothed rate at the confirm still reflected the walk's path (moat_h3000/h4000,
flood_j100 on seven seeds of eight, shieldtrap_s13 seed 1), where the continued direction had
been a coin flip (moat_h3000 +1.6, h4000 +0.8, h5000 −1.75, flood +4.1); those stay on the
alternation. Priced at N=8, arms rotated: absolve 24.77 → 41.46, absolve_x4 36.45 → 46.89,
veilmoat +3.7, shallowmoat basin B +2.7, flood_j100 +0.81 (one seed +6.5), saw_p40 +0.77
(8 of 8), whisper_mod_a12 +0.24, shieldtrap +0.2, scarburst +0.19; mixture_d010_long
−0.27 (8 of 8), demoflood −0.16 (8 of 8; the calibration audit's confirm overshoots the walk's
own best sample and the continued audit wastes two samples before the crash returns it),
whisper_mod_p12 −0.05; the corpus cp_w050 +1.07 and cp_w044 −0.17 on one seed each, the
rest bit-identical. Alternation resumes after that audit. Invariants and don't-harmonize warnings
live in rules/design-decisions.md.

What the guard rail is for, and what it is not. It fires rarely, and for two distinct reasons
that were once recorded as one. On blind-corner cells it is not reached at all: the refractory
hold outranks its branch, and the starved samples the hold claims are exactly the stalest ones.
Where it is reached, which test binds depends on the population — on constructed cells the
margin does, with the shortfall present and large while the streak never reaches one; on real
cells the streak does, the shortfall holding for many consecutive rail evaluations while one
clearing evaluation resets it (walk, undo, return, retest and blind-corner samples neither count
nor reset it). The corollary worth keeping: between the veto's rate − 3·dev and the confirm's
rate + VETO_MARGIN_MIN lies a band in which neither goal-metric layer can act, and the
machine spends real time there.

Deleting the rail is rejected by the shallow moat doses, where it is the only mechanism that
recovers the window and several audits arm over the run without rescuing it. Everywhere else
removal is free, and on the real cells where it fires it is worth about −0.01pp. So it is kept
because it is free and is the sole exit on a rowed adversarial family — which makes the case
conditional: if the moat rows were ever retired, the case for the rail goes with them, and §5's
agedown kill, which rests on disarming the rail, would need re-grounding. Two facts not to
re-derive: the streak is not consecutive in time (walk, undo, return and blind samples neither
increment nor reset it), and beginReturn's hold is cleared only incidentally, when the return's
own rate move trips isWorkloadShift — which suppresses density steering, not exploration, since a
due clock outranks the hold.

AUDIT_CRASH_PERSISTENCE's tolerance is one-shot by construction. Any non-crash ending,
including the tolerant retry's own budget expiry, retires the crash streak. A workload that runs
crash → fail → crash → fail for its whole trace therefore ratchets the ladder while learning
nothing; the streak legitimately measures consecutive crashes, and the one-shot form is
deliberate rather than an oversight.

5. The graveyard (do not re-explore without new insight)

2026-05 sweeps (~40 variants on real BLC, see large_cache_climber_findings memory): PID and all
descendants (integrator + no setpoint = corda catastrophe), feedback/ghost signals (break on loop),
Borkar, Smith predictor, RLS parabola, shadow/probe/MAB state-replacement (noise-driven switching),
EWMA smoothing alone, dead-zone smoothing (ap_dz, 28 cells, 6 big wins / 3 big losses — "each
fixes some, breaks others"), Gini/Indicator classifiers (skew does not predict the optimal
window), POSINIT. That campaign is the reason the density tier exists: it was a two-session
attempt to improve the reactive law at >512 and its verdict was "ship nothing". Its metric was
relative %, which the project later disowned, so the individual rows are weak evidence — the
dead-zone row in particular was re-tested properly in 2026-08 (below).

2026-07 density-era escapes (Opus session): kickoff-to-center (false-fires on w50 −8), EWMA-regret
(transient — re-traps after the EWMA catches up), annealing HR-walk (inconsistent), wide start
(fixes stress, destroys frequency traces −15), lowering/raising the floor alone (symptom patch).

2026-07 probe-machine ablations (this design's own dead ends, all measured — the tradeoff surface
is real, not tunable away):

  • v6 small-entry probe + absolute exits: stray hits scale ~linearly with window size while
    their density does not, so an absolute hit-count exit fires just short of the reuse band and
    fail-adjudicates the probe (mixture_d025's escape died from a one-step stride change).
  • v7 density-competitive exits + travel budget (¼ max when the other region is healthy,
    uncapped when dead): principled, kept every escape — but deep walks displace healthy-main
    workloads (w50 doubled to −2.1) because a probe that starts in a low workload-phase never
    triggers the crash-abort.
  • v8 give-up-jumps-ladder-to-max: no help; the first deep round is the cost.
  • Verdict logic weaker than confirm-or-fail, and any threshold-only starvation test: see §3/§4.
  • v9 slam-continue (walks continue past unconfirmed 4×bar hits under the budget): escaped
    every deep synthetic (d050 +25, widepin +15, ratchet +3) but re-imported v7's deep-walk damage
    on real thin-signal traces (w50 −2.2, S3 −1.5 vs the locked design) — the ablation that proved
    the two-class tradeoff is rule-independent and motivated escalating commitment.
  • Early-confirmation exemption from the commitment gate: false mid-depth confirms reset the
    ladder and neuter the escalation (d050_long 53.3 → 38.7 on the original binary; the exemption's
    harm did not reproduce at N=3 on the shipped binary — era-scoped, the gate stays strict on the
    measured tradeoff surface). The gate is strict.
  • Live-probation adjudication (the marginal verdict's unfrozen variant): measured as an ABSORBING
    false-veto on the demoflood construct — a protected-saturating hot core demoted mid-walk earns
    ~11.8/slot in probation, so every walk is vetoed and the machine pins at the floor with zero
    confirms (58.94 vs 67.17 frozen vs 74.10 incumbent-on-that-shape). The pre-walk freeze is
    earned, not decorative; on every non-saturating family live ≡ frozen (mixture d010/d025/
    d050_long identical).
  • Average-density up-probe verdict (the rejected simpler adjudication): vetoed real
    reuse bands whenever the squeezed main stayed denser on average (F3 trickle −14..17, B1
    bandtrap2 ×3 discard, widepin/aligned-phases pins) — replaced by the probation-marginal frozen
    baseline; its one superiority (lucky diluted-average confirms on lowmix-class bistable low-HR
    shapes) is the named, journaled trade.

2026-07 fresh-eyes study kills (all pre-registered, data in the
local hill-climber-study workspace ledger): per-sample goal-metric triumph
confirm (spoofed
by phase-rise traps; ratchet/troughwalk2); verdict-gated triumph (widepin +9.7 but two defended
traps regress and the pin persists); probe-time sample compression (halved-sample verdict noise
re-pins mixture_d040 −24); sketch-as-ghost repeat-miss lean (null — never engaged its cells);
confirmed-window warm start (null — widepin never first-confirms); boundary marginal-cost damping
(moved the give-backs +1.5 for the first time, but the admission-boundary comparison is degenerate
by construction — victims are always sketch-hotter — and the equilibrium-gated variant is inert);
and four honest-window-hits verdict forms (absolute / vs-main-average / vs-own-baseline /
vs-baseline-with-bar-floor), each trading a distinct family — the verdict-design tradeoff surface
in that study's report. The one survivor shipped: the rung-scaled walk stride (§4, the walk).

What each step is worth (the ablation prices)

Every step against the whole battery, 91 cells at seeds 1 and 2, beside the firing counts from a
ship run of each cell. Nothing is dead: corner reads 0 because that step was deleted, and every
other site fires. What each step buys where it acts, against what it spends elsewhere: the
retest 33:1, a repeat confirm's escalation 22:1, a park's first audit following its walk 20:1,
the frozen probation baseline 17:1, the audit layer 13.5:1, the return and retreat cover 9.3:1, a
reversed confirm's escalation 7.6:1, the walk's commitment depth 5.3:1, a blind corner's probe
4.9:1, the fresh-park shield 4.1:1, the refractory ladder 3.9:1, the rung-scaled stride 3.7:1.

Both candidates are closed. Restoring the deleted upper-corner probe (cornerprobe)
costs 9.61 across 18 rows against 4.32 across 5, and 27:1 against on that cell set
itself, so the deletion holds on the evidence that flagged it. The walk's commitment depth read
0.23:1 at N=8 then and 5.3:1 now.

The guard rail's veto is the one price that inverted, and the battery cannot decide it. On the
that cell set at the same seed it fell from 11:1 to 1.86:1; over the whole battery it reads
0.28:1, and at N=8 across the eight cells either rail arm moves it reads 0.33:1 with 10.95 of its
12.87pp cost on sidecliff alone (−1.34 to −1.42 on all eight seeds, while cp_w015 splits by
basin, +0.26 on five seeds against −0.26 to −0.37 on three). Every battery cell starts where the
product starts it, and a rail that returns the window to an anchor has little to defend from a 1%
start. Planted, it is worth +6.3 to +6.6pp on four of four seeds on mainsat at a 55% window
(32.42–32.68 against noveto's 25.90–26.65) and +0.15 to +0.52 at 70%. The step stays and the
mainsat plants are what holds it; without a planted cell the battery reads it deletable. On
sidecliff the rail is a net cost on every seed and the retest is what bounds it, since
noretest there takes seed 2 from 59.25 to 51.63.

The retreat cover's widening is real and small. The commit that moved the retreat's
cover out of isParkTest, so it now runs without a held park. nowidecover (the cover scoped
back to a held park, the return half kept) is bit-identical on 66 of 68 rows and costs 0.07 and
0.08 on cp_w081's two seeds. So noreturncover's 44.12pp belongs to the held-park retreat cover
and the return half rather than to the widening, which is why the moat and hazefloor rows move
under that arm while the commit landing the return half read them bit-identical.

A detected regime change must not restart the audit schedule

The latency face of the top-corner residual: a discarding stand-down (isWorkloadShift and standDown with the
window on the anchor) restarts the audit schedule, so a detected regime change reschedules
exploration instead of leaving the rung and the wait the ended regime earned. Seven forms of the
one candidate, each read from trajectories on latebloom's alignment ladder at seed 1 and priced
on the rows that wanted the backoff seeded 1–8 (metronome, h4c1_attack, h4c1_reverse,
whisper_mod_p6, whisper_mod_p12, whisper_mod_a12); the reset forms on the moat rows seeded
1–8; the two live forms on the unseeded battery with every mover re-adjudicated at seeds 1–8
with the arms rotated inside each seed, the floors and the corpus:

  • rearm (the wait drops to AUDIT_WAIT_FIRST; stillness and ladder untouched) and
    rearmstill (the stillness run restarts as well): inert at the 64 alignment, since the first
    re-armed audit goes down from the interior rest (the alternation state) and its failure at rung
    64 re-doubles the wait to 128; rearm reads whisper_mod_p6 −5.35 and phases_d050 −1.9
    unseeded.
  • rearmreset (auditClock.reset and audit.reset, the cold schedule a resize gives) and
    rearmboth (the retest's discard as well): the round-3 hardreset kill re-entered through
    the discard. whisper_mod_p6's period-6 swing is ±6–7pp, every swing at the floor anchor
    discards it, the discard zeroes the stillness run, and with discards every three samples the
    clock never reaches its four-sample wait: no audit arms in the whole run, the window sits at
    the floor throughout, 55.58 against 66.01 on every seed (the F4 pin). whisper_mod_a12 −2.9 at
    seeds 1–8, mixmod_a010 −12.2, widepin −5.7 and phases_d050@32k −5.3 unseeded, moat_h7800
    −1.1 at seeds 1–8. The pin auditClock_rateSwings_doNotResetStillness fails on this form.
  • rearmcold (every discard; the wait drops and the ladder resets, stillness kept): a
    redistribution across the battery rather than a refinement. slowswap_step 40.41 → 31.23 (−7 to
    −13 on seven seeds), absolve_p8 45.95 → 42.50 (−5.7 on four), mixnoise_a10 60.70 → 59.37 on
    every seed (its bar is no drift below 60.4), h4c1_reverse below LRU on three of eight seeds
    (65.39 → 62.84), parkveil −5.2 on one seed; against whisper_mod_a12 63.59 → 66.03 on every
    seed (above LRU, and deterministic where the row was bimodal), crashnoise_a12 +3.0 / +5.0 on
    every seed, parkveil_min +5.0, mixmod_a010 +2.1, whisper_mod_p6 +0.83, the witness +8.75 on
    five seeds. The moat rows bit-identical; floors and corpus within noise. On h4c1_reverse the
    low-phase pulse discards the unheld floor anchor, the wait drops with the stillness run intact,
    so the audit arms on the discard sample with the pulse's dip as its base, reads the pulse's end
    as its own reversal, and each such walk spends the rung: the 63% confirm the shipped machine
    reaches at s46 comes at s101.
  • rearmheld (the cold schedule only where the discarded anchor was a held park, read before
    the stand-down releases the hold) and rearmheld2 (the same with settledRate kept, which
    changes nothing on a12 and costs 0.5 on p6): the closest form. Battery 59 of 75 unmoved and
    ten of the sixteen movers bit-identical at seeds 1–8 (the bimodal rows' draws); the moat rows
    bit-identical at seeds 1–8; metronome and the h4c1 rows bit-identical; floors within 0.2;
    corpus within the spread (cp_w100 45.77 → 45.23 against a 0.36 spread, cp_w098 −0.30). It
    earns whisper_mod_p6 +0.32, scarburst +0.54 and the witness +3.79 on five seeds, and it
    pays on the F1 pair: whisper_mod_a12 63.59 → 61.72, −2.2 on seven seeds against the row's
    ≥ 62.8 sentinel and 2.65 below LRU, and crashnoise_a12 61.69 → 60.13, the high basin lost on
    four seeds. The post-shield swing discards the park, the restart arms an audit four samples
    later from the park's position, and on the ±12pp swings those walks crash and spend the rung
    where the kept wait spaces the audits out.
  • rearmheldcold (the held gate with the stillness kept): whisper_mod_p6 66.01 → 63.31 on
    every seed, since the post-shield discard arms the audit on the swing sample itself; a12
    −0.39; metronome and h4c1_reverse bit-identical.

The latebloom earnings were the same in every reset form and are the candidate's measure. Where
the stand-down fires the gap to the ceiling falls 16.7 → 6.4 (phase 1 = 64), 15.4 → 5.6 (80),
11.3 → 4.9 (120), 9.8 → 4.9 (132) and 14.9 → 5.2 (64 with a 220-sample phase 2); the base is
bit-identical at every seed, since its arrival lands inside the retreat cover and no stand-down
fires; the witness rises on the five seeds whose arrival straddles the cover's end (−8pp on the
landing sample, covered, and −6pp on the next, not); the 48 alignment pays 3.5 → 6.9, an audit
fired from a still-good unheld position four samples after the undo-landing discard walking onto
the flat shelf and failing at budget; and the residual in every form is the alternation's side
choice from the interior rest point, the first re-armed audit spending sixteen samples walking
down to the floor before the next one goes up. The clock alone cannot carry the deep rung, and
the ladder reset is load-bearing: with the rung kept, the first re-armed audit's failure at rung
64 re-doubles the wait.

No form gates clean because the discarding stand-down is the machine's one regime-change
detector and it fires on every crash-scale move at the anchor, which the periodic families
produce every few samples (the track-planted anchor at every swing, the park at the first
post-shield swing) and the pulse family at every dip. The shipped machine pays those discards
with the anchor alone and keeps the schedule. Letting them touch the schedule loses whichever row
the form's shape exposes: the stillness restart starves the clock on the swings, the kept
stillness arms on the dip, and the held gate trades one whisper_mod row for the other. The
distinction a landable form needs, a level change that persists against a swing that returns, is
the signal-classification wall (§5, Ageing an off-anchor claim); a restart deferred until the rate has stayed a margin
below the discarded claim through a settle is a new state inside the confidence-gate graveyard
and was not built. The seven arms stay in the harness under one flag table (rearm*), so the
square can be re-priced after an alternation change.

A regime change can also miss the detector. standDown discards only with the window on the
anchor, so a step that lands while an audit's undo is still carrying the window home keeps the
stale claim; its veto later drags the window back, and the next audit waits out the ended regime's
clock. slowswap_step shows the size: moving its swap 16 samples later (slowswap.py --p1 166)
puts it inside such an undo, and the shipped climber reads 33.3 against 40.3 at the committed
timing, holding near a 25% window with a 1% hit rate through a 64-sample wait while the new regime
wants 80%. Discarding on an off-anchor step meets the same wall, since the controller's own moves
also step the rate.

Guarding the probe's arming, and re-reading its verdict, on a regime-change sample

Four guards on the starvation probe's arming, built for arc_DS1's trough-armed confirms at a
1M maximum (§3's thin-signal floors; the v7 shape above), each priced on the unseeded battery,
the floors and the corpus, with the movers re-adjudicated at seeds 1–8 and the arms rotated
inside each seed:

  • shiftarm (a blind corner on a sample whose rate moved by a restart threshold from the last
    closed sample holds one sample before arming): DS1 11.99 → 13.74 at 1,000,000 and 13.45 →
    14.77 at 1,051,635, floors and corpus within noise, and the phase-alternation families lose
    their escape, since the flip sample is the blind sample and a hold on it arms nothing when the
    next sample is sighted: phases_d050 −10.6, widepin −9.4, phases_d050@32k −4.4,
    shallowmoat −2.7 (unseeded N=8/8/5/2), against mixture_d050 +5.7, blindlock_blind +5.4
    and metronome +2.3, where a shift-armed probe was failing and imposing its refractory.
  • shiftarm0 (shiftarm, and the first sample holds as well): the above plus demoflood
    −10.6, absolve −5.2 and slowswap_ramp −2.9. The first-sample probe is the escape on those
    rows, so a first-sample hold on its own (coldhold) was not run.
  • shiftdead (the hold only where the sample is dead, both regions starved, and a shift: the
    v7 walk armed in a trough and nothing wider): DS1 +1.73 at 1,000,000 and +0.42 at 1,051,635,
    since two of the four arms there are on samples with main at 8k–146k hits against a bar near
    4k (the wedge round's "low samples straddle the starvation bar"); 14 of 16 re-adjudicated
    cells bit-identical at every seed, widepin 55.48 → 47.80 (five of eight seeds at −8.4 to
    −13.7; its bar is no per-seed drift) and phases_d050 −0.69 on four seeds.
    The rows that pay are the ones whose escape is a probe armed on exactly the sample the guard
    holds.

The verdict side was then exhausted on Ben's ask, three forms on the same cells and battery:

  • rebase (a starvation walk whose sample rises by a restart threshold re-freezes its base,
    rate and probation density, on that sample once and walks on; rebasepos returns the confirm
    to the rebase sample's window): DS1 13.83 / 14.62 at the two sizes, 27.44 at 2M (an extra
    stride), and phases_d050@32k −13.7, phases_d050 −12.3, widepin −5.9, norank_rep_r6
    −3.2. Re-freezing the probation baseline on a live sample is the nofreeze failure for the
    escape: the walk's own demotions enrich live probation into a veto.
  • deadbase (a walk armed on a dead sample is adjudicated against the live probation density):
    DS1 14.07 / 13.59 (alignment-limited as shiftdead), slowswap_step −6.7, widepin −5.2,
    phases_d050@32k −3.5.
  • deferrise (a crash-scale rise defers the verdict one sample, holding the position, with only
    the crash reference moved and the verdict baseline kept frozen at the arm): DS1 13.91 / 15.12
    (above the hit-rate law's 14.91 at the gate's size), 2M −0.09, S3 +0.13, and phases_d050@32k
    −13.2, widepin −9.4, phases_d050 −7.4.
    Every form that refuses or defers a verdict on a regime-change sample loses the phase-alternation
    escapes, since on those rows the flip sample is the only sample the verdict can be read in: the
    regime alternates at the sample cadence as DS1's does, and there the larger window is right. At
    the decision the two are one signal. DS1's cost at this size stays the wedge round's accepted
    price; wedgeshift (above) remains the recorded guard for the shape, still not
    landed; the untried lever is the sample period itself (dens10x reads 14.34 at 1M by taking four
    samples instead of ten, and on S3 +0.2 / +1.0 / −0.6 / −0.3 at 100k–700k), a cadence question
    rather than a rule for this shape.

Whole alternative controllers

  • Marginal-loss controller: locally right is not globally sufficient. Exact loser-refault
    attribution was locally informative: all seven screened deltas were non-negative (0 to
    +1.80pp). The controller built around it still failed its final-regret gate. Moving a fixed 1%
    of capacity every 2C requests left the ten-cell nearest-rank p90 regret at least 2.83pp against a
    1.5pp bar. P3 states the limitation cleanly: hit rate falls while moving from the 1% start toward
    roughly 20%, then rises to a remote 80% basin. The local signal correctly points back toward the
    floor and therefore cannot discover that basin. Adjacent-direction accuracy and global search
    regret are separate gates. Keep the signal as a possible diagnostic or local trim input, not as
    evidence for a replacement climber.
  • Replicated MiniSim: useful information, rejected realization. The first prototype was
    invalid: ordinary static W-TinyLFU arms recomputed probation at each window size, while the live
    host keeps probation fixed and transfers capacity between window and protected. The repaired
    experiment constructed reachable integer segment triples, clamped and deduplicated targets
    before scaling, followed capped moves to the exact approved coordinate, and supplied the same
    request-indexed admission variate to every arm. At the co-minimal 128-entry, three-cohort setting,
    its six-cell median and p90 static-oracle regret were 0.197pp and 0.305pp: remote counterfactual
    ranking did contain useful information. The 27-policy realization then failed every production
    cost bar: 103,816 bytes retained when empty, 314,784 bytes steady, 24 bytes per sampled hit, a
    median 1.96 bytes/request on the natural 95/5 mix, and a 37.52% median hot-hit throughput
    regression. This rejects that realization, not the information source.
    The useful correctness subset was later extracted to main: registered MiniSim now preserves the
    live fixed-probation geometry, removes full and scaled aliases, and computes each integral move
    from the host's actual window coordinate. The expensive replicated controller remains rejected.
    Equal RNG seeds still do not give its arms request-indexed common draws, so MiniSim should not be
    described as a fully paired counterfactual oracle.
  • Learners over sampled miniatures: better on quality, rejected on cost. A learner over the
    registered MiniSim miniatures (fixed-probation copies of about 100 entries fed a 1/R key sample)
    that keeps a discounted miss total per arm over a 64·maximum horizon and moves the host window to
    the leader gains a median of about 3.7pp over product.Caffeine on the gate battery, with no row
    worse than about 1pp, and 0.5 to 1.1pp on eighteen real density-tier cells (simulator host,
    unseeded, one to three draws per cell). Five arms at 1/20/40/60/80% keep the gain, so the prize is
    reaching the right basin rather than resolution. Its real-trace wins are the recorded give-backs:
    the average law's rest point on frequency-favorable traces (the cp_w097 family 4 to 6pp, P8
    and S3 1 to 2pp) and cp_w050@123038's start path. Its own failure is miniature fidelity: just
    after DS1@2M fills, the miniatures rank a 76% window above 1% where the full cache scores 56.8
    against 61.9, costing 1 to 2pp there and at 4M. Three variants are dead. Hedge at a static-regret
    learning rate (H-MC, Ben Mazziane and Zou 2026) cannot follow a regime change: −5.9 on the corda
    stress at 512, −1.4 and −3.6 on slowswap. Its maximal coupling of consecutive picks buys fewer
    moves and no hit rate over a deterministic leader. The registered per-epoch argmin chases
    alternation, losing phases_d050, widepin and rep_r6 by 6 to 12pp to either learner with
    memory. Rejected as a product mechanism on costs that grow with the arm count or the maximum:
    per-entry nodes and an eviction loop for each miniature (only the sketch and a key table can be
    shared across arms), miniatures sized in proportion to large maxima, and a grid to choose. Kept
    as /audit-regret's ghost, the simulator's minisim-leader, because it is an online policy
    rather than a hindsight one: it can exceed every fixed window on a phased trace (39.3 against
    33.5 on the corda stress at 512), and its copies' misses at each sample say which window that
    sample rewarded, the per-phase reference against which the skill reads the climber's costly
    episodes. As a gap-finder it adds little: its gap to the climber ranks cells as the static gap
    does (rank correlation 0.99 over the gate rows, 0.94 over real density-tier cells), and its own
    window, flat on the traps and dithering among near-equal windows on real traces, localizes
    nothing. Its configuration is measured. With five arms and MiniSim's ~100-entry miniatures it missed narrow optima
    (crestpast 62.6 against a 64.7 ceiling), and at finer grids the miniatures mis-ranked near
    neighbors (moat_h7800 48.3, where five arms read 49.5); miniature size was the larger lever.
    Seventeen arms at 5% steps with full-size copies up to 20,000 entries read 64.6, 56.8 and 50.8
    on crestpast, metronome and moat_h7800 (ceilings 64.7, 55.5 and 49.7), sit a median 0.1pp
    above five arms across eighty cells with one row lower by more than half a point (widepin
    −0.8), and forty-one arms add nothing (a mean 55.60 against 55.63 on the twenty cells where the
    configurations differed). It lands below the climber on DS1 at 2M and 4M at every size tried.
  • Bounded reuse-survival estimator. A smaller primitive estimator was a credible attempt to
    retain the remote-ranking information without full policies. Its registered realization missed
    both information bars: 0.727pp median winner regret against 0.5pp, and 2.849pp p90 against
    1.5pp. Retire that realization as controller authority. The broader reuse-survival idea was not
    disproved by one resource setting and seed family.
  • Compact Shadow prototype. Its controller semantics and bounded layout checks passed, and the
    observer allocated nothing in its steady interval. The first pair of the terminal four-cell cost
    gate then measured 32 bytes per candidate command against 16 bytes for the baseline, so the
    frozen stop rule ended the study before quality was scored. Compact state is possible; an
    affordable observation seam and a quality win remain unproved. There is no superiority result.
  • Archive boundary. The trace-metadata prototype, compact primitives, static oracles, runners,
    manifests, and experiment evidence plumbing have no upstream consumer or shared owner. Keep them
    archived until one exists. Whole-file transplants and a public SimulatorAdmissionRandom API
    are rejected. A future consumer needing exact randomized pairing must first show that both the
    draw count and request-index digest match.

Cheaper forms of the veto's return re-test

  • A hold before the retreat commits. A completed shortfall streak stands the window still for
    two samples and re-samples, cancelling the retreat if the rate recovers to within the veto
    margin. Endpoint +1.16 against the retest alone's +1.39, three negative seeds of sixteen against
    none, norank_flood_j100 −0.08 and shieldtrap_s11 −0.01 where the retest alone is
    bit-identical, and cp_w081 −0.42 on the corpus where the retest alone is +0.07. The mechanism
    is self-defeating: spreading the arrival drop across the hold's own samples is what puts it
    under the crash-scale threshold, so the hold manufactures the masking the retest then repairs.
    The cancel branch never fired on any cell. Do not re-propose a pre-retreat hold without a cell
    where a veto is shown to fire on noise.

Other ways to judge an audit's verdict against its walk

  • bestgap (the verdict pulls back only past one stride of separation, the arrival transient's
    own width): cp_w097 +0.73 against demoflood −3.59 and mixture_d050 −0.33. demoflood's
    real crest and cp_w097's noise spike are both one stride behind the walk's end, so the
    pull-back's distance does not separate them.
  • bestrough (the pull-back must beat the largest sample-to-sample move of the walk itself):
    restored the then-baseline cp_w097 value recorded by the 07c6e370c crestpast study
    (47.61 under its original unseeded simulator protocol; not comparable to the fresh seeded N=8
    mean) and costs demoflood −3.59 again plus hazefloor −0.73. Two independent statistics refusing a
    real crest in order to refuse a spike is the shape of item 1's finding on this layer: the signal
    cannot tell a crest from a peak.
  • monoland (the margin plus a monotone decline from the best to the walk's end): inert,
    bit-identical to the shipped form on every decisive cell. Where the margin has decided, the shape
    test has nothing left to refuse.
  • bestflip (a park short of the walk's end points the next audit the other way, since the
    ground beyond is now known worse): hazefloor −3.92. The follow rule's value is the direction the
    confirm earned, not the unexplored side.
  • landclaim / refclaim (a covered landing keeps the claim but releases the hold, so density
    may still improve on it): moat_h4000 −2.29, a re-derivation of the recorded arrive kill —
    without the discard's Rates.reset the rail's margin stays at its 1pp floor and track re-plants
    the anchor down density's slide.
  • retreatref (the retreat's samples judged against the rate the probe left, the freshref
    rule applied to the stand-down rather than to the confirm): weaker than covering the landing
    outright (crestpast −0.09, the plant-at-40 cell −0.45), because the landing's recovery is itself
    an arrival transient that takes more than one sample, so a level test against the pre-walk rate
    still fires while a first-difference test no longer does.

Other ways to un-stale an away-anchor claim

  • Discarding the claim on a crash-scale swing that lands with the window still (stilldiscard,
    still2, still3, quiet): fixes the family exactly as the landed reference change does, and
    costs the rail's control rows because a still swing is not necessarily the workload's. The one-
    boundary form mistakes a retreat's echo (moat_h4000: the undo arrives, the collapse lands one
    sample later inside the band, −2.3 / −3.4); two or three boundaries excuse the echo but not the
    terrain's erosion at a still window (moat_h3000: fourteen still samples, the window's hits fall
    5,662 → 112 with main's flat, then a −7pp break: −0.7 to −1.9 on eight of eight). The claim the
    swing discards is the machine's memory of the prize the rail then recovers (veto to 0.62 kept,
    0.46 discarded). Region composition would separate the cases seen (a shift moves main's hits, the
    terrain's collapse only the window's) and was not built: a second threshold on a heuristic.
  • Re-seeding the goal metric on a still swing while keeping the claim (resetstill,
    freshreset): the re-seeded deviation prices the shortfall against the kept claim as real, the
    rail vetoes into the dead anchor within four samples and the audit that then arms from it confirms
    a mediocre position (ghostclaim_p30 41.2 against the reference change's 49.2). The deviation
    spike a shift leaves is what holds the rail off while the audit walks first.

Variants of the repeat-confirm memory

  • The repeat memory cleared on the anchor's discard: the confirm sample is the lure's on-step and,
    where the walk lands at the previous anchor, that step discards the anchor on the sample the confirm
    is judged; the chain breaks (absolve_p8 seed 1 41.4 against 46.4 for the fail/crash-reset form).
  • The park's first audit following the confirmed walk unguarded (momentum): absolve +16.7,
    veilmoat +3.7, shallowmoat basin B +2.7, flood_j100 +4.1 (N=8), moat_h3000 +1.6, moat_h4000
    +0.8, crashnoise_a12 +0.5, shieldtrap_s13 +0.6 against moat_h5000 −1.75 (four seeds at −3.4: the
    far slope's gentle decline takes five strides to trip the 5pp bar, the next audit is the tolerant retry
    and its undo evicts the band), whisper_mod_p6 −1.76 (its sentinel), climbtrend_up −0.9 (its
    sentinel: trend misconfirms swept wall to wall), saw_p40 −0.5. The guard on the smoothed rate is what
    ships.
  • The guard on the confirm sample's raw rate against the arm sample's: fragile to the lure's phase
    (absolve_p12 seed 1 falls back into the trap, +0.6 against +17.6) and keeps moat_h5000 −3.4 and
    saw_p40 −1.4; the smoothed reference is robust to the phase and fires on the trend.

Releasing a park that the workload has starved

  • unpark (a parked, non-blind window starved for a sample discards the anchor and hands the
    sample to density; unparkboth also on a starved main): fires at three sites in 642 audit
    confirms and loses one of them big (phases_d050@32k seed 8 −5.2 against rep_r6 seed 4
    +3.8). The park on a starved position is priced by rarity, and the census is the instrument to
    re-run before proposing any rule about it.
  • marggate and margrest on flood_j100: both are the average law there. The gate's
    "probation richer than main's average" test is satisfied by the SLRU geometry alone whenever a
    rotation exceeds protected (every promoted key is demoted before its reuse and protected earns
    nothing), 47 of 48 steering samples, and the rest band is entered only at 57–76% windows. The
    marginal signal itself is right on the cell (tail hits 0 at every window; ungated marghalf
    63.19 against 51.98) and dead by the frontier above.
  • arrive alone (an undo's or a return's landing sample is not judged for a shift): moat_h4000
    −2.69, h5000 −0.50, h3000 −0.36 on every seed. Without the discard's Rates.reset the rail's
    margin stays at its 1pp floor and track re-plants the anchor down density's slide into the
    valley, so the rail's moat win turns out to rest on the reset widening its margin. (The
    undo-landing half of this arm is what the verdict covers; the return half, re-expressed through
    the retest state once the retest existed, was implemented as
    isReturnTest, and on release HOLD by the repeated-real-trace recheck.)
  • auditshield alone (a shift during an audit walk armed from a park does not stand the park
    down): bit-identical to ship everywhere; the undo's arrival at the anchor discards what the crash
    spared. arrive + auditshield as an unconditional pair: moat +6.5 in total and flood
    +2.2 against demoflood −1.88 on every seed (its bar) and whisper_mod_a12 −1.83, the park
    held where ship's release let density drift to a better position. The park-retention trade with
    its mechanism; a scoped form on the top-corner residual is where it would be worth something.
  • pricedshift (the stand-down's shift trigger priced as a starvation probe's crash bar,
    clamp(3·dev, [5pp, 15pp])): crashnoise_a12 +1.71 and deterministic, rep_r6 seed 4 +30.9, and
    cp_w081 −0.39 on 4 of 4 seeds, arc_P8 −0.19, arc_ConCat −0.13, moat_h7800 −0.94 (a basin
    collapse). The fixed trigger's churn on noisy cells is inert on the corpus and protective on
    cp_w081, where a persisting anchor lets the rail veto on slow weather. A trade; do not re-derive
    the level, and do not read the C3 churn as a defect without this cell.
  • nocorner was the diagnostic that priced the upper corner's probe here (+2.3 rep_r6, +3.8
    balloonflip against −0.2 on cp_w015 and arc_ConCat, read then as "the probe stays"). The probe
    was deleted and the arm with it; cornerprobe restores it, and the
    battery reads the restoration 2.2:1 against.
  • deferreward (the wedge pricing's next-sample half): a redistribution of rep_r6's seeds
    (+3.0 mean, one seed −20.9) and inert or slightly negative elsewhere (phases_d050 −0.49); the
    ladder's doubling is coarse against a 91-sample trace, so any faster escalation wins the seeds
    where the deeper probe finds the band and loses the one where it hits a thin burst. Not landed;
    the arm is in the workspace if the sentinel's mean is ever worth that.

Other prices for a confirm the density arm reverses

The round priced a starvation confirm that the density arm reverses in the same sample (a
"wedge"). What shipped is the escalation without a wait; the rest is dead:

  • wedgehold (the wedge keeps the rung, neither reward nor escalation) and wedgefail (the
    wedge is a failed experiment: undo, escalate, refractory = rung): both hold the floor long enough
    for the calibration audit to arm and misconfirm on the warmup trend, so shallowmoat's wedge
    seeds read 27.5 / 27.3 against ship's 28.5, and neither reaches the prize inside 64 samples.
  • wedgewalk (the wedge does not end the walk): the v9 slam-continue family (§5 above), kept as
    the diagnostic upper bound (33.5 on the wedge seeds) and not promotable.
  • wedgeflat (only a wedge on flat terrain escalates; one across a restart-magnitude change
    keeps the rung): trickle_s7 −6.7 (four seeds at −14) because the kept rung makes the next
    fail's refractory 32 samples instead of the reward's 2.
  • wedgedead (a walk armed on a dead sample is first-round): arc_DS1@1051635 −1.7; its low
    samples straddle the starvation bar (2,662 main hits against a bar of 4,108 on one, 10,221 on
    the next), so the second is a rung-64 walk to an 80% window.
  • wedgelow (a walk armed a restart threshold below rates.smoothed is first-round): never
    fires on the alternating trace, because every sample is a shift and rates.reset re-seeds the
    smoothed rate to the arm sample itself.
  • wedgeshift (a walk armed on the sample the rate fell into by a restart threshold is
    first-round; the guard the shipped rule can carry): measured and not landed. It restores DS1 and
    deadphase bit-for-bit and removes phases_d050's two seed flips, is bit-identical on every
    non-wedge cell run (arc_P8, arc_S3, cp_w015, cp_w050, mixture_d050, straywall2, slowswap, moat),
    and gives back strad_p8@4097 +1.9 → +0.5, widepin +1.7 → −0.8 and phases_d050@32k +4.8 →
    +2.7. Its evidence for the floors is one cell (DS1 is the only floor cell with any starvation
    confirm), so the pass is by construction; the instrument that would price it is the unspent
    stillness holdout. Add it if trough-armed deep walks show up at scale.

The 1% initial window is not worth moving

PERCENT_MAIN has never been re-asked since it was chosen. The hostile-window study above gave it
an instrument, so it was swept properly: 77 cells classified by their measured static optimum
(43 frequency-favorable at ≤5%, 14 middling, 20 recency-favorable at ≥40% — the axis the hostile
study could not see, since its cells were screened for a small optimum), then 37 of them swept
at starts of 1/2/5/10/20%, plus a 30% arm on the 23 density cells.

The bar was fixed before measuring: corpus mean ≥ +0.50pp, no stratum mean below zero, no
workload family losing more than 1.0pp, holding at each tier separately. The best arm returns
+0.049pp
, a tenth of the bar, and no arm satisfies the stratum clause. Applying this study's own
readability floor (≥15 density samples) the whole table collapses further: every arm's corpus mean
lands within ±0.21pp of zero, the best being +0.018 at a 20% start. The mid stratum's apparent
+0.25..+0.87 is entirely corda@2k and corda@4k at 3.3 and 6.6 samples, which price the
start rather than the controller; with the floor applied that stratum is negative at every arm.

The mechanism is why, and it is the part worth keeping. The climber absorbs the plant
asymmetrically: on recency workloads it captures 96% of what a larger start could statically
buy (frozen +2.68 against a delivered +0.11 at a 10% start), because it was walking to a 20–50%
window anyway and the plant only shortens a journey already underway. That assumes the climber
walks; on the LLM prefix-cache cells (§3, terrain classes) it walks too slowly for one pass, where a
plant does pay (one pass of To-C@185364 gains +8.7 from a 10% start and +17.5 from 50%), though with
fresh-key replays it reaches the large window within two to four passes anyway. On
frequency workloads it passes 90% of the harm through (frozen −0.80, delivered −0.72), since there the plant is pure
displacement to be walked back at the descent rate of 4–6% of the maximum per sample. A larger default therefore buys almost
nothing where it would help and costs nearly full price where it hurts. On two cells it lands in a
worse attractor than 1% reaches at all: wiki_1191a@64k frozen −0.12 → delivered −0.99 on
3 of 3 seeds, arc_P13@64k frozen −1.59 → −3.41.

The storage families are the case against moving it: the ARC set is 16 of 18 frequency-favorable
with an optimum at a 1–2% window, losing monotonically as the start grows (frozen −0.15 / −0.44 /
−1.18 at 5 / 10 / 20%, worst −5.41). A 5–10% default is a straight tax on exactly the large,
frequency-signalled caches the 1% choice was made for.

cp_w050@123038's basin (§3, Path-dependent rest points) does not argue for a larger default: at N=8 the 2/5/10/20% arms
read +0.02 / −0.01 / +0.04 / −0.21 and all converge to the same ~20.6% attractor, and only the 30%
arm moves (+4.72, and one seed of eight already falls into the bad high basin from a 20% plant).
It is a threshold between 20% and 30% that a 5–10% default captures none of, and it remains a
question about the audit layer rather than about the default.

Not decision-bearing but recorded: strata are near-collinear with families in this corpus (ARC 16
of 18 frequency, cloud-physics@16k 12 of 14 mid-or-recency), broken only by scarab_recs,
wiki_1191a, msr_prxy_0/hm_0 and the lirs reactive cells; cp_w097/098/100/101@16384 are
near-duplicates and count as one workload; and cp_w050@2048 has its whole static curve below
LRU (ceiling 13.65 against LRU 14.52). No holdout was spent, and none is owed, since nothing was
fitted. Two rest-point give-backs above the documented 0.5–2.5pp band turned up in passing and
belong to the marginal-steering thread (§5) rather than here: arc_P1@64k settles at a 30% window against a 1% optimum
for 3.25pp (45.63 against a 48.88 frozen ceiling). cp_w097@16k is the other face, but it does
not settle: its 135-decision endpoint is near 47% against a broad 5–15% optimum, and continuous
270- and 541-decision runs stay near 47% while cycling through seed-dependent walks and parks.

An LRU estimate as an audit trigger

A sampled estimate of what an LRU of the same maximum would hit (SHARDS-sampled keys in a
fingerprint-and-stamp table of about 25 KB, read through AET with the SHARDS-adj correction, within
about 1pp of exact LRU) can schedule an up-audit when LRU out-hits the cache by more than 3pp for
four samples. Under plain multi-pass replays it brought every LLM prefix row within 2.6pp of LRU at
steady state and lifted the battery by +1.6 with no bar failing, but the steady-state win was the
replays' frequency memory (§6): with fresh keys per pass the shipped climber converges within two to
four passes on its own, and the trigger adds 0.0 to +0.8. It also cost 16% of EvictionBenchmark
throughput at 5,000 entries and 9.5% at 10,000, where 2,048 sampled keys cover 41% of a 5,000-entry
cache's requests; capping sampling at 1/16 of keys cut that to 2.7–3.7%, the rest being per-request
work. Early in a run the cold frequency sketch lets LRU lead on frequency-favorable traces too, so a
lead-triggered audit ahead of the climber's own first audit confirms premature windows. What
remains is +1.6 on synthetic traps, which does not pay for the state and the throughput. Price a
per-request sketch at the smallest maximum it serves before validating its hit rates.

Accelerating warm-up: jumps, momentum and shorter samples

On a recency-favorable trace the climber reaches its large window, but slowly. On the LLM
prefix-cache cells (§3, terrain classes) one pass of a two-hour trace is 8.5 to 25 samples at the
larger sizes, and the climber spends 9 samples (To-C@185364) to 74 (Thinking@32768, about three
passes) at the floor before steering climbs: a grown window reads sparse while main holds the
captured hot set, and probes and audits from the floor fail on phase swings. The rules that shorten
that phase buy +1.6 to +2.8pp on those first passes, and each costs something on every seed: a
frequency-heavy real trace 0.7–2.4pp or, once gated so that no real trace triggers it, the
battery's traps. None passes the bar the start-window ruling above set:

  • A geometric up-audit from the floor (stride doubling while the hit rate holds), with dip
    tolerance and arming ahead of the calibration wait: +2.8 on the prefix cells, but To-B −1.2 and
    ARC S1 −2.7, S2 −1.2, S3 −0.9. Gating it on an early sample in which the window out-earned main
    leaves the controls bit-identical but not ARC, since a cold cache makes every window look dense in
    its first sample. Judging it against its base plus the hit rate's prior rise, with arming held two
    samples, keeps +2.4 (To-C@185364 +7.4) and still costs P8 −0.9, S3 −0.8, S1 −0.7.
  • The same jump behind a fill-drop gate, which allows it only when the hit rate falls once the
    cache is full (the second and third samples averaging under 70% of the first): every corpus cell
    (seeds 1–2) and control is bit-identical and the prefix cells gain +1.6, but the battery fails
    slowswap's step and ramp (−11.7, −9.3) and mixture_d050_long (−1.3), each on every seed, while
    absolve and absolve_p8 gain +9.2 and straywall2@16k +4.0, for a battery mean of +0.4. On
    mixture_d050_long the doubling strides land at 80% and park at 69%, where the shipped walk parks
    at 56%. The early audit also moves every later one, so on slowswap an audit crashes just before
    the swap; the swap's rate step lands during its undo, away from the anchor, where standDown
    ignores it, and the stale anchor vetoes the climb. That trap is timing-sensitive for the shipped
    climber too (33.3–41.8 as slowswap.py --p1 moves the swap ±24 samples, the row's committed
    timing near its best), but averaged over those timings the jump still costs 2.1. Handing the
    landing to density steering instead of a park does not escape this: at To-C@185364's 80% landing
    density reads the window sparse (log ratio −0.8, main still holding the captured hot set) and
    would give back the gain, while at mixture's 69% overshoot it reads −0.5 and would correct it,
    so the landing carries no signal that tells the two apart.
  • Momentum, consecutive same-direction density steps growing ×1.5: +0.3, too small.
  • Samples of one maximum instead of four until the first audit: +2.2 (Thinking@65536 +5.1), but
    DS1@1M −2.4 and cp_w097 −1.2. DS1's first audit never runs, its 0–68% per-sample swings drive
    density-judged probes to a 40–62% window, and capping the short samples at 8 or 16 still costs
    DS1 −1.6 or −1.2.

Two things recur. The first samples after the half-full gate are cold: the hit rate rises with the
warming cache (ARC S1 climbs 1.1 → 6.9 over the first ten of its 15 samples) and the empty frequency
sketch makes the window look dense, so a verdict taken then is biased. And an early move persists
through parks, backoffs and the audit schedule (P8's jump crashes cheaply at 20%, yet a later audit
confirms larger windows on every seed). So the ungated rules help the traces whose right move is up
anyway and charge the ones whose right move is to stay; the fill-drop gate removes the first problem
and still fails on the second. Warm-up stays as it is; a rule that could pass needs an early move
that neither parks its landing nor shifts the later audits.

Neither main-space knob is worth adapting

The main space has two elements the climber never touches: the 80/20 protected/probation split
(PERCENT_MAIN_PROTECTED) and the 1-hit promotion rule (reorderProbation). Merlin adapts the
analogous quantities, so both were swept as a second dial: P in {0.20, 0.50, 0.65, 0.80, 0.90,
0.95} and a promotion gate requiring the sketch's estimate to reach T in {1, 2, 3, 4} — the
zero-metadata analogue of Merlin's guard_freq, needing no new per-entry field and no scan.
Object-count, the 288-cell Merlin matrix minus the two multi-GB traces, one build, the shipped
arm re-measured rather than inherited.

  • There is no better fixed constant, in either knob. Every alternative loses on the corpus
    mean: p90 −0.051, t2 −0.031, p65 −0.074, p50 −0.155, t3 −0.140, p95 −0.191, p20 −0.249,
    t4 −0.373. The shipped 0.80 / 1-hit is the optimum, not merely a defensible default. This is
    the opposite of Merlin's own region dials, where the ghost budget was worth +1.49 mean and the
    filter ratio +0.83 (merlin_region_sizing_headroom) — those were bad defaults; this is not.
  • The hindsight prize is inside the instrument. Best-of-9-arms per cell is +0.326 mean, but
    product.Caffeine is not bit-deterministic and max-of-9 is biased upward even when the arms are
    identical: re-running the shipped arm nine times on a representative 26-cell subsample buys
    +0.116 from noise alone. Net +0.21 pp for a perfect per-workload oracle. Any real controller
    captures a fraction of an oracle, and would be paying for it with a second extremum seeker.
  • The response is real but its argmax is not learnable. 16/26 cells show a P spread exceeding
    twice the same-arm noise range, so the surface is not noise. But the winner is scattered almost
    uniformly (p95 57, p90 54, base 50, p20 43, p50 38, p65 34 of 276), 46/46 traces change their
    preferred P across the size ladder
    , and the mean response is flat to ±0.1 pp at every size rung
    with the peak at or adjacent to 0.80. There is no stable per-workload optimum to seek.
  • The within-trace pass, which is the one that could have overturned this, closes the same way.
    A per-cell oracle prices only a per-workload controller, so each arm was re-run on three disjoint
    thirds (trace.skip/.limit, binding confirmed by segments that genuinely differ). On
    selected high-spread cells the per-segment prize looks large — +1.075 mean, up to +4.11 — and
    the per-segment noise floor is small (0.16–0.39), so those gains are real rather than noise. On
    an unbiased 14-cell sample it collapses to +0.225 gross, ≈0 net. The difference is cell
    selection, not workload regime: splitting by adaptations available in the segment does not
    separate it (<15: +0.184, ≥15: +0.256), so the tempting "it is climber non-convergence" reading
    is not supported and should not be repeated.
  • The downside dominates the prize by an order of magnitude, and it is a cliff. p95 costs
    −1.76 at the 5th percentile and −6.16 worst; t4 costs −25.72 on backf@300. A controller
    would be risking multiples of what a perfect one could win.
  • The promotion gate is inert where theory says it should be. t2 changes nothing at all
    (|Δ| < 0.01 pp) on 55/220 cells: an entry that won the TinyLFU admission contest already carries
    frequency ≥ 2, so the first threshold step is a no-op. The gate only bites by starting to deny
    promotions that should happen, which is the −25.72 tail.

Two properties of the structure are worth keeping in mind for anyone who reopens this. Because
increaseWindow takes its quota from mainProtectedMaximum and caps there, window + protected is
conserved and probation's capacity is fixed for the cache's lifetime at (1−P)·0.99·max
≈ 19.8%; the window's reachable range is [2%, ~80.2%] and it can never grow into probation. That
also means the low-P arms conflate probation size with window ceiling (p20 caps the window near
20.6%), so their losses are a lower bound on p20's true merit — but p90/p95 carry no such handicap
and fail on the mean anyway, so the verdict does not rest on it.
A constructed two-reference band between the window's reach and LRU's (s_probcapgeo) costs every
reachable split 32pp against LRU, and a larger probation at the same window recovers none of it
(the filter refuses once-seen candidates); only a window of 89–92% catches it.

Not covered: ConCat and MergeP (12 of 288 cells, ~9 GB of trace) were not run.

Companion result: the SLRU main space itself is still earning its keep against a plain LRU
main.
The 2015 commit that introduced it ("Use Segmented LRU for the main space cache") claimed
0–2% in many workloads, especially small caches, and the question is whether that survived the
sketch fixes since. Asked by setting the promotion threshold to 16: the sketch saturates at 15 and
reorderProbation is the only path into protected, so nothing is ever promoted, protected stays
permanently empty, and main degenerates to one LRU queue with the climber's window boundary
intact. (PERCENT_MAIN_PROTECTED = 0 is not the way to ask this — it zeroes
mainProtectedMaximum, which increaseWindow early-returns on, so it measures a crippled climber
rather than plain LRU.)

Over the same 276 cells, plain LRU main is worth −93.1 pp net, mean −0.337, median −0.227.
SLRU wins by >1 pp on 40 cells and >2 pp on 8; LRU wins by >1 pp on 9 and >2 pp on 3; 227 cells
tie within 1 pp. 38 of 46 traces prefer SLRU. Confirmed at N=3 on the 14 headline cells with
0 sign flips (σ ≤ 0.31 on all but P1@69344, whose N=1 +1.48 collapses to +0.07 — a noise
cell, not a result). The 2015 claim's size dependence also survives: the advantage concentrates on
the smaller rungs (rung 1 −0.596, rung 2 −0.601) and vanishes at rung 4 (−0.002). So: keep SLRU,
but it is a modest, concentrated effect
— it is inert on 82% of cells and worth 1–3 pp on the
rest. The traces that genuinely prefer a plain LRU main are a coherent set worth remembering:
fiu_ikki +0.851, fiu_webmail +0.548, backf +0.326, loop +0.305, MergeS +0.259,
fiu_homes +0.186, ps +0.136 (and corda_large +0.002, a tie). They are the same cells that
wanted a smaller protected region in the P sweep, so the preference is consistent across both
knobs — it just isn't worth a controller, per the headroom result above.

The promotion curve is non-monotone, and that is the interesting part. Mean vs the shipped
promote-on-first-hit: @2 −0.031, @3 −0.140, @4 −0.373, @6 −0.640, never −0.337. A partial gate is
worse than either extreme, and it owns the catastrophic tails (@4 −25.72 on backf@300, @6
−20.33) that never-promote does not have (worst −3.06). Mechanism, read from evictFromMain:
victimQueue starts at PROBATION and only moves to PROTECTED once probation is exhausted, so
under a high-but-finite gate the few entries that do reach protected are effectively removed from
the victim pool indefinitely — protected never approaches its maximum, so demoteFromMainProtected
rarely fires to release them. Never-promote avoids the pathology by keeping protected empty. Any
future proposal to gate promotion must answer this, not just pick a better threshold.

Reconciling failed-probe returns

undoRemaining accounts for issued integral commands; the cache applies them through budgeted
transfers of indivisible entries. A full sample can replace an unapplied remainder from each
stride, so a failed return can finish short and rebase the next probe. This can accumulate beyond
one entry: with maximum 10000 and nine entries weighing 1100, a public-operation witness returns
from 7800 toward base 100 with commanded decreases of 3000 + 3000 + 1700, but applies only
2200 + 2200 + 1100. The shortfall is 2200, two whole entries. A controlled unweighted witness with
8192 residents, a 128-CPU buffer configuration, a queued executor and buffered replacements loses
914 entries over two sample boundaries; its 16-CPU control returns exactly. These establish
reachability, not ordinary-runtime prevalence or hit-rate harm.

Two candidates were priced on 2026-09-13 against the real cache:

  • Bounded reconciliation retains the base and derives later strides from the actual window,
    while the original command ledger limits the number of return samples. Final carry remains
    best effort. It recovers the two lost 200-unit remainders in a 25-entry/400-weight witness
    within the same three samples, so recovery need not prolong retreat.
  • Acknowledged return retains the base and preceding window, ending on observed arrival or
    non-improving distance. Completion is checked before rate-reference updates to preserve the
    ordinary arrival sample's anchor tracking. Small positive progress can keep this return alive
    longer; the nine-entry witness needs a fourth sample to recover the whole 2200-unit shortfall.

Ten weighted cells from six traces (MetaCDN_rprn, metaStorage_block_traces_1, alibabaBlock_128,
tencentBlock_11784, cloudphysics_w50 and w99, 16 MiB to 1 GiB), paired at admission seeds 1–8,
gave mean object-hit-rate changes of -0.050 to +0.060pp across both candidates. Some small
effects repeat: Tencent at 64 MiB gains about 0.04pp; w50 loses 0.016/0.010pp. Neither candidate
earned adoption. After a fixed write-flood prefix exposing the unweighted shortfall, a switch to
two million unit-weight w99 requests costs the candidates 813/834 additional misses (about 0.04pp)
at all eight seeds. Exact return does not imply a better allocation after a workload change.

The acknowledged arm reached 14 return samples against a maximum of three in the baseline and
bounded arm. On metaStorage at 64 MiB, return duty rose from 3.30% to 7.26% of adaptation samples,
with no resolved hit-rate gain. These are sample fractions on each arm's evolving path, not CPU
time. Both arms still often stop short on weighted traces when the final remainder cannot move.
JOL estimates on JDK 26 put the added retained state at 8/16 bytes per density cache, with no extra
objects. A 32-fork uninstrumented JMH screen could not resolve its declared 5% threshold: baseline
spread exceeded it. The acknowledged weighted arm was slower in all four blocks (descriptive mean
-4.68%), an unresolved cost signal rather than a no-cost result. Three unit-weight trace projections
and all 87 gate and
bundled stress cells at eight seeds matched baseline's checked cache outputs and admission
digests; shared baseline bar failures remained. This does not price weighted behavior by itself.

Retain best-effort undo on the measured tradeoff. Reconsider with a repeatable quality benefit
that pays for additional state or retreat time; neither incomplete physical arrival nor a
percentage of displaced capacity establishes that benefit.

Entry-count tier selection for weighted caches

resized selects density above a maximum of 4096 in the weigher's units, however few entries the
cache holds, while AddTask sizes a weighted cache's sketch from its live entry count. Density's
period min(4·maximum, sketch.sampleSize) matches standard reactive's when the sketch cap binds;
byte units alone do not guarantee that. The comparison prices the whole controller, including
density's floor, probes, audits and anchor.

The 2026-09-13 comparison used 18 byte-weighted oracleGeneral cells (MetaCDN_rprn,
metaStorage_block_traces_1, tencentBlock_11784, alibabaBlock_128 and cloudphysics_w99, from
4 MiB to 16 GiB) replayed through a cache configured like product.Caffeine, with three unseeded
repeats per climber arm and static windows at 1/5/20/50/80% as the reference. Entry counts
are medians sampled every 65536 demands after the first half-full observation. Small differences
need seeded pairing before motivating a change. The fixed-window grid is a coarse reference,
not an upper bound on adaptive gains.

No consistent reactive advantage at low resident counts. On the seven cells with medians of 20–389,
reactive − density reads −0.21..+0.27pp with density ahead on four, and density trails the best
static window by at most 0.66pp (one cell reads above it), against up to 1.57pp on the cells holding
800 to 72k entries. Larger observed differences do not follow a simple count threshold: reactive +0.96
on MetaCDN_rprn @ 4 GiB (800 entries) and +0.45 on tencentBlock_11784 @ 256 MiB (11k), −1.45 on
alibabaBlock_128 @ 1 GiB (72k), where its window median falls to 12% against a best static 80%.

Retain the configured-weight threshold on this evidence. Grow-only selection from the first
request changed object hit rates by −0.21..+0.26pp across the 18 cells. On five cells whose counts
straddled 4096, delaying that check until half occupancy produced the same first switch. Following
the count both ways on those five cells switched 23–13,434 times per run and changed hit rates by
−1.71..+1.10pp. Each switch replaces the controller and resets its sample and carry; the experiment
did not isolate those effects. Reconsider with a reproducible benefit that includes switching costs.

Other pricings for the walk's two interior exits

The split's own dead arms, all measured N=8 seeded. Do not re-derive either.

  • A reversal bar with no base-rate term at all (revdev, min(5pp, k·dev)) is dead across its
    whole range, at both ends for different reasons. At k=3σ (noiseBand) it is bit-identical
    to floorrev on every seed of every cell measured
    — gain and loss alike — because wherever a
    floor at 3σ binds, max(fraction, 3·dev) is 3·dev, so the two derivations are one bar. At
    k=0.5σ it resolves arc_S3 (+0.04) and then costs whisper_mod_a12 −2.35 and
    crashnoise_a12 −1.77. Nothing in between serves: crashnoise needs ≥5pp at its scatter
    while arc_S3 needs ≤1pp at its own, and the required multiples are ordered the wrong way. The
    mechanism is general — a scale-free bar is necessarily small where the scatter is small, which
    is exactly where the shipped absolute is load-bearing. A base-rate term must survive on the
    reversal as a lower bound
    , which is what shipped.
  • floorrev / revfl300 (floor both the reversal and nothing else at 3·dev, under the 5pp
    cap): the right shape at the wrong multiple. It buys shieldtrap +0.49/+1.81/+1.40 and
    marggate's slowswap_r20 +1.81, and costs arc_S3 −0.45 — 38% of what
    AUDIT_BAR_FRACTION was introduced to buy, on the cell it was bought for — plus cp_w050 −0.27.
    At the shipped 0.45σ the same arm costs arc_S3 −0.01 and gives the slowswap prize back. The
    whole distance between those two outcomes is one multiple inside one family, not two mechanisms.
  • revfl100 (the same floor at 1σ): arc_S3 −0.42, i.e. the full floorrev loss. The cliff
    between the shipped 0.45σ and 1.0σ is why the level is not worth re-deriving.

Marginal steering on both sides

The round-2 kill pointed at ln(d_windowTail / d_probationTail), marginal on both sides,
keeping probation as the denominator while measuring its margin rather than its bulk. It was built
in two sizings (margboth, proportional δ·W against δ·P; margeq, equal capacity on each
side so the denominators cancel to ln(H_wTail / H_pTail)), and both are dead. But the result
that matters is not the arms.

Neither beats the one-sided form. Nine real cells at N=8: ship +0.00 / 0 losses / Spearman
+0.950 / 21.3pp level error; marghalf +0.83 / 1 / +0.983 / 11.3pp; margboth +0.66 / 3 /
+0.967 / 14.0pp; margeq +0.69 / 1 / +0.967 / 13.0pp. A second band on a second deque, for
strictly worse numbers. The two sizings land together, so proportional-versus-equal-capacity is
not the question.

The frontier. Averaging the two slowswap rows against that screen, the four denominators
trace one monotone inverse curve: marghalf 30.40 / +0.83, margeq 31.74 / +0.69, margboth
34.92 / +0.66, margtail 36.92 / +0.43, ship 41.32 / +0.00. Spearman −1.0, slope ≈16pp of
slowswap per 1pp of corpus
. A denominator does not escape the trade, it only picks a point on
it.

Gain is not the lever, and the family has a ceiling. marghalf swept over gain 0.125–1.0 on
slowswap_r20 reads 32.90 / 30.38 / 30.02 / 29.65 / 36.20±5.23 / 31.12: non-monotone, unordered,
its one high point a lottery. And margtail, the family's best slowswap point, reaches only
37.52±0.18 at its best gain. Across the ~14 (denominator, gain) points measured on that row the
best mean is 37.52 and the best single seed anywhere is 38.67, against a ≥40 bar and
ship's 42.19.

So the thread does not close because arms failed; it closes because the family has a measured
limit short of the gate over both of its free parameters
. What remains untried is outside it:
every remedy so far picks a different denominator, while the slowswap diagnosis names probation's
composition under heavy inflow. A regime-gated fallback is a different shape from round 1's
rejected starvation-gated one, but price it against ~+0.24pp, the corpus prize the frontier leaves
at the bar, not against +1.22.

Marginal steering: the signal is real, the controller is not shippable

The gated arm is the family's best point and its residue redirected the thread. marggate
(fall back to the average law where probationDensity exceeds mainDensity, a trustworthiness
test on two terms Reading already computes) reads +0.690 mean over the nine cells with one
−0.04 loss
, rest-point tracking Spearman +1.000 with 12.2pp level error against ship's
21.3, and sits at the same slowswap robustness as dead margtail for 60% more prize. Its
structural false positive: on flood_j100 a rotation exceeding protected demotes every promoted
key before reuse, probation reads richer than main with no inflow at all (47 of 48 steering
samples), and the arm is the average law there. It fixes slowswap_r1 (5.6pp below LRU →
38.24 ± 0.18, +1.45 above, deterministic) and not r20 (−6.9 → −0.95, bimodal); neither meets
the ≥40 bar. The residue is recovery, not detection: per-seed on r20 the gate fires 11% of
steering samples in the good basin and 9% in the bad one, firing rate does not predict the
outcome, and what separates the basins is where the window ends up (30.5% against 20.0%) — the
depressed-window thread's territory. Dead within the gate, do not re-derive:
a run-to-enter requirement (streaks 2 and 3 both lose to 1), an exit latch (non-monotone, never
lifts r20 above LRU by more than 0.00 over 1/2/4/8/16), and the gate ratio (the two rows want
opposite settings: 1.0 gives r1 +1.45 / r20 −0.95, 0.6 gives r1 −0.62 / r20 +1.01).

margrest (the marginal law only inside a rest band, |steeringError| <= REST_BAND) solves
slowswap_r20 outright (42.20 ± 0.05 against ship's 42.19, deterministic) and destroys the
prize
(nine cells +0.133 against marghalf's +0.833, w098 collapsing +4.33 → +0.35). The
mechanism is the wall the record now names everywhere: w098's win is a rest-point
correction, but a large one — the valuable corrections are long relocations, and a long
relocation is indistinguishable from a transition by command magnitude. No size-gated rule can
work, and the same fact underlies the frontier: the signal cannot separate "a big move because
the rest point is wrong" from "a big move because the workload changed". Two structural facts
fell out: the average-law error is bimodal (near zero at rest or large in transit; bands 0.5
through 0.1 are bit-identical), and on the bad r20 seeds the final third is a park, 95 of 103
samples at a 16.9% window with one audit armed — the marginal law digs a hole, the anchor parks
in it, and nothing in a steering law climbs out of one. The stillness-coupling blocker is
repaired entirely by halving the gain (a threshold on the stable band, not a monotone trade;
0.25 is worse than 0.5); the probation-denominator defect under heavy inflow is what the
denominators above could not fix.

External corroboration (OSDI'26 "Learning-Augmented Heuristics", Xia et al.): their model's
feature importance puts 75% on the three queues' hit-position histograms — per-region
hit-rate-versus-size curves, i.e. marginal-value estimators — over 4140 production traces.
Corpus-scale evidence for the premise from outside the project. d_tail is one bin against
their 20 per region, so bin resolution is the knob if a future arm reads ambiguous; nothing
else in the paper transfers (it is a static per-trace configuration selector). Screen any
successor on rest-point tracking before spending a battery
: Spearman(meanWindow, peakWindow)
over the nine D3 cells reads ship +0.917, marghalf +0.983, margtail +0.367, and a candidate
that does not beat +0.917 is buying stability with tracking — trap wins paid for by corpus
losses.

The thread was closed against a corpus prize under a point. The LLM prefix-cache cells (§3, terrain
classes) are real traces where the average law's rest error is most of a 3–7pp loss: on the static
anchor it is bistable on Thinking@16384 and rests at the floor or near 40% on Coder@65536, where the
marginal form rests near 71% and at the ceiling against 80% peaks. A re-opened thread prices
against /climber-gate's prefix-cache row as well as slowswap.

Verdict corroboration, and the delayed entry stride

Verdict corroboration, the smoothed reference (corrob2; the raw form died first). A
pulled-back confirm must reproduce its own claim (Walk.bestRate) in the smoothed rate within a
confirm-streak of samples, or the park relocates to the walk's end. It does what the raw form
did — crashnoise_a12's low basins lifted (+1.03 mean, the row near-deterministic at 62.7) and
whisper_mod_p12 +0.64 on 8 of 8 — and fails whisper_mod_a12 the same way (−1.67 mean, seven
seeds at −1.98, seed 7's low draw the one gain): at that amplitude the EMA still carries ~40% of
the modulation, the park's first samples land in the trough, and a correct position fails its
own claim. The reference is not the defect; the depth is. The smoothed form also adds
relocation damage the raw one did not have: demoflood −3.64 on 8 of 8 and crestpast
−4.2 / −2.9, where the lagging EMA reads a fresh crest park as unproven and revokes it to the
walk's end, re-deriving the exact overshoot the verdict repair removed, plus straywall2
−0.78. Dead. Both measurable references die by the same symmetry, and the third (the fallback
position's own rate) is unmeasurable without going there; the corroboration family is closed.

The delayed entry stride (delayentry, item 8's blank). Freeze the base at the arm and
take the entry stride on the next sample, the WaveCounter comparison. The prior held and the
mechanism sharpened: the entry's timing is itself phase-load-bearing. absolve reads −15.7
on both seeds
, back at its pre-repair value, because the lure-paced escalation needs the walk
to arm in the off phase and stride before the on phase returns; straywall2 −0.88;
norank_rep_r6's escape lottery reshuffles violently (seed 3 +48.6 against seed 7 −59.8, mean
−2.2); against blindlock_blind +5.8 and demoflood +0.7, which do not buy that. Dead; the
record's blank is priced in the direction the prior expected.

The marginal band priced against main's average

ln(d_tail/d_main), the marginal band priced against main's average instead of its probation
margin, was the named answer to the one marginal-steering residue that halving the gain does not
touch. It is dead in both directions, and the reason was derivable from §4 before it was built.

It halves the prize. On the nine D3 cells margtail is +0.43 mean with four losses
(P9 −0.41, P10 −0.55, w081 −0.64, w060 −0.75) against the full form at half gain marghalf,
+0.84 with one.

It fails the gate it existed to pass. Seeded at N=8, slowswap_r1 reads 36.31±0.92 and
slowswap_r20 37.52±0.59 against a ≥40 bar and ship's 40.44 / 42.19. It recovers ~63% of
marghalf's slowswap residue, not the ~89% the static rest-point anatomy implied (1.69 against
14.63pp), and still lands 4.1–4.7pp low. Two side results: the declared trade was wrong, since
trickle_s7 improves (72.59±1.30 against ship's 70.70±3.63); and widepin is the arm's one
real win, 56.32±7.00 against ship 49.58±13.73 and marghalf 48.12±14.03.

The mechanism, which is the part worth keeping. A steering denominator must move with the
window or the rest point stops depending on the window. d_prob moves (a grow squeezes main and
demotes protected entries into probation); d_main is dominated by the protected core and barely
moves, so the error degenerates toward ln(d_tail) − ln(const). Measured directly as rest-point
tracking across the nine cells, Spearman(meanWindow, peakWindow) is ship +0.917, marghalf
+0.983, margtail +0.367, with mean |window − peak| 23.0 / 14.9 / 19.2pp: margtail's
middling level error is range compression, not accuracy, and it rests at 12.6% and 13.0% on the two
cells whose optima are 40% and 70%. That is the same fact §4 records for the sibling mechanism, an
up-probe's verdict being priced against probation "rather than its average", and widepin is it
read from the other side, since an arm that does not track cannot chase a pin. An arm that wins
on the traps and loses on the corpus is buying stability with tracking; screen the tracking
statistic before spending a battery.

Repairing the reactive law instead of replacing it

Can the reactive law be repaired instead of replaced — specifically by giving its reversal a
noise band, as nextStride gives the walk's? Four forms were built and measured against both
incumbents: a flat 5pp band, the walk's priced band (clamp(3·dev, 5pp, 15pp)), the same with
the density tier's 2% floor, and probeEnding's base reference (measure from the run's best
sample, ratchet on improvement) with and without aging. All dead, and the reasons are structural
rather than tuning.

  • A band on the reactive reversal, any form. Real traces: mean +0.16pp vs reactive over
    seven cells, range +4.06 (cp_w061@64k) to −2.81 (arc P8@64k), never closing the gap to the
    shipped machine (mean −0.73). Constructed families: five of six lose, mixture d050 long
    −25.89, mixture d025@32768 −25.29, straywall2 −12.24, whisper −10.53, demoflood −3.37. A band
    buys commitment, and commitment without an adjudicator is a direction chosen by early noise
    that nothing can revoke — the same wall v7's travel budget and v9's slam-continue hit from the
    probe side. The reversal is the reactive law's "confused but never persistently wrong"
    property; a band spends exactly that.
  • A band with no window floor is catastrophic, and this is a standing constraint on the tier.
    BoundedLocalCache.decreaseWindow floors the window at one entry; the 2% floor is climber-side
    in Reading.floor and belongs to the density tier alone. The reactive tier has never needed
    one because a bold driver cannot sustain a run. Widen the band and the window walks to ~0,
    where TinyLFU refuses every new entry against an established victim: corda 30.96 → 1.13.
    Any future change that lengthens a reactive run — a band, a longer period, a confidence gate —
    must bring a floor with it.
  • Aging the base reference (decay the peak toward the live rate at 0.2/sample): inert on four
    cells, −0.5 on two, and on arc P8 it collapses to the un-ratcheted band's value exactly
    (42.57 vs 46.90). Third independent kill of reference-aging, after the anchor claim's two.

Two results from that study are not kills: the magnitude deficit is
partly a reversal-rule problem rather than purely a signal one, and the trap gallery's value is
partly variance reduction that N=1 comparisons hide.

Guards derived from the control-theory map

The control-theory map (§2.1) suggested two guards that could be derived rather than tuned.
Both were built behind variant knobs and both are dead. The holdout frozen for the study was
never spent — neither arm reached a ship gate.

  • Freezing the walk's deviation reference at arm (devfreeze): walkInteriorBar prices a
    starvation probe's crash abort at 3×rateDeviationEma, and because updateRateReferences runs
    before the probing branch, the walk's own transient inflates the very statistic that decides
    whether to abort it. That description is accurate; the inference that it is a defect is not.
    The feedback is load-bearing: on a dosed workload the deviation at arm is a pre-dose
    value, so a frozen bar sits too low and the walk crash-aborts inside the dose — exactly the
    failure the adv3 probe-side pricing exists to fix. Measured: mixnoise_a10 60.70 → 55.97
    (−4.73, spread 0.01, fails its ≥58 bar — the pricing's own prize row) and crashnoise_a12
    63.59 → 62.18 (−1.41, spread 0.01, drifts below its 63.6 floor); devfreeze_nocap is worse
    (widepin −2.48, phases_d050 −2.75). The live deviation and PROBE_BAR_CAP are a matched
    pair — the feedback keeps the bar above the weather while the walk is exposed to it, the cap
    bounds where that would run away. Don't freeze it, and don't drop the cap.
  • A within-sample confidence dead-band (deadband<z>): suppress a steering step whose error
    is inside z·√(1/H_w + 1/H_m) (the delta method on two counts — free from counters in hand),
    as a derived answer to F-2 and, since a step never taken cannot move the window, a derived
    stillness band. Dead twice over. It barely binds: at a 4×C sample period each region earns
    thousands of hits, so 3·sd is ≈0.04 nats and the step that commands is ≈0.13% of the maximum —
    measured mean +0.03 across 40 non-bimodal cells, two cells past ±0.2. And its motivating
    case is not a significance problem
    : on posjam_d0, 0 of 121 super-band moves have an
    error inside its confidence interval — the jam's motion is statistically real and window-
    irrelevant, so no confidence test can act as a stillness measure (the dose-matched flat
    control is the mirror image, 5 of its 11 super-band moves inside the CI — it binds exactly
    where a defense is not needed). The posjam residual stays a stillness-measure problem, not
    a noise problem.

Ageing an off-anchor claim instead of freezing it

Both forms of the thread's other named fix direction — letting an off-anchor claim age toward
the live rate instead of freezing — fix the target and are dead for different reasons. The landed
fix re-seeds the goal metric on a discarding stand-down instead.

  • Symmetric aging (ageclaim: anchor.rate decays toward rates.smoothed while the
    window is away and settled): −11.9 on the slowswap_r20 ramp control, on every one of eight
    seeds (42.14 → 30.29), while fixing slowswap_r1 exactly as the landed fix does. A claim that
    chases the live rate upward can never fall a vetoMargin below it, and that comparison is the
    only thing that moves the anchor to a better position: the anchor freezes at a position the
    window has left for the rest of the run (trajectory: park samples 69 → 1, AUDITCONFIRM 1 → 0,
    down-probes 7 → 0, steering 128 → 238). Slowing the decay to 0.05 hides it on that trace without
    changing the mechanism.
  • One-sided aging (agedown: decay only while the live rate sits under the claim): passes
    both slowswap arms, battery mean −0.067 with no seeded mover, and still dead — it disarms the
    guard rail
    . The rail needs VETO_STREAK consecutive rail evaluations of
    smoothed < rate − vetoMargin, and aging closes that gap at the same rate rateDeviationEma
    decays, so the shortfall and the margin shrink together and the streak never completes; both
    rail pins fail
    (guardRail_sustainedShortfall_vetoesBackToAnchor,
    guardRail_marginTracksNoise_silentWhileWideThenVetoes). The rail is measured near-dead on this
    battery, so the live cost is small — but a compensating fix that trades a documented mechanism is
    the wrong shape when the same defect has a source-level fix that leaves every pin standing.

Park persistence, and other confirm-side levers

  • Unconditional park persistence (a parked confirm never crash-releases): captures the full
    w097/whisper-p6 prizes but converts the tracking controller into hold-and-retest exactly
    where tracking is the cure — regimeramp 54.8 → 48.5, widepin 54.8 → 48.5 (below its bar),
    phases@8192 −4.1. The bounded fresh-park shield (one initial audit wait) keeps the prizes
    with all three at ship values — the shield length is load-bearing, not a tuning residue.
  • Deviation-priced audit margins generally (the F1 mitigation table: flat/ema/cap
    margin modes recover only 25–35% and trade the rail for the audit) — superseded by the
    run-length streak; kept here so nobody re-prices the confirm by any multiple of
    rateDeviationEma again.

Blending the two steering laws

  • Confidence-gated steering blend ((1−w)·density + w·reactive, w = W·min(1, |ΔHR|/devEMA)):
    the T1a form. Whisper −1.1, jam-control −3.4, and zero rescue on its own targets — the
    density attractor undoes uncommitted per-sample nudges exactly as §T1's coin-flip analysis
    predicted; a confidence gate does not change the physics. Committed walks (probes, audits) are
    the only instruments that cross basins.
  • Settle-then-judge (hold a walk's final position ≤5 samples, adjudicate by live-margin rate):
    demoflood +6.6 but phases −8.4/−11.5 dose-responsive — an exogenous phase rise credits the
    held position (misconfirm) and plants a wrong anchor. Any goal-metric confirm must survive the
    phase-alternation families before shipping.
  • Goal-metric senior confirm with margins frozen at arm: no effect — the margin freezes an
    inflated deviation and the anchor reference is stale by construction on noisy traces (the 3·dev
    ratchet bar never fires at dev 0.03–0.07; measured 130-sample-stale on cp_w097_16384).
  • Dwell-gated anchor ratchet (auditClock.stillSamples ≥ 2 before the anchor may move): no
    co-tenant help; widepin re-destabilized.

Audit attribution: headroom without a deployable rule

These findings constrain future audit and retention changes. Reusable demand inputs and commands
are in /audit-regret's targeted controls.
Experiment reports, raw runs and controller-specific replay instrumentation are transient;
the design conclusions and controls below are the lasting record.

Paired replay at 0494074819b08f8c6076f40a544c882beeeac2d0 found material avoidable
misses, but neither tested decision-time discriminator justified a production candidate. The
selected real, trap and lifecycle panel used the real cache, fixed-probation geometry, admission
seeds 1–8 and request/contest-indexed randomness; it does not establish corpus-wide saturation.
For the comparisons below, C is capacity and the horizons are demand requests after the decision.

  • Retention can repay an initial loss. On jumpslide at C=8192 from the shipped start,
    routing the first unparked starvation confirmation through the existing park path costs
    4,176 additional misses over 16C (−3.19pp), then saves 93,460 over 128C (+8.91pp) and
    193,000 over 256C (+9.20pp). Each sign holds on all eight seeds. These are paired means for
    one intervention, not a rule for recognizing a persistent reuse band. The cp_w050 case in
    §3 likewise establishes a feasible gain without establishing a false rejection.
  • A pre-existing decline does not justify tolerating the first audit crash. The screen
    snapshots whether the arm sample already fell by at least 5pp, then grants the existing
    bounded three-sample retry at the first crash. At 128C it selects cp_w050's +0.99pp and
    also selects −0.73pp after whole-cache invalidation and −1.71pp after hot-key invalidation
    on the 55% mainsat plant, each on eight of eight seeds. Weather preceding the walk is
    evidence about cause, not sufficient evidence for continuing it.
  • A raw shortfall does not establish an obsolete return claim. Rejecting a passing retest
    when its raw rate is below the frozen claim by 1pp and has just fallen by at least 5pp
    costs cp_w081 0.23pp and crashnoise_a12 7.73pp at 128C, on all eight seeds. Both screens
    were specified from baseline observations before their paired outcomes were disclosed;
    neither was repaired by adding predicates to exclude its losing controls.
  • Some hindsight verdicts require unseen future information. jumpslide from a 60% plant,
    with only its rotation period changed from 13 to 26 nominal samples, has 425,984 identical
    initial requests. Every first audit crash occurs at request 262,152, with identical cache,
    controller and admission history in the two futures. Tolerance at 256C averages −0.65pp
    for period 13 and +5.61pp for period 26; five seeds require opposite actions. The latter
    wins only 5/8 seeds, below the study's 7/8 consistency requirement. No history-only rule
    can distinguish those futures at that decision. This does not rule out acting later, once
    the unchanged cache has received distinguishing evidence.
  • Lifecycle misses can be unavoidable. At C=8192, empty caches with fixed write TTL covering
    C requests on a request-based clock and loading only on demand misses matched an independent
    TTL-only reference on mainsat and a 512-key hot-set/scan control. The reference's live entries
    fit in C, and neither cache performed a size eviction.
    Resizing cannot reduce those misses. This is a scoped lower bound, not an inference from
    underfill or an expiry counter, and it does not price transfer CPU or cover other lifecycle rules.

Keeping an allocation fixed for the whole suffix also suppresses later audits, so its gain
cannot be charged solely to the first excursion. A short-horizon gain can reverse through later
scheduling: first-crash tolerance on cp_w100 reads +1.16pp at 16C and −1.88pp at 256C.
No production candidate or full adoption-gate pass followed from this study.

Later retention: useful evidence before the benefit is lost

The focused follow-up at the same commit used independent unchanged-baseline prefixes at
1, 2, 4, 8 and 16 nominal samples after the first unparked starvation confirmation. A nominal
sample is 4C requests. Each action uses the first eligible ordinary density-steering decision
in its registered one-sample window, after higher-priority routes; no earlier alternative park
supplies the later state or evidence. Endpoints remain fixed to the original episode.

  • Waiting need not consume all the benefit. At the common 128C endpoint on jumpslide,
    current-position parking at delay 8 saves 67,242 misses (+6.41pp, 7/8 seeds), about 72% of
    early parking's benefit. Delay 4 saves more than the early action, so remaining benefit is
    not monotone. At delay 16, parking the current allocation saves only 0.36pp, while returning
    through the real capped-transfer path to the earlier confirmed allocation still saves 6.46pp.
  • A later signal can distinguish some harmful parks. The two initially tested
    earliest-trigger rules failed controls. A single timing hypothesis was then frozen from
    discovery: evaluate persistent regional support only at delay 8. The signal, S1, required both
    current and preceding baseline samples to stay within 0.02C of the confirmed window, with
    window hits per sampled request no more than 1pp below the confirmation's contribution and raw
    hit rate no more than 5pp below its raw rate. Only the current sample's ordinary integer density
    command had to shrink. Confirmation used jumpslide workload seeds 11/13 and admission
    seeds 9–16, held out until that rule was frozen. All ten selected actions saved misses at 64C,
    128C and 256C. The complete seed-13 cohort has seven gains and one abstention: +7.46pp at 128C,
    retaining 93% of early benefit.
    Seed 11 has three triggers, four abstentions and one missing eligible window; it is not a
    complete eight-seed pass. The signal rejects seed-11 actions that would lose 2.39 and 1.26pp.
  • Missing eligibility is not discrimination. Phase durations 20C/24C supplied no eligible
    delay-8 windows. A separately registered 18C/22C extension supplied 13, all rejected by the
    frozen signal; ten forced parks lose misses at 128C, eight by more than 0.5pp. Those individual
    paired losses are valid controls even though missing seeds prevent an eight-seed group gate.
    Across the 48 fresh registered cases, only 28 had eligible observed decisions: ten selected
    gains, thirteen avoided losses, three missed gains and two neutral abstentions. The signal
    recognizes a useful subset, not every retention opportunity.

This establishes a useful tested later point, not a continuous safe interval, a general timing
optimum or a production improvement. The 104-baseline, 403-pair study verifies feasible loss
avoidance and baseline-only information; repeated candidate behavior and the existing adoption
gates must be measured separately. The real cp_w050 control has no qualifying episode on
any seed, so it supplies no retention-effect or discrimination claim.

The resulting isolated candidate kept the first pending confirmation's independent window,
raw-rate and window-contribution references. Later confirmations did not replace them or postpone
the eighth-completed-sample deadline. Assessment required final ordinary steering, no incoming
transfer adjustment and no pending retest. Priority routes consumed the deadline without a retry;
shifts, walks, parks and releases did not cancel it. resetSample canceled it, and an actual
maximum change replaced the tier. A true S1 planted the current window/current smoothed rate,
used the existing park(32) shift shield, and preserved the previous step rather than committing
the unexecuted integer shrink. The episode then expired and a later confirmation could rearm.
It preserved the existing router, audit, release and retest machinery. First-episode validation
matched the diagnostic across 104 cases: 67 eligible decisions, 24 ineligible expirations and
13 absent episodes. In the canonical seeded-stream battery, 92 of 94 cells had identical counters
and trajectories. jumpslide gained 2.63pp (five gains, three ties), and whisper_quarter moved by
less than 0.005pp per seed. These are separate admission
schemes and cohorts; their deltas are not pooled. Three historical bars (deadphase,
sidecliff's noaudit control and whisper at 16384) failed identically on the study baseline
and candidate. The paired comparison found no new behavioral regression; the literal historical
bar results remain separately qualified.

Longer continuation still matters. On the previously held-out jumpslide workload seed 13,
admission seed 14 parks once, saves 25,085 misses at the original 128C endpoint and 8,926 at 256C, then
ends the 260-sample trace 234,178 misses behind (−2.75pp over the whole trace). Its complete
hit sequence matches the earlier one-action replay, so repeated rearming did not cause that
reversal. All 120 registered TTL/invalidation cases were unchanged, but the matched no-expiry
period-26 jumpslide control from a 60% plant lost 35,363 misses on one seed (−0.83pp; the
other seven tie). Its initially beneficial park changed later audit/release behavior and ended
at a ceiling park. A retention action includes those later consequences, not just the time it
keeps the original allocation.

Keep all known full-continuation costs visible, including losses allowed by the group screen:

Workload / admission seed Retention parks Whole-trace extra misses
jumpslide, workload seed 13 / admission 14, 260 samples 1 234,178
jumpslide, workload seed 13 / admission 10, 260 samples 1 64,698
jumpslide, workload seed 11 / admission 14, 260 samples 1 104,524
jumpslide, workload seed 11 / admission 15, 260 samples 2 13,505
period 26 jumpslide, 60% plant / admission 1, no expiry 1 35,363

These are request/contest-PRF, direct-executor results, not canonical seeded-stream or ordinary
ThreadLocalRandom expectations. Both seed 13 losses match their earlier single-action continuations.
The period 26 control's seven peers tie, for a group mean of −0.103769pp. The registered rejection
requires at least 0.5pp mean loss, C/4 mean extra misses and 7/8 negative seeds; none of these groups
meets that rule. That does not erase individual costs or create an any-negative-seed rejection rule.

The candidate was not adopted. On the tested Temurin 25/macOS arm64 JVM, the Java
11-compatible implementation added 32 retained bytes per density-tier cache and no additional
retained object. Direct density-tier JMH measured about 5ns more per ordinary sample and 3ns per
sample over the controlled retention cycle. Whole-cache throughput remained inconclusive: baseline
fork variation and wide paired intervals did not resolve small costs.
Neither those intervals nor a cold-path location establish zero cost or an unacceptable cost.
This is a demonstrated later opportunity with unresolved adoption gates, not saturation or a
production improvement.

The bounded runtime follow-up also ended inconclusively. Its 32 source-identical baseline forks
(four pairs in each of four whole-cache cases) resolved neither the registered ±2% sensitivity
target nor a directional build effect. They were not candidate regression measurements. The
archived 2% sensitivity floor, 5% noise ceiling and 0.01 B/op flag floor are diagnostic rules;
no numeric engineering acceptance budget was found. That budget and a qualifying measurement
setup are prerequisites to new candidate classification. No additional candidate timing or
reserved-holdout outcome checks followed from this unresolved result.

A later integration-only follow-up passed 1,499 unit/integration invocations and one
five-minute fuzz campaign (2,228,167 executions), with no candidate defect found; specific retention
branch exposure in fuzzing was unmeasured. One of two ordinary-admission/direct-executor observer
runs naturally armed, parked at sample 72, audited at 99 and released at 109. Neither of two default-
executor observer runs parked; clean counterpart action counts were unmeasured. Default mode had
one demand producer and background common-pool maintenance, not concurrent demand producers.
Source stripping and controlled observation checks do not establish exact concurrent histories
or CPU neutrality. A same-maximum policy call still runs maintenance and may age a completed
sample; tier preservation does not mean all controller state is unchanged. Likewise, quiet
completion and lifecycle operations add no cancellation rule but may process prior observations.

The 17 real/floor cells and their registered quarter, repeated and planted controls made 42
paired cells at eight admission seeds each. All counters and trajectories matched, and the
retention action never fired. The existing recovery bars held, but dormant behavior supplies
neither a real-corpus gain nor evidence that an active retention decision is safe there.

S1 was closed without adoption on 2026-09-08. Its source/test snapshot was discarded; the
decision-time findings, known losses and reusable workload controls remain the lasting result.
Runtime acceptance and final reserved checks were not established: ARC P3 was unspent and the
referenced hash-pinned merge specification was not located. Closing the candidate does not turn
those unknowns into a performance rejection or establish that no useful improvement remains.
A future investigation can recreate an experiment against the current source using the targeted
controls and §6's decision-replay protocol, with its own source binding and validation.

6. Methodology (the discipline that kept this honest)

  • product.Caffeine in the simulator is the arbiter — the sim reference climbers diverge from BLC
    (ps2-vs-x2 history). Randomized admission gives ~0.1–0.8pp run-to-run noise: N≥5 runs for any
    claim under 2.5pp, absolute pp only, no net-sum headlines over noise cells.
  • Where that noise comes from, and why some cells are bimodal. The source is
    BoundedLocalCache.admit: a candidate at or above ADMIT_HASHDOS_THRESHOLD that does not beat
    the victim is admitted on a 1-in-128 ThreadLocalRandom draw (the HashDoS defense). TLR seeds
    from system entropy per JVM, and the policy runs .executor(Runnable::run), so this is the only
    live nondeterminism. The experiment harness can replace that contest-time draw with a seeded
    stream, which makes each arm independently reproducible. Equal seeds do not make two arms use a
    draw on the same request when their contest schedules differ. The arms are paired by seed, not
    by request-indexed common random numbers, unless both the draw count and request-index digest
    match. On most cells the draws average out
    (measured over five batteries: 28 of 47 cells vary by <0.05pp run-to-run, 37 by <0.20pp). On a
    constructed bistable trap an early draw can tip the region composition into the other attractor
    and the climber's own feedback locks it in, which is what a lottery cell is: 6 cells
    (phases_d050, widepin, shieldtrap_s7/s13, balloonflip, crashnoise_a12) vary by
    1.1–5.5pp with the code held fixed. Consequences for reading a gate: a mover inside that set is
    evidence of nothing, interleaving arms limits temporal drift but does not make their draws
    common, and a single-cell regression is adjudicated by basin count at N≥10, never by a mean at
    N=2.
  • Adaptive N is sound and ~60% cheaper: N=2 everywhere, N=5 where |Δ| ∈ 0.5–2.5pp or spread >0.5.
  • Lengthen a trace with fresh keys, not a plain replay. Replaying a trace back to back lets the
    frequency sketch remember every key from the previous pass, which flatters small windows and can
    trap the climber there: on the LLM prefix cells a static 1% window gains 4.8pp on To-C@185364 from
    that memory alone, and under plain replay the shipped climber stays small where with each pass's
    keys mapped onto new ones it reaches LRU or the static 80% window within two to four passes.
    Check a multi-pass claim against a fixed policy over one and over N passes before trusting it.
  • Pair usable-prefix scores with the legacy block scores. A request's usable hits stop at its
    first missing block (§3, terrain classes). Preserve exact prefix-chain keys and real request
    boundaries, and score both measures on the same paired replay. LRU's equality in the studied
    setup must be checked, not assumed for every workload or serving model. Do not silently stop
    the access stream at the first miss, add request pinning, or replace historical gate bars;
    those are separate replay or acceptance changes.
  • Test fill transitions rather than naming them from a drop. A hit-rate drop near first
    eviction can motivate a fill-transition hypothesis, but admission, sketch initialization/aging,
    capacity pressure and workload phase can change together. Isolate those effects before
    attribution; a filling cache is not unconditionally equivalent to a large LRU window.
  • Trajectories (window size per adaptation) are the diagnostic instrument — every fix in §4 was
    found by reading a trajectory, not a scoreboard. The debug hook pattern: stderr print in
    determineAdjustment behind a temporary system property (the simulator forwards caffeine.*);
    no hook is checked in — the 2026-07 study's working-tree harness (variant knob + debug + seeded
    admission RNG) is preserved in the
    local hill-climber-study workspace (experiment-harness.patch).
  • Separate information, actuation, and deployability. First ask whether a signal predicts reuse
    or ranks counterfactual window sizes on a declared observation stream. Use past-only features,
    complete negative labels, cold-start accounting, authentic frequency and equally deep no-feature
    controls, and bounded state/work. Check which observations survive generic maintenance; metadata
    recovered offline does not make it available there. Reuse prediction alone establishes neither
    when to trigger a different mode nor which window direction helps. Then ask whether a controller
    moves the live cache safely and converges under clamps, reversals, and phase changes. Only then
    price the actual production host for retained state, steady allocation, command/boundary
    allocation, and CPU. Passing one layer says nothing about the next: replicated MiniSim passed its
    information screen and failed cost, while Compact Shadow passed layout and stopped before quality.
  • Distinguish issued commands from applied movement. An experiment requiring exact arrival
    must keep its decision pending until the host acknowledges the actual signed setpoint change.
    Reject overlapping acknowledgements and the wrong sign or magnitude; retain geometry-specific
    evidence after a rail-clamped zero; clear it only after a validated nonzero move. Continue a
    capped trip from the acknowledged setpoint, not from a nominal percentage or transient queue
    occupancy. Tests must pin partial and zero clamps, reversal, cancellation, and exact arrival in
    both directions. The shipped undo retains
    best-effort arrival; ledger closure alone does not prove arrival.
  • Use an oracle ladder, not a self-confirming digest. Compare a compact estimator first with an
    independent readable model, then request by request with the actual simulator policy under the
    same key, frequency history, and admission variate. Check hits/misses, membership, queue order,
    promotions/demotions, and admission decisions, not only final hit rate. Accumulate live evidence
    independently; copying an expected digest into the measured path proves only that the metadata
    agrees with itself.
  • Low duty cycle is not low request-path cost. A dormant callback, counter decrement, routing
    indirection, or branch still executes on every request. The compact studies measured zero steady
    allocation alongside material CPU regressions. Dormant observation must be structurally absent,
    and even that is only a hypothesis until the exact product splice passes an order-balanced
    benchmark. Warm the exact root, actor, probe, and branch shape being measured, and make estimator
    work externally observable so dead-code elimination cannot manufacture a win.
  • Establish benchmark repeatability before interpreting small costs. Use ordered baseline
    pairs before candidate pairs, preserve slow forks and report uncertainty. A stable slow fork is
    not necessarily an unfinished warmup. If baseline variation exceeds the registered sensitivity,
    additional measurements may diagnose it but cannot silently turn it into a permissive pass bar.
    Separate cold ns/sample from whole-cache throughput and fixture allocation from new allocation
    in an existing cache; state the sample exposure used for any per-request amortization.
    Bind the entire cache implementation as well as the controller overlay: class origin proves
    location, not that a supplied JAR contains the pinned baseline. Pin the resolved JDK and keep
    different runtime cohorts separate even when their benchmark names and launcher strings match.
    Check activation outside timing; an initialized sketch can process density samples in an
    underfilled cache, while a six-reader/two-writer JMH group does not fix the request ratio.
  • One clock owns each lifecycle boundary. Host requests, sampled requests, warmup, measure,
    travel, and cooldown are different quantities. Express each transition as one absolute request
    deadline or one remaining count, not two counters that can both charge the same interval. Pin the
    request immediately before and at every edge, and prove that under-full or dormant requests do
    not advance the estimator's active clock.
  • Classify endpoint state before scoring regret. A trace that ends in a walk or audit measures
    recovery latency over that horizon; it does not prove a settled rest point. Report the terminal
    phase and pending target beside static-oracle regret. For percentile gates, also pre-register the
    monotone futility bound: stop once the observed order statistics force failure under every
    possible value of the unrun cells.
  • A holdout must be able to exercise the thing under test. AUDIT_BAR_FRACTION is
    min(RESTART_THRESHOLD, AUDIT_BAR_FRACTION · baseHitRate), so above a 1/3 hit rate it equals the
    absolute bar and every arm ties by construction. Selecting by "unspent + density tier + enough
    adaptations" is not sufficient: screen the candidate band against the constant's own binding
    condition first, or the freeze spends cells on a null result that reads like a pass.
  • Holdout discipline: freeze a fresh holdout BEFORE tuning, sizes by LRU-only characterization,
    score it exactly once at the ship gate — and screen the candidate cells against the constant's
    own binding condition first (above). Every earlier holdout generation is SPENT (each spend is
    recorded in its study's entry and workspace); the density-climber merge-gate holdout
    (frozen with a hash-pinned spec in the merge workspace) remains UNSPENT — no
    candidate arm may touch its cells before the final pre-merge gate.
  • The simulator has NO reference implementation of this climber (the experimental
    density-arm-only SimpleDensityPropClimber was retired rather than shipped
    half-faithful); product.Caffeine is the only arbiter until a faithful reference
    lands. The climber-class extraction that was to enable one has happened
    (WindowClimber), so a reference is now a port, not a redesign — still unbuilt.
  • Judge climber terrain on the object-rate column, not the byte panel. The weighted anchor
    degenerates bit-for-bit to the classic form on unit-weight traces, so the unweighted record
    stands, but the byte track answers a different question and its win/loss counts move for reasons
    that are not the climber's. A related trap that inverted a real cell before it was fixed: the
    admitter was sized once, when the cache first filled, and never re-sized, while the cache
    re-calls ensureCapacity on every addition. Since the sketch's reset period scales with its
    capacity, a frozen one is TinyLFU aging at near-zero period, which flatters small windows and
    washes out at large ones.
  • No duty or dwell bound can detect the deferral defects, and that result is worth not
    re-deriving.
    The shapes that motivated the search violate the schedule's provenance, not
    its magnitude — maxOverdue measures zero straight through a pin that lasts over a hundred
    samples — so no elapsed-time or duty bound can see them. What survives the search is three
    oracle clauses, none of them a duty bound: two ownership/evidence clauses (a layer's rung and
    wait grow only on a sample that ended its own walk; the wait passes its floor only on a walk that
    reached a verdict), which carry zero constants and kill mutations quickly but are regression
    value rather than discovery; and one progress clause (a due audit is refused for at most a
    bounded number of consecutive samples). The progress clause has real discovery content — on the
    pre-fix tree it fails in seconds and fires on exactly the cells the fix moves — but it was not
    adopted, because its margin sits one sample under the shallowest defect instance and it is a
    mechanism detector whose firings need a human to price, including where the refusal is
    load-bearing. Killed alternates: upper duty bounds (every defect is duty-too-low),
    sliding-window lower bounds (permanent false positives), and "consecutive samples commanding no
    motion" (the progress clause in disguise, or blind at the absolute level a legitimate deep park
    requires).
  • Simulator operational gotchas: the default admission list includes Clairvoyant, which wraps
    the trace in a materializing reader (pass -Dcaffeine.simulator.admission.0=Always for LRU
    anchors, or run product-only); simulator:simulate's chart renderer wedges headless (run one
    size per call); Twitter/IBM holdout projections live at ~/projects/merlin-traces/holdout/.

Decision attribution and later retention

Establish the replay before interpreting a verdict. Independently replay the same prefix
into the real cache; compare full policy/controller state, queue order, sketch, node/lifecycle
metadata and pending maintenance at the intervention. An A/A continuation must reproduce demand
outcomes and state. Check observation neutrality separately against the same admission rule with
diagnostic hooks disabled. Do not add cleanup or cache accesses to obtain a convenient snapshot.
If local instrumentation keys admission by request, event phase, ordered candidate/victim and
contest occurrence, verify shared identities and variates and count branch-only contests
separately. The committed seeded-stream harness does not supply that stronger pairing by itself.
Keep study-only hooks isolated; their logs and allocation costs are not production measurements.

Price the defined action. Count cumulative misses from demand outcomes independently of
controller samples. Record actual allocation and occupancy, transfer carry-over, sampled exposure,
decision reason and reference values. Share external demand, ticker and lifecycle schedules on
immutable request indices; regenerate each branch's loads, expirations and completions. Use
several predeclared horizons spanning immediate
damage, settling and repayment. Missing decisions, no-op interventions and censored horizons do
not establish zero headroom. A static optimum or a large expiry count cannot label a false reject.

Later retention is a separate decision-time diagnostic. Its question is whether substantial
remaining savings and a cheap distinguishing signal coexist at a later baseline decision:

  1. Predeclare an original episode event, a small finite set of later offsets or eligible source
    boundaries, skip rules, permissible actions, materiality and controls. Include cases where
    retention is harmful. Select eligibility from the baseline prefix at the exact pre-action
    boundary, without choosing times from subsequent loss, a future phase label or an alternative
    branch's success.
  2. Replay the unchanged controller independently through each later point. No earlier forced
    park, hold, tolerance or other intervention may supply its state or evidence. Waiting follows
    ordinary adaptation, even if it loses the good position. A hold used to gather evidence is
    already a treatment. Log which fields were available before the decision; post-route fields
    overwritten by a plant, release or retest are not automatically valid inputs.
  3. From that common state, compare ordinary continuation with one specified bounded action.
    Distinguish retaining the current allocation from returning to a previously observed one.
    If the position has already been lost, charge real capped transfers, displaced residents and
    settling; do not restore an earlier cache snapshot or assume an earlier park's occupancy.
  4. Compare later times at the same predeclared episode endpoints, anchored to the original
    event. Keep the misses incurred while waiting and after acting in the accounting. Equal-length
    postdecision horizons can additionally explain settling, but moving every endpoint later would
    grant the delayed action extra regime time. Report absolute remaining savings first; compare
    them with the early-intervention gain at the same endpoint only when that denominator is
    materially positive. Such a ratio can be negative or exceed one and is not a probability.
  5. Construct discriminator inputs from baseline-prefix observations alone. Offline outcome labels
    may guide fitting on discovery cases; freeze the cheap rule before testing held-out cases and
    seeds with both helpful and harmful retention outcomes. Identical observed histories must
    produce identical rule outputs until baseline-observable evidence diverges. A difference in
    counters is not enough: it must distinguish the better action while substantial savings remain.
    Future replay may label outcomes, never become a feature or choose a per-event trigger by its
    realized savings.
  6. Report remaining savings and discriminator performance against decision delay, including
    waiting, movement and recovery costs. For the tested actions and discriminator, a useful
    interval exists only where both tests pass. Report no such interval if the signal arrives after
    the residents or most of the benefit are lost.
    Do not assume savings decline monotonically, select the best noisy time as recoverable regret,
    or add overlapping event gains. An unexercised or underpowered interval remains inconclusive.

Keep true abstentions separate from missing opportunities: a false signal at an observed eligible
decision is exact baseline continuation, while an absent episode or eligibility window supplies
no action label. Do not require an entire control group's mean to be negative before recognizing
an individually paired harmful action that the signal rejects. Report both group gates and
individual losses. A failed earliest-trigger rule also does not exhaust the registered later
points; a timing hypothesis chosen from discovery needs its own frozen held-out confirmation.

Price the smallest sufficient history. A conjunction of episode-relative support tests can retain
the preceding sample's boolean verdict instead of all preceding measurements, provided it is
updated on every complete sample and reseeded when the reference changes. Keep the current-only
action predicate out of that history. A sampled clock may reproduce a request-indexed diagnostic
on a fixed unit-weight panel without doing so under buffering, weights or lifecycle changes;
verify the equivalence on the actual targets and state that boundary.

Only a surviving discriminator justifies an end-to-end candidate. Freeze its first-trigger and
subsequent behavior, then replay that candidate on its own evolving states: baseline event
tables cannot estimate the reachability or gain after earlier interventions. The existing
behavioral, corpus, holdout, invariant and runtime-cost gates still apply. A useful one-decision
signal justifies measuring that candidate; it does not establish the repeated policy's gain or
make unmeasured runtime cost either free or an obstruction.


.Claude Docs Jsr107 Conformance (`.claude/docs/jsr107-conformance.md`)

JSR-107 (JCache) Conformance Reference

Sources, audit methods, and standing rulings for the jcache/ adapter. Read the relevant topic
before raising a finding; these conclusions preserve accepted behavior and resolved defects.
Provider comparisons are recorded evidence, not a substitute for checking current source.

Sources and method

Use the JSR-107 1.1.1 specification and API javadoc. The 1.0 PDF predates changes to
getCacheNames iterator behavior, typed getCache(String), and iteration over expired entries.
Compare the 1.1.1 API javadoc before relying on older text; the specification's revision history
records no 1.1 behavior change.

Fetch the spec as needed; do not bundle it. Cite section or javadoc names because exports reflow.

bash
SPEC_DOC=1ijduF_tmHvBaUS7VBBU2ZN8_eEBiFaXXg9OI0_ZxCrA
curl -fsSL "https://docs.google.com/document/d/$SPEC_DOC/export?format=txt" -o /tmp/jsr107_spec.txt

For an ambiguous behavior, read the spec, inspect what the TCK actually asserts, compare the RI
and at least three other providers, then check Caffeine's sibling paths. Explicit spec wording
outweighs RI behavior; the RI helps resolve ambiguity but has documented bugs. Preserve TCK
interoperability where its assertions are stricter than 1.1.1, and record the distinction.
Use the other providers to check whether the RI is an outlier. When providers split, favor the
spec text and RI interpretation and record the differing results.
Source comments and search summaries are not verified behavior. In particular, prior summaries
reversed Ehcache 3 and Coherence's synchronous listener exception handling.

Area Spec section / API RI source
Expiry Expiry Policies; ExpiryPolicy method table RICache, RICachedValue
Events and filters Cache Entry Listeners; CacheEntryListener, javax.cache.event RICacheEventDispatcher, RICacheEntryEvent, RICacheEntryEventFilteringIterator
Counters and timing Statistics Effects of Cache Operations; CacheStatisticsMXBean RICacheStatisticsMXBean, counter call sites in RICache
Loader and writer Integration; CacheLoader, CacheWriter RICache.writeCacheEntry, deleteCacheEntry, batch calls
Entry processors Entry Processors; MutableEntry, EntryProcessorResult EntryProcessorEntry, MutableEntryOperation
Copying Store-By-Value and Store-By-Reference RISerializingInternalConverter, RIReferenceInternalConverter
Configuration Configuration; factories and type enforcement RICache, configuration classes
Lifecycle and JMX Caching Providers; CacheManager/Cache close contracts RICacheManager, RICachingProvider, RICacheMXBean

The spec's Annotations section (CDI/Spring) is outside this adapter's scope. The audit skill's
A–N matrix covers the remaining surface. Expiry, events, statistics, integration, and processors
need event/statistic assertions: the TCK often checks only final contents. Do not infer that an
operation's expiry-event column of “No” forbids an independently timed expiry notification.
For statistics, check the operation table as well as broad counter definitions: a read-through
get is a miss, not a put; clear differs from removeAll; expiry has no dedicated MXBean counter.

Provider source map

Fetch individual files or shallow-clone small modules. Locate current paths before citing them;
Hazelcast, Infinispan, and Coherence are large enough that a full clone is usually unnecessary.

Provider repository JCache expiry entry point
cache2k/cache2k TouchyJCacheAdapter, ExpiryPolicyAdapter.calculateExpiryTime, durationToTicks
oracle/coherence coherence-jcache/.../localcache/LocalCache, JCacheEntryMetaInf.modified, LocalCacheValue.updateInternalValue
ehcache/ehcache-jcache (Ehcache 2) JCache.setTimeTo
ehcache/ehcache3 Eh107Cache, Eh107Expiry
hazelcast/hazelcast AbstractCacheRecordStore.updateRecord
infinispan/infinispan jcache/commons/.../AbstractJCache.put / replace

Find other domains' writer, MXBean, listener, and copier classes in the same modules. Trace
delivery as well as publication: Coherence's UPDATED notification comes through a backing-map
listener, not an explicit event call in put. Distinguish local and partitioned variants.

Validation pattern

Run :jcache:test and :jcache:tckTest for conformance changes. The latter unpacks tests into
jcache/build/tck/org/jsr107/tck/; its cache-tests-1.1.1-test-sources.jar is also in the Gradle
cache. Read CacheExpiryTest and CacheMBStatisticsBeanTest assertions, not just test names.
CacheLoaderTest.shouldPropagateExceptionUsingGet and shouldPropagateExceptionUsingLoadAll
assert the CacheLoaderException wrapping 1.1.1 still requires;
CacheMBStatisticsBeanTest.testIterateAndRemove pins iterator hit/removal accounting.

Use a parameterized parity test when sibling operations should agree on events, counters, and
contents. Examples: JCacheCreationExpiryTest.writeOp_absent_zeroCreationExpiry and
JCacheUpdateExpiryTest.writeOp_present_zeroUpdateExpiry. Establish the expected direction from
the contract first; an existing sibling can itself be wrong. Add a pin only where siblings diverge
or the TCK is silent on an outcome a caller or listener acts on; the TCK's per-operation checks
count as pins, and statistics vectors or event and processor tables for behavior recorded here are
not added for completeness.

Expiry

Zero expiry

Creation and update have different contracts. getExpiryForCreation() == Duration.ZERO means
the entry is not added; zero update expiry means it is updated and immediately expires.

Case Store Operation event Put count Writer
Zero creation Suppressed No CREATED; EXPIRED carries the new value None Still runs
Zero update Store immediately expired UPDATED, then independently timed EXPIRED Counted Runs before commit

Do not suppress the writer on zero creation. It persists the caller's write intent even though
the cache stores nothing, matching RICache.writeCacheEntry before creation-expiry evaluation.
Do not substitute the prior value or null in its EXPIRED event: that would hide disposal of the
new value from resource-tracking listeners. A read-through load under zero creation expiry follows
the same rule: get, getAll and a processor's getValue publish EXPIRED for the loaded value
and count no eviction. The RI publishes it from invoke and loadAll, but RICache.getValue
returns null silently for get and getAll.

Recorded creation-path comparison, from source on 2026-08-29:

Provider Writer before expiry guard putIfAbsent result Put counted EXPIRED
RI yes false no yes
Coherence yes false no no
Caffeine yes false no yes
Ehcache 2 suppressed true unverified unverified
Hazelcast suppressed unverified no no
Infinispan below adapter; inconclusive unverified no unverified
cache2k delegated to core; inconclusive unverified unverified unverified

The RI and Coherence have comments suggesting suppression that disagree with their writer/event
call order; trace the implementation. CacheMBStatisticsBeanTest.testExpiryOnCreation pins zero
puts for put/putAll; CacheExpiryTest.expire_whenCreated discards putIfAbsent's result.
Caffeine records a miss for an absent zero-creation putIfAbsent, following the statistics
table's prior-presence rule and getAndPut; the RI incorrectly derives a hit from false.
Pin: JCacheCreationExpiryTest.putIfAbsent_absent_zeroCreationExpiry_recordsMissNotHit.

Recorded zero-update comparison:

Behavior UPDATED / put counted Providers
Store immediately expired yes / yes RI, Coherence
Remove or suppress no / no Infinispan, Ehcache 2, Hazelcast, cache2k

Measured cells: Ehcache 3.12.0 splits by path (put, invoke and loadAll(replace) store and
publish UPDATED then EXPIRED; getAndPut, putAll, both replace forms and getAndReplace
publish REMOVED for the old value); cache2k 2.6.1 publishes EXPIRED for the old value and counts no
put; Infinispan 16.2.3 publishes REMOVED and calls writer.delete. Hazelcast 5.7.0 hung on the
scenario, so its row keeps the recorded reading.

cache2k maps a zero update expiry to ExpiryTimeValues.NOW (ExpiryPolicyAdapter.durationToTicks),
and core's EntryAction.checkKeepOrRemove removes an entry expiring NOW.

Caffeine follows the first behavior because the spec distinguishes creation from update and the
RI agrees. All put/replace/invoke siblings gate suppression on creation only
(expirable == null). CacheExpiryTest.expire_whenModified checks only absence afterward, so
both behaviors pass. The parity tests above check the missing event/statistic distinction.

Policy defaults

  • Creation returning null or throwing, a checked exception included, yields eternal expiry
    (Long.MAX_VALUE), consistently in CacheProxy and JCacheLoaderAdapter. Null creation is
    implementation-defined; the RI would NPE. The normal CREATED/put effects still occur. Pin for a
    checked throw: CacheProxyTest.put_expiryThrowsCheckedException_storesWithDefaultExpiry. The
    adapter catches Exception, so an Error propagates; RI, Ehcache 3, Infinispan, and Coherence
    catch Throwable around expiry calls, Hazelcast catches Exception, and cache2k does not catch.
    Each write consults the policy before it publishes the attempted write's event, counts its put
    or, in the put family, publishes an expired prior's EXPIRED, so the Error leaves no event for
    the aborted write, the order Hazelcast and cache2k use. invoke instead publishes an expired
    prior's EXPIRED before its processor and commits that removal if anything throws (see
    entry processors). Pins:
    JCacheCombinedExpiryTest.*_expired_creationExpiryError_expiresOnce and
    JCacheUpdateExpiryTest.writeOp_present_updateExpiryError_publishesNoUpdate.
  • Update/access returning null or throwing leaves expiry unchanged. The loader helper takes a
    created flag; reload translates the update Long.MIN_VALUE sentinel to the old wrapper's
    deadline. Do not turn a finite deadline eternal or log an NPE for an ordinary null update.
    Pins: CacheLoaderTest.reload_nullUpdateExpiry_keepsExpiration and
    reload_updateExpiryFailure_keepsExpiration.
  • Expiration counts in CacheEvictions, whether discovered lazily or through native EXPIRED
    removal. RI, Ehcache 3, cache2k, and Hazelcast exclude it, but the spec is ambiguous and offers
    no expiry counter. This accepted choice makes expiry visible to dashboards; do not remove it
    for ecosystem parity.
  • An expired entry found by remove/getAndRemove/removeAll emits EXPIRED and an eviction, not
    REMOVED and a removal, and containsKey reaps one the same way where the RI only tests the
    deadline. The RI set overload disagrees with its own single-key/no-argument paths.
    TCK pins: CacheExpiryTest.testCacheStatisticsRemoveAll and
    testCacheStatisticsRemoveAllNoneExpired; the expired set-overload pairing is pinned by
    JCacheCombinedExpiryTest.removeAll_setOverload_expired.

Deadline conversion

ExpirableToExpiry subtracts in milliseconds, then converts the remaining duration once.
The wrapper deadline and ticker share an arbitrary origin. Converting the absolute deadline to
nanoseconds first saturates near the signed-long horizon: at Long.MAX_VALUE - 100, a one-minute
deadline formerly became 100 ns. The lazy Expirable.hasExpired gate was correct; the native
timer was the affected mirror. Custom tickers can reach this boundary, and
JCacheFixture.START_TIME exercises the full long range.

Keep millisecond granularity (the native timer may fire up to about 1 ms later), core's
MAXIMUM_EXPIRY clamp, and call-site Math.max(0L, ...) handling of expire-now. Keep the ticker
read rather than using core's currentTime: dropping it was reverted because auto-increment
tickers shift observed times. Pin: JCacheExpiryTest.nativeDeadline_nearNanosecondSaturation.

Loader creation expiry uses the same ±1 correction as write/access expiry when a finite deadline
collides with sentinel 0 or Long.MAX_VALUE. Pins:
CacheLoaderTest.load_adjustedTimeSentinelZero / load_adjustedTimeSentinelMax.

The millisecond deadlines and their sentinels (0 expire-now, Long.MIN_VALUE unchanged) assume a
clock that neither reads below zero nor crosses the signed wrap, and durations far below
Long.MAX_VALUE milliseconds. The eternal deadline is the same boundary: at a clock below zero an
unguarded hasExpired wraps it into expired, so an eternal replacement or an in-place renewal to
eternal seen by removeExpired's re-check is removed. These boundaries are accepted without a fix.
The recorded witnesses used custom boundary tickers; System.nanoTime documents that its values may
be negative, so the system ticker can also read below zero, at a frequency that is unmeasured.
JCache cannot express a lazy expiry below a millisecond: Duration rejects smaller units, and
TypesafeConfigurator reads a configured duration in milliseconds, so a smaller one becomes
Duration.ZERO.

Access expiry

getAccessExpireTime evaluates the policy; adjustAccessExpireTime writes the resulting deadline
to the held wrapper. Only lock-free reads call setVariableExpiration to update the
native timer by key, after writing the wrapper: any core read re-derives the native deadline from
the wrapper through ExpirableToExpiry, so one landing between the two writes would otherwise
restore the older deadline. Pin: JCacheAccessExpiryTest.get_concurrentRead_extendsNativeExpiration.
Writes already refresh the native timer through core's expireAfterUpdate, including a
same-wrapper return. Calling policy setExpiresAfter inside their compute violates the policy
API's atomic-scope restriction and can enter maintenance while holding a bin lock.
Read-path anchors: getAndFilterExpiredEntries, EntryIterator.hasNext, and
LoadingCacheProxy.getOrLoad.

An access whose millisecond deadline equals the wrapper's writes neither the wrapper nor the native
timer. getExpiryForAccess() is still called once per access, which the TCK counts, and
Duration.getAdjustedTime is millisecond arithmetic, so the skipped writes are unobservable.
Rewriting them made every hit take the node's lock and queue a write-buffer task through
setExpiresAfter. Under AccessedExpiryPolicy on 8 threads a hit cost ~2.2 us, and ~0.2 us with
the skip. Pin: JCacheAccessExpiryTest.get_sameMillisecond_keepsNativeExpiration.

A replacement between a read and its by-key timer update can receive the old read's native deadline.
The old wrapper alone receives the timestamp, so this does not permit a stale value through its
lazy-expiry check, and a replacement that keeps the old deadline loses that read's extension, under
the standard AccessedExpiryPolicy too. getExpiryForAccess() is parameterless; standard
Accessed/Touched policies use the same access and creation duration. A custom access duration
shorter than creation can cause early native eviction and EXPIRED for the replacement. This race is
accepted; do not add a bin lock or identity guard to every access-expiry read.

get and getAll are happen-before, so a read whose captured wrapper has expired removes it only
by identity and continues with a live replacement that the removal kept: a key replaced before the
captured deadline was never absent. The removal also rechecks the captured wrapper's deadline at
the read's clock, so a wrapper that another read extended meanwhile is kept as well, containsKey's
removal included. LoadingCacheProxy.getOrLoad reaches the same answer by looking the key up again.
That lookup can find a concurrent insertion or replacement and return it without another
ExpiryPolicy.getExpiryForAccess() call, as can getAll's lookup of initially missing keys.
Native expiry still applies; ExpirableToExpiry mirrors the wrapper's stored creation/update
deadline. Skipping this one access adjustment is an accepted read-through race. Do not add a
special expired-wrapper recovery fix for the same miss-then-insertion pattern. Pinned by
CacheProxyTest.getLoading_replacedBeforeExpiry_skipsAccessExpiry, with
get_replacedBeforeExpiry_appliesAccessExpiry as the plain-read control.
containsKey and EntryIterator.hasNext are last-value and may still skip it. A read whose thread
stalls across its own deadline between the two writes still loses its extension, because
setExpiresAfter refuses an expired node; closing that needs the per-read key lock declined above.
The reverse ordering is the same accepted race from the other side: removeExpired's
computeIfPresent re-reads the wrapper's volatile deadline at removal time, so a renewal already
written before that check runs is respected and the removal backs off. But if the check has
already passed and unlinked the wrapper before the renewal's write lands, that write reaches an
object no longer reachable from the map — a lost update, not a stale read, since the renewing
get already returned its value at the linearizable point before the removal. Closing this needs
the same declined per-read key lock.
Pinned by CacheProxyTest.get_replacedBeforeExpiry, getAll_replacedBeforeExpiry,
containsKey_extendedBeforeExpiry, get_extendedBeforeExpiry, and getAll_extendedBeforeExpiry.

An accepted timestamp-race report combined zero access expiry, an eternal entry, and a concurrent
processor READ to produce EntryProcessorException. Its postProcess path predates pre-processor
expiry reconciliation and is not a current reproducer; the lock-free boundary remains intentional.

The iterator stamps access expiry in hasNext() when staging the entry. Deferring to next()
widens its expiry hole or makes hasNext() promise an unavailable entry; the iterator contract
already allows next() to return null after expiry. Calling hasNext() without next() extends
one entry's deadline, an accepted cost. TCK: CacheExpiryTest.iteratorNextShouldCallGetExpiryForAccessedEntry.

CacheProxy.get always takes a fresh ticker.read() to derive the clock it checks hasExpired
against. LoadingCacheProxy.getOrLoad instead reuses the statistics start timestamp
(nanosToMillis((start == 0L) ? ticker.read() : start)) when statistics are enabled, so enabling
statistics changes which reading its expiry check uses while get's is unaffected. This is
intentional-enough, not a bug: the reused stamp is taken earlier in the method, so it can only
make the check see an older clock, which can only judge an entry live that a fresh read might
have judged expired — expiration is a maximum lifetime, and both readings satisfy that. The
compute-based writes, remove(K,V) and replace(K,V,V) included, read the clock inside the
remapping function instead. A stamp taken before the compute is older by the wait for the entry's
lock, so it would judge an entry that expired during the wait as live and date a mismatch's access
deadline from before the wait. Pinned by JCacheAccessExpiryTest.removeConditionally_mismatchAfterLockWait
and replaceConditionally_mismatchAfterLockWait.

Native extensions

Returning the same wrapper from a query still reaches core's native-write metadata. Failed
putIfAbsent (including keep-existing loadAll), a NONE-action processor, and eternal-policy
failed conditional writes/processor READ can reset native write time and cancel refresh. Thus
polling may defer vendor refresh.after-write or eager native TTL. This is accepted: JCache
expiry comes from ExpiryPolicy, while those Caffeine settings are optional extensions.

containsKey probes with getIfPresentQuietly, as keep-existing loadAll does. A recording read
would start a due vendor refresh, which calls the CacheLoader that the spec says a read-through
containsKey never calls, and it would count as an access, which the ExpiryPolicy table does
not give containsKey. Pin: CacheLoaderTest.containsKey_refreshAfterWrite_doesNotCallLoader.

With a vendor Expiry beside a JCache ExpiryPolicy, the native timer is not a mirror of the
policy, so its deadline after an access depends on the operation: a read sets it to the JCache
access duration, a compute-based access (invoke READ, a failed conditional, putIfAbsent on a
present key) leaves the vendor's expireAfterUpdate, and containsKey leaves it unchanged. Every
read still checks the wrapper's deadline, so only when eager eviction and its EXPIRED happen varies.

Do not expose RemapHints as a public no-op escape. Treating an unchanged value as no write
would change asMap().compute for all users and leave write-deque reorder paired with a stale
timestamp. A computeIfAbsent plus expired fallback splits the atomic operation and does not
solve invoke. These repairs were rejected.

A throwing extension Weigher (setWeigherFactory / setMaximumWeight) or native Expiry
(setExpiryFactory / ExpiryAdapter) runs in core after the adapter's remapping
function has written through and published the event, so it can abort storage after those
effects. Standard ExpiryPolicy is different: the adapter catches every Exception it throws
and uses the default duration, while an Error propagates.
The extension case remains accepted misuse of callbacks whose core contract forbids throwing.
Notifications/writer effects cannot be rolled back, and core has no post-metadata hook. Attempted
creation/update notifications are accepted; store/cache discrepancies need external reconciliation.
Do not move publication outside compute, which would lose per-key order. Unlike invoke,
put/getAndPut do not blanket-clear synchronous futures after arbitrary extension failure; a
pending notification reaching the next operation is also within this accepted boundary. A gate
must still be released from finally so dispatch can drain.

Entry processors

EntryProcessorEntry.Action records the dominant operation. Preserve these distinctions:

Sequence / terminal action Resulting effects
Read-through getValue() / LOADED Miss, no put; follows Cache.get and the RI LOAD case
Remove an absent or expired entry / DELETED Writer delete if write-through; no REMOVED or removal count
Load, then remove / LOADED → DELETED Writer delete; intentionally differs from RI's cancellation
Create, then remove / CREATED → NONE Full no-op
Load, set a value, then remove / LOADED → CREATED → NONE Full no-op, including writer and events
Update a present entry / UPDATED Writer must succeed before put count, UPDATED, and store

The RI counts/fires a removal with null for absent processor removal, unlike its own remove(K).
Caffeine follows the listener table's requirement that an entry was removed; the statistics
table's wording is inconsistent. TCK covers remove-on-present only. For a read-through load,
pin the miss/no-put behavior with CacheProxyTest.invoke_readThroughLoad_recordsMissNotPut.

A remove that undoes this invocation's own create returns the entry to its untouched state: a
later getValue reads through if the loader has not yet been called in this invocation (as in
the RI), and a remove issued before that create is not written through (as in every provider
compared). A remove of any other state is DELETED and blocks read-through.

This terminal-action model follows the RI and the compared providers rather than the
specification's Entry Processors examples, which read more broadly. For expiry the
specification's own ExpiryPolicy table is explicit and takes precedence over Example 1's
first-read wording, so a read-modify-write invoke under AccessedExpiryPolicy keeps its
creation schedule. Example 3's writer delete after load, set and remove is the row above's full
no-op, which the RI shares and the TCK does not test.

invoke reconciles a lazily expired prior before the processor: publish EXPIRED, count eviction,
and expose absence. (A prior that core has already expired natively is instead reaped by core
with the quiet EXPIRED of dispatch and commit, which the caller does not
await.) If the processor or writer then throws any Throwable, commit removal of
that expired prior, await its synchronous listener, and rethrow after compute via
processorFailure (Error unchanged; other failures as EntryProcessorException), with a
listener failure suppressed onto either. Otherwise the already-published expiry would be orphaned
and could fire again. postProcess is expiry-free; READ/UPDATED require a live prior, so do not
re-read the clock or restore an expiry check there. Pins:
EventDispatcherTest.invoke_expiredAndProcessorThrows_retainsListenerFailure and
invoke_expiredAndProcessorThrowsError_retainsListenerFailure.

Read-through MutableEntry.getValue() normalizes the direct loader call before returning control
to the processor: existing CacheLoaderException passes through, other Exceptions are wrapped
in it, and a direct InterruptedException first restores interruption. Error propagates unchanged.
A processor can therefore catch the loader failure and store a fallback. Keep this guard local;
the storage-oriented loader adapter would duplicate copying, expiry and event publication.
Pins: CacheLoaderTest.invoke_loaderFailure_canRecover,
invoke_loaderInterrupted_restoresInterruptStatus, and invoke_loaderError_propagates.

Exceptions escaping processor invocation, including write-through failures, surface as
EntryProcessorException; the spec's “Exceptions in EntryProcessors” section includes failures
from the caching implementation itself. Single-key writer failure therefore surfaces as
EntryProcessorException(CacheWriterException). The RI exposes a raw writer exception because
its writer is outside the catch; cache2k wraps and Ehcache 3 exposes CacheWritingException.
TCK accepts all as CacheException; explicit spec text governs this corner.

Pre-processor copy failures in single-key invoke, of the key or the prior value, remain a raw
CacheException, consistent with the API's general cache-failure surface: invocation begins
when the processor is called. invokeAll, however, must isolate each key's
runtime failure in its EntryProcessorResult: preserve an existing EPE, wrap a non-EPE once,
and continue. Otherwise one uncopyable key aborts the batch and discards completed results.
RI, Ehcache 3, cache2k, and Hazelcast also isolate per-key failures (some double-wrap EPE).
Pin: CacheProxyTest.invokeAll_perKeyFailure_isolatedNotAborted.

Both invoke variants forward an explicitly null varargs array unchanged; only the key/keys and
processor require null rejection. RI, Ehcache 3, cache2k, Hazelcast, and Infinispan forward null;
Coherence indirectly reads its length. Normal omitted varargs remain an empty nonnull array.
Pins: CacheProxyTest.invoke_nullArgumentsArray_forwarded and
invokeAll_nullArgumentsArray_forwarded.

getValue on a DELETED entry returns null without loading. A successful null read-through load
is consumed rather than retried by another getValue in the same invocation. A failed load
leaves the loader available for a processor that catches the failure and retries. Keep loader
consumption and LOADED assignment after successful return, not in a finally block. Pins:
CacheLoaderTest.invoke_removedEntry_doesNotLoad, invoke_nullLoad_consumed, and
invoke_loaderFailure_canRetry, each covering invoke and invokeAll.

Read-through and event ordering

Read-through getAll materializes loaded values with core put, replacing a value written
concurrently during the load, and JCacheLoaderAdapter.loadAll publishes CREATED for the loaded
value. This accepted choice treats the load as a fresh read of the system of record and preserves
notification for resource-tracking listeners. Do not align it with
loadAll(replaceExistingValues=false), whose explicit contract is to keep existing values. That
separate API uses loadAllAndKeepExisting / putIfAbsent. Under replaceExistingValues=true a
present key the loader cannot load is kept by the RI, Caffeine, Hazelcast and Infinispan and removed
with a REMOVED event by Ehcache 3.12. The spec's Read-Through table says getAll invokes
loadAll(), which Caffeine and Ehcache 3 follow (Ehcache once per missing key), while the RI,
cache2k 2.6 and Hazelcast 5.7 load per key through load; a loader whose loadAll throws (for
example UnsupportedOperationException) therefore works with the RI's getAll and fails Caffeine's
with CacheLoaderException, and only Caffeine's getAll sees the bulk shapes. Two CREATED events
without an intervening UPDATED are an accepted concurrent-load outcome. A key the loader returns
outside the request is unspecified by CacheLoader.loadAll and the TCK, so it is stored and
published as CREATED like any loaded value, even over an existing mapping; a presence check before
core's put would be racy check-then-act. The same side-load does not classify the prior: it
replaces an unrequested key's JCache-expired prior without its EXPIRED event or eviction when native
expiry does not mirror the JCache deadline (a vendor expireAfterWrite), and with the default
mirror the EXPIRED arrives after the CREATED; under zero creation expiry the unrequested key's
EXPIRED names the discarded load while the prior stays live. loadAll routes such keys through the
write path and announces the expired prior first, as the RI does.

The overwrite holds under write-through and for a concurrent delete as well. A put or remove that
commits through the writer while the bulk load runs is undone when the loaded value is stored: after
a put the store holds the new value and the cache the loaded one, and after a remove the deleted
row is back in the cache. Either way the cache trails its store until the entry expires. This is
accepted. The spec gives getAll per-key happen-before but also has it invoke loadAll(), and no
surveyed provider reconciles the two with a batched load: the RI and Ehcache keep the write by
loading one key at a time under that key's lock, Ehcache through a single-key loadAll. A per-load
token that every write discards would keep both the batch and the write, at the cost of a hook on
each adapter write path to make the adapter stricter than any provider; core's own bulk overwrite
stays as its contract states. Cache.loadAll has the same shape, and its consistency row is "N/A".
An application that needs the write kept can use Caffeine's AsyncCache.getAll, whose bulk load does
not stomp a write to a requested key.

The listener contract does promise per-key event ordering for synchronous and asynchronous
listeners, and says listeners fire after the cache mutation. The accepted read-through behavior
diverges from those promises: loader publication precedes core bulk storage, so CREATED can
precede EXPIRED for an expired prior even without a concurrent writer. Single-key loading first
discards the expired entry. Do not describe the contract itself as lacking an ordering promise.

Recorded evidence: the inversion occurs when the reload lands before the timer wheel has swept
the prior's bucket, whichever executor runs maintenance. On 2026-09-24 a getAll about 100 ms
after a 50 ms deadline inverted in 17 of 20 runs on the direct executor and 16 of 20 on the
common pool, and in none at about 1.25 s. The sweep follows a level-0 timer-wheel tick (2^30 ns of
the ticker): on the direct executor the inversion occurs exactly when no tick has passed between the
prior's write and the getAll, and on the common pool a tick lets the sweep win only usually, more
rarely under CPU contention. The transitions can also end at a value the cache does not hold: when
a concurrent put commits between a bulk load's CREATED and its insert, the load overwrites the
put's value, so a mirroring listener's last event names a value the cache no longer holds and no
later event corrects it. That is the accepted bulk overwrite's event face (see the write-through
paragraph above). The accepted rationale is
the timing/transport behavior of distributed providers, while preserving the contract difference:
Hazelcast 5.7's per-entry cache-event factories did not set the publication order key (bulk
removal used the key-set hash); Coherence localcache dispatched EXPIRED before loading, but its
partitioned expiry used a separate synthetic-event listener and delivered asynchronously even to
synchronous registrations. The RI accumulates per-operation events in one JVM and orders this
case. Arrival order in a distributed replica also cannot cover lost-node or split-brain events;
the recorded decision accepts reconciliation by transition rather than imposing a new adapter
ordering guarantee. These observations are not a claim that the spec permits the divergence.

If a concrete requirement justifies repair, the recorded direction is core getAll: discard
expired entries in the miss set before loading, as single-key loading does. The adapter's quiet
accessors expose no expired mapping for a conditional removal. Treat publication-before-storage
and reload-before-prior-expiry as the same accepted design choice, not independent new findings.

Statistics

Operation timing

  • Loading get/getAll opens a per-thread timing scope after recording a miss.
    JCacheLoaderAdapter contributes only time spent in delegate.load/loadAll; the outer
    operation records total minus loader time once, from finally. Copying, expiry evaluation,
    publication, and failure handling stay included. Native loads through
    unwrap(LoadingCache.class) have no scope and do not alter JCache get time.
  • Do not restore a negative global pre-credit. An output-copy failure or native load without
    an enclosing JCache read left that credit unbalanced and made AverageGetTime negative.
    RI, Hazelcast, Infinispan, and cache2k avoid negative means, although some include loader time.
    Pins: CacheLoaderTest.load_outputCopyFailure_keepsGetTimeNonNegative,
    load_failure_excludesLoaderTime, load_copierTime_includedInGetTime, and
    nativeLoad_doesNotChangeJCacheGetTime (single and bulk variants).
  • Successful plain getAll ends its timer after copyMap, matching get and loading reads.
    RI and Coherence include return conversion; Hazelcast's bulk per-key timer and cache2k's
    core load-time metric differ. The MXBean's execution-time definition and internal parity
    support inclusion. Pin: CacheProxyTest.readOp_outputCopyTime_includedInGetTime.
    Failed-read hit accounting remains distinct: plain get and the RI count a hit only after
    successful output conversion.
  • PutAll starts timing before preparing CopiedEntry inputs; all copies still finish before
    writeAll and only successful stores count as puts. RI starts before conversion; Coherence
    delegates to its copy-inclusive put, while Hazelcast times below serialization and cache2k
    reports no put mean. Pin: CacheProxyTest.writeOp_inputCopyTime_includedInPutTime across
    put/putAll, create/update, and statistics enabled/disabled.
  • CacheGets = hits + misses. Caffeine divides each average duration by its own operation
    counter; the RI's three averages all divide by gets, a known RI bug to avoid copying.
  • recordHits/recordMisses and the conditional pairs that call them (replace(K,V,V),
    batch hit/miss splits) are unconditional LongAdder.add, unlike recordPuts/recordGetTime/
    recordPutTime/recordRemoveTime, which also gate on a nonzero count. The asymmetry is
    accepted under best-effort statistics: the zero count costs one uncontended LongAdder
    update, and rewriting the pairs as if (found) recordHits else recordMisses, as the sibling
    call sites read, was declined as churn.
  • invoke, conditional remove, and iterator yields count gets without timing them, and an
    empty getAll times no gets. The RI times the first three and not the last; these populations
    stay unaligned under best-effort statistics. putIfAbsent's hit or miss is untimed too, as in
    the RI. The same mismatch reaches invoke's put and
    removal counters: postProcess calls recordPuts/recordRemovals for CREATED/LOADED/UPDATED/
    REMOVED actions but never opens a timing scope, so getAveragePutTime and
    getAverageRemoveTime are diluted by invoke traffic the same way getAverageGetTime is.

Commit and failure accounting

Writer failure suppresses the operation's put/removal count as well as its mutation event and
store. This includes invoke UPDATED, whose writer must precede the count. Parity pins:
CacheWriterTest.writeOp_failingWriter_noPutsRecorded, writeOp_writerSucceeds_recordsPut, and
removeOp_failingWriter_noRemovalsRecorded across direct, batch, processor, and iterator paths.

After a committed effect, capture a synchronous listener failure with awaitSynchronousFailure,
record the required counters/timers using the operation's existing start-state rules, and then
rethrowListenerFailure. Direct operations follow bulk/invoke/loading siblings. Returned-value
copying in getAndRemove/getAndReplace happens after accounting, as in getAndPut, so copier failure
does not erase the committed effect. The spec does not prescribe this ordering against listener
failure; required counters and sibling parity justify it. This covers the specified listener
exception, not arbitrary Error; enable/disable races remain spec-undefined. When a synchronous
listener throws an Error, awaitSynchronousFailure does not capture it, so the statistics block
is skipped: the committed mutation is retained but its counters are not recorded for that
operation. The mutation is not rolled back. Pins:
EventDispatcherTest.synchronousListenerFailure_committedMutationRetainsStatistics and
synchronousExpiredListenerFailure_retainsGetStatistics.

Write-through

Single-key writer calls and their cache mutations share the per-key compute lock. This follows
CacheWriter's non-batch atomicity contract. In particular remove/getAndRemove must use compute,
not computeIfPresent, because delete must run even for absence. A writer call before compute lets
a same-key put interleave, leaving cache and store inconsistent. Preserve publishToWriter so
bulk loops can reuse the mutation without repeating writer calls. Pin:
CacheWriterTest.removeThrough_racingSameKeyPut_noStoreCacheDivergence.
RI, Ehcache 3, Hazelcast, cache2k, and Coherence partitioned serialize these effects; recorded
Coherence localcache had the old unlocked writer/removal window.

Bulk writeAll/deleteAll runs once before per-key computations. The spec exempts batch methods
from cross-key atomicity; a racing single-key operation can invert cache/store order across
that window. This is accepted for both putAll and removeAll. RI locks all keys beyond the spec;
Ehcache 3 and Coherence partitioned batch like Caffeine, while cache2k/Infinispan/Coherence local
loop per-key through their persistence SPIs. Do not replace batching with per-key writer calls
solely for RI parity. The Integration contract also bounds write-through guarantees to the cache
being the application's only writer to the external resource.

Treat a non-throwing writeAll (putAll) or deleteAll (removeAll) as full success. Honor a
writer's residual collection only on a partial failure, when it identifies entries that failed.
The RI's residual-on-success interpretation made both putAll and removeAll a no-op for a
non-clearing writer and was rejected for both. Pins:
CacheWriterTest.removeAll_nonClearingWriter_stillEmptiesCache and
putAll_nonClearingWriter_stillStores.

A partial batch failure reconciles the same way whatever Exception the writer throws, including
a checked exception thrown undeclared by another JVM language. A writer Error after a partial
writeAll/deleteAll propagates before the store loop, leaving the external resource changed
for the completed entries and the cache unchanged, as in the RI. Pins:
CacheWriterTest.putAll_partialSuccess_storesWrittenEntriesOnly and
removeAll_partialSuccess_removesDeletedEntriesOnly. The specification requires wrapping any
Exception a writer or loader throws. Recorded from bytecode on 2026-09-14: RI, Ehcache 3,
Hazelcast, Infinispan, and cache2k catch Exception or Throwable around loader and writer calls
(Ehcache and cache2k in their cores), while Coherence's writeAll and deleteAll catch only
RuntimeException.

The RI swallows a CacheWriterException thrown by writeAll/deleteAll, leaving nothing to
rethrow; Caffeine propagates it, as CacheWriter.writeAll's "if thrown cache mutations will occur
for entries that succeeded" requires. Pins:
CacheWriterTest.putAll_fails_propagatesSameWriterException and
removeAll_fails_propagatesSameWriterException. When a write-through put over a JCache-expired
prior fails, the RI has already removed the prior and skips its EXPIRED, and a read-through get or
getAll whose loader throws over an expired prior removes it the same way; Caffeine keeps the prior
until a later read or commits and announces it, one EXPIRED either way.

Converting or defaulting a callback's InterruptedException first restores the thread's interrupt
status, as core's Caffeine.toUnchecked does, since the wrapper or the default would otherwise hide
the interruption. Pins: CacheLoaderTest.readThrough_loaderInterrupted_restoresInterruptStatus,
CacheWriterTest.writeOp_writerInterrupted_restoresInterruptStatus, and
CacheProxyTest.put_expiryInterrupted_restoresInterruptStatus.

The batch exemption does not permit writes the adapter later refuses because copying failed.
Copy all putAll keys/values before writeAll and reuse CopiedEntry in the store loop. A copier
failure then writes/caches nothing. Pins: CacheProxyTest.putAll_writeThrough_copierThrows_doesNotWriteToTheStore
and EventDispatcherTest.putAll_copierFails_abortsBeforeAnyCommit. If a throwing native extension
still aborts a loop after partial writer failure, preserve that CacheWriterException as
suppressed, not replaced. Pin: CacheProxyTest.putAll_writerPartiallyFails_storeThrows_retainsWriterFailure.

No-argument removeAll delegates to the native map key set, which filters natively expired entries.
Those expired-but-unreaped keys are omitted from deleteAll, unlike the RI. This remains an
accepted reading of an existing mapping, with no TCK assertion resolving it. The opposite half of
the same method has the opposite outcome: a key that is JCache-expired but still natively live is
present in that key set, so it is still passed to writer.deleteAll before being reaped as EXPIRED
plus an eviction — not REMOVED. CacheWriter.deleteAll allows both halves, since under 1.1.1 its
keys "may include keys even if there is no mapping". What the second half departs from is
Cache.removeAll()'s "If the cache is empty, the CacheWriter is not called" when every resident is
JCache-expired, which needs a vendor native expiry that outlives the JCache deadline. Both halves
are recorded together here rather than only the natively-expired half.

Events and callbacks

Dispatch and commit

Append events inside the mutating compute to preserve per-key ordering, but stage listener
execution until it returns. beginComputation marks the publishing thread; the first publish
lazily creates a gate. endComputation releases the gate from finally, holding the mark through
release because a caller-runs executor may dispatch there. A throwing Weigher/native Expiry must
not leave the chain blocked. No listeners means no gate allocation, and the release clears the
gate's slot with set(null): remove() discarded the thread-local entry, so every computation
allocated a fresh one. A drain likewise empties the thread's pending list in place, except one
holding more than MAX_RETAINED_PENDING futures, which it releases: a bulk operation with a
synchronous listener otherwise left each thread holding its largest array until the thread died
(about 4.9 MB after a 1M-entry removeAll). Pins:
EventDispatcherTest.awaitSynchronous_largeBatch_releasesPendingList and
ignoreSynchronous_largeBatch_releasesPendingList. Do not stage an event outside a computation,
such as an unwrap-driven load, because it has no matching release.

This staging makes ordinary listeners observe committed mappings. RI dispatches after put under
its key lock; Ehcache 3 releases a StoreEventSink after compute; Hazelcast publishes after record
mutation; Infinispan forwards post events; Coherence uses post-mutation MapEvents. Recorded cache2k
listeners run before its final value write. The read-through publication exception is described
above. Pins: EventDispatcherTest.publish_listenerObservesTheCommittedMutation (a created
listener, observing through the native cache because callback JCache re-entry is refused),
publish_computationThrowsAfterPublishing_doesNotWedgeTheKey, and
CacheProxyTest.unwrap_nativeLoad_dispatchesAndDoesNotStallTheKey.

Every publication uses a dependent CompletableFuture stage, including the first event for a key.
A source supplyAsync calls executor.execute synchronously; rejection inside the cache compute
can abort the cache mutation after the writer succeeds. A dependent stage captures rejection in
its future, so commit completes and the synchronous caller receives CacheEntryListenerException
afterward. This fixed the earlier first-event/successor difference under the non-batch writer
atomicity rule; the rejecting executor is still misconfigured. The “first” branch is common
because idle-key cleanup empties the slot. Pin:
CacheProxyTest.put_writeThrough_eventExecutorRejects_doesNotSplitTheStore.

The queue needs no extra lock: append is an atomic map compute, cleanup is conditional
remove(key, future), and its preceding identity get is only a fast path. A successor installed
before cleanup survives; cleanup before a successor permits a new chain only after the old one
completed. Await/ignore pending synchronous futures on relevant exits and clear in finally.
Bulk operations must drain already-published events even when the loop fails. putAll copying
now happens before any commit, covered by
EventDispatcherTest.putAll_copierFails_abortsBeforeAnyCommit; the earlier mid-loop copier test
was removed, but later failures still require draining.

invoke's outer catch discards rather than awaits the pending synchronous future
(ignoreSynchronous()) before rethrowing. That choice rests on every postProcess branch calling
publishToCacheWriter before its publish*, so a throwing writer aborts before anything is
published and there is nothing pending to leak. This ordering premise is not restated at the catch
site; a future edit that publishes before the writer would silently leak a synchronous future here
without a failing test, since the accepted native-extension and Error boundaries are a separate,
already-scoped exception to the same rule.

An accepted report described duplicate EXPIRED, but exactly-once eviction counts, when a listener
ran before executor rejection aborted a reap. It predates dependent-stage publication. Preserve
the accepted executor-failure boundary, but require a current throwing path before claiming that
mechanism remains reproducible.

Native size/weight eviction publishes quiet REMOVED; native EXPIRED publishes quiet EXPIRED.
Refresh reload publishes quiet UPDATED, EXPIRED for zero update expiry, or REMOVED on a miss.
The last two remove the entry but count neither an eviction nor a removal: core removes it as
an explicit removal, outside JCacheEvictionListener, and statistics are best-effort.
Quiet means no synchronous caller await: it informs resource-tracking listeners without blocking
the evicting/refresh thread. Lazy expiry mostly arrives this way on the system ticker: the native
mirror hides an expired entry from reads, and a read in the sub-millisecond gap between the
wrapper's millisecond deadline and the native one re-derives the native deadline from the expired
wrapper, so removeExpired finds the entry natively expired. A read, or a computeIfPresent-based
replace or conditional remove, that finds an entry expired therefore publishes and counts nothing,
and its EXPIRED and eviction wait for a maintenance cycle after a wheel tick, which an idle cache
without a Scheduler does not run. The adapter's own read-side reap runs only when a vendor
native expiry outlives the JCache deadline. For a read that finds an expired entry, the RI 1.1.1,
Ehcache 3.12.0 and Infinispan 16.2.3 deliver EXPIRED to a synchronous listener before the read
returns, cache2k 2.6.1 does not, and Hazelcast 5.7.0 does so only in some JVM histories, so the
deferral is a Caffeine choice (lazy expiry; a Scheduler for promptness) rather than ecosystem
parity.
Clearing natively expired residents can produce quiet EXPIRED and eviction counts through core's
removal cause, even though ordinary explicit clear removals are silent. Closed-cache delivery is
separately suppressed by dispatch's closed check. A reload publishes before core decides whether
it commits, and core reports a reload it discards because the entry changed in flight only to a
removal listener, which the adapter does not register. That event stands with no mutation, which
is accepted for a best-effort extension that has no post-commit hook. Native eviction and expiry
publish the same way, from core's eviction listener inside the eviction's compute on an unmarked
maintenance thread, so their dispatch is not staged either: a size-evicted entry's REMOVED listener
can read the mapping back before the removal commits, within microseconds of it, and on a
caller-runs executor the listener runs inside that compute, where writing its own key fails with
ConcurrentHashMap's Recursive update. An expired entry is hidden from reads, so EXPIRED shows
nothing stale. Accepted on the same terms: JSR-107 defines no eviction, and staging it would need
core's post-unmap removal callback to release a gate per eviction.

Listener failures

Filters run inside compute and decide whether an event is published. A filter failure, an
Error included, is logged and returns false from GuardedCacheEntryEventFilter.evaluate; it
must not abort a mutation after the writer or earlier registrations already took effect. This
differs from post-commit listener failure. RI commits before propagating filter failures;
Hazelcast/cache2k filter at delivery. Pin: EventDispatcherTest.publishCreated_filterThrows.

A synchronous listener's CacheEntryListenerException passes through; other listener runtime
exceptions are wrapped in it. javax.cache.event package-info and CacheEntryListener require
propagation. Listener Error is logged and rethrown unchanged, not wrapped. Ordinary listener
failure travels as a chain result so it does not break subsequent same-key delivery;
awaitSynchronous throws the first failure and suppresses extras. Exceptional executor futures
also become CacheEntryListenerException. Async/quiet failures are logged because there is no
synchronous caller. Mutation remains committed. Spec, RI, cache2k, and recorded Infinispan sources
support propagation. Ehcache 3.12.0, Hazelcast 5.7.0's member event path, and the recorded Coherence
implementation log failures. Hazelcast's listener adaptor releases its completion latch in finally and
StripedExecutor catches the failure; the waiting cache proxy has no listener-exception result.

Pins: EventDispatcherTest.put_syncListenerThrows_propagatesToCaller,
publishCreated_asyncListenerThrows_swallowed,
publishCreated_syncListenerThrows_subsequentEventStillDelivered,
awaitSynchronous_listenerException, awaitSynchronous_listenerExceptions_suppressed, and
EventTypeAwareListenerTest.

A listener failure must not re