fix(state): per-author cap for pending buffer (SEC-V-08)#451
Merged
intendednull merged 1 commit intoApr 28, 2026
Merged
Conversation
SEC-V-08. PendingBuffer had only a global cap (5_000 in client). One chatty signer could fill 100% of the slots with unresolved-prev events and starve legitimate out-of-order events for the whole 1 h eviction window. Adds a per-author sub-cap. Default = max_entries / 50 (200 at the client cap, 100 with the state-crate default of 10_000), with a floor of 1 so tiny test caps remain usable. When an author is at the sub-cap, further events from that author are dropped without touching other authors' buckets. Bookkeeping mirrors the existing cached_count pattern: per_author_count BTreeMap<EndpointId, usize> updated on insert / resolve / evict_expired / evict_to. Warn-once-per-author via warned_full_authors set so a single offender cannot flood logs. Why a config knob and not a hardcoded constant: tests need to override the sub-cap to exercise the global eviction policy in isolation. with_per_author_cap(usize) is the explicit override; production callers stay on the derived default. Runner-up was a hardcoded constant — rejected because three existing tests would either need multi-author rewriting or magic-number tuning, and the override is cheaper to read. Tests at the lowest tier (state crate, sync.rs): * per_author_cap_isolates_one_signer * per_author_cap_defaults_to_total_over_divisor * per_author_cap_floor_is_one * per_author_cap_warns_once_per_author * per_author_cap_frees_on_resolve Adapted three existing capacity-eviction tests to set an explicit sub-cap (so they keep testing the global policy): * sync::tests::pending_buffer_auto_evicts_when_limit_exceeded * sync::tests::capacity_eviction_drops_oldest_when_exceeded * sync::tests::pending_count_reflects_both_eviction_policies * tests::pending_buffer_eviction_reduces_count_to_cap Reduced deep_pending_chain_does_not_stack_overflow chain depth from 3000 to 1500 so it fits within the 100_000-cap'd ManagedDag's per- author budget of 2000. Test still proves the iterative resolution path; capacity policy is validated separately. Refs #237
intendednull
pushed a commit
that referenced
this pull request
Apr 28, 2026
…ional three lessons from batch 2026-04-28-002530: 1. coordinator must `git fetch + reset` master batch branch before each implementer dispatch — stale local state contaminates the next worktree (#451 implementer found half-applied prior work in fresh worktree). 2. when implementer's pre-flight detects upstream fix already landed (#316/#317/#318 vs PR #402), close issues + caveman-comment, do NOT include in master PR `Fixes` list — record under `## Already-Fixed` instead. 3. github webhook subscriptions for sub-PRs are informational only — implementer owns its merge gate, coordinator must not duplicate that work.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
why
SEC-V-08. PendingBuffer had global cap only. One signer can fill all 5_000 slots with chain-gap events, starve legit out-of-order events for the full 1 h eviction window. Robustness, not impact, but trivial to fix.
what
max_per_authoronPendingBuffer. default =max_entries / 50(floor 1).cached_count:per_author_count: BTreeMap<EndpointId, usize>updated on insert / resolve / evict_expired / evict_to.warned_full_authors: BTreeSet<EndpointId>. one chatty offender cannot flood logs.with_per_author_cap(usize)builder for tests/explicit overrides. production callers stay on derived default.tradeoffs
pending buffer at capacitywarn cadence — debug logs can be raised if needed.tests (state crate, lowest tier)
new:
per_author_cap_isolates_one_signer— attacker capped, victim untouchedper_author_cap_defaults_to_total_over_divisor— 5_000 / 50 = 100per_author_cap_floor_is_one—5 / 50 = 0floors to 1per_author_cap_warns_once_per_author— bookkeeping HashSetper_author_cap_frees_on_resolve— slots freed on resolveadapted to use explicit sub-cap so they keep testing global policy:
pending_buffer_auto_evicts_when_limit_exceededcapacity_eviction_drops_oldest_when_exceededpending_count_reflects_both_eviction_policiespending_buffer_eviction_reduces_count_to_capdeep_pending_chain_does_not_stack_overflowchain shortened 3000 -> 1500 to stay under per-author cap of 2000. iterative-resolution coverage unchanged.verification
local (no
juston the box, ran equivalents):cargo fmt --check-> cleancargo clippy --workspace --all-targets -- -D warnings-> cleancargo test --workspace-> all pass222 passed; 0 failed281 passed; 0 failedcargo check --target wasm32-unknown-unknown(state, identity, messaging, crypto, transport, common, network, client, web) -> cleanRefs #237
https://claude.ai/code/session_01VdcwSdvqig423A5CX2fSzR
Generated by Claude Code