ADR-142: A reset_after limit's config is hidden from pre-v0.15 readers¶
Status: Accepted Date: 2026-09-27 Issue: #640 Related: ADR-139, ADR-140, ADR-141, ADR-137, ADR-114, #638
Context¶
A client predating ADR-139 cannot reconstruct a reset_after
limit: it reads a zero rate with no reset, which ADR-137 rejects, and loses the whole config
level with it. Under on_unavailable=block every acquire on that level fails; under allow every
acquire is admitted with no limiting at all, including the level's other limits.
ADR-141 keeps the old aggregator and provisioner away, but
nothing a newer build does can make an old client fail closed.
Every pre-v0.15 config reader discovers limits by the l_ attribute prefix alone, and none of
them rejects an attribute it does not recognise. A limit stored under another prefix is therefore
invisible to them, and they go on enforcing the rest of the level. Bucket attributes need no such
treatment: old clients already read and rewrite a bucket carrying the session limit's attributes
without error, and only the old aggregator mishandles them, which ADR-141 prevents.
Hiding the limit lets old clients write buckets that carry a window, whose shared rf and vu
they stamp from their own limits and clock. ADR-140
makes the window mechanism independent of both, and the seed funds a shard an old client creates
by ADR-145's move off the sibling whose grant covers
it. The storage mapping, readers and verification against
v0.14.0 are in #640 and CLAUDE.md "Hidden config (#640)".
Decision¶
A reset_after limit's config attributes must be stored under the w_ prefix (w_{name}_*),
which pre-v0.15 readers do not read, and every config reader from v0.15 on must read both prefixes and
must treat a name stored under both, or a w_ limit without its window length, as a corrupt item.
Owner decision (2026-09-27): accepted that under on_unavailable="block" a pre-v0.15 client
enforces every other limit and skips only the session limit, rather than refusing the level.
Consequences¶
Positive:
- A pre-v0.15 client keeps enforcing every other limit on a level that holds a session limit,
under either on_unavailable mode, instead of failing the level or admitting it unlimited.
- A level holding only the session limit reads as empty to it, so it falls through to the next
level's limits rather than to none.
- The prefix is a storage mapping only: nothing above the serialiser sees it, and a level written
under l_ by an unreleased build is read as before and moves to w_ on its next write.
Negative:
- Old clients do not enforce the session limit for the requests they serve; under block that
replaces an outage with silent non-enforcement, bounded by their share of traffic.
- An old client stamps a bucket's TTL from the limits it resolves, which can be shorter than the
window's recovery horizon; a bucket swept mid-window restarts its window part-way, admitting at
most one extra allowance.
- An old admin tool rewrites a whole level from the limits it can see, so it silently drops the
hidden limit from config and leaves its balance on the buckets. Admin tooling must be upgraded
first.
- An old param sync can change the item-level schedule a session limit without its own override
inherits. An old client's shard creation, funded on the next v0.15 pass by ADR-145's move, can
only under-admit.
Alternatives Considered¶
Hide the bucket attributes as well¶
Rejected because: old clients already handle them, and only the old aggregator misreads them, which ADR-141 already keeps away.
A hidden field suffix under the l_ prefix¶
Rejected because: any suffix ending in the capacity field stays visible to the old discovery rule.
A separate config item per level for session limits¶
Rejected because: it breaks the atomic full replace of a level and adds a read to every cache miss.
One opaque map attribute per session limit¶
Rejected because: it violates the flat-schema rule of ADR-111.
Leave the configuration visible and upgrade every client first¶
Rejected because: an old client under allow then admits a whole level with no limiting at all.