Skip to content

test: property-check SessionAffinityStore TTL, eviction, and reindex contracts#594

Open
ndycode wants to merge 2 commits into
mainfrom
claude/audit-75-session-affinity-property
Open

test: property-check SessionAffinityStore TTL, eviction, and reindex contracts#594
ndycode wants to merge 2 commits into
mainfrom
claude/audit-75-session-affinity-property

Conversation

@ndycode

@ndycode ndycode commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

What Changed

New test/property/session-affinity.property.test.ts (5 properties, real store driven through its injectable now parameters — no fake timers needed):

  1. Model-based TTL/upsert equivalence — for any interleaving of remember/update-response/forget/advance events, routed through whitespace-decorated key spellings, getPreferredAccountIndex and getLastResponseId match a trivial TTL map. This pins the non-obvious upsert rules: remember preserves the prior continuation id, response-id writes refresh the session's TTL, and updateLastResponseId never creates entries. (Eviction is excluded here via a large maxEntries so the model stays exact; capacity is its own property.)
  2. Capacitysize() never exceeds maxEntries under any write stream, and LRU eviction (oldest updatedAt) can never evict the entry that was just written.
  3. Write-version conflicts — a stale writeVersion loses to a live entry on both the account-index and response-id channels, but the same stale version may rebind the session once the entry expires.
  4. Account-removal spliceforgetAccount(removed) + reindexAfterRemoval(removed) produce exactly the mapping of an account-array splice (equal indices dropped, higher indices decremented, lower untouched), with both methods' return counts pinned against the model.
  5. Prune exactnessprune(now) removes exactly the expired entries and is invisible to readers. The subtle case fast-check itself surfaced: sessions touched by updateLastResponseId while expired are lazily reaped on access, so they must not count as prunable — my initial model missed that and fast-check produced the 8-event counterexample on run 48; the model now mirrors the lazy reap.

No SUT bugs found; the store's documented contract held everywhere.

Validation

  • npm test -- test/property/session-affinity.property.test.ts test/session-affinity.test.ts test/session-affinity-entry.test.ts test/issue-474-affinity-invalidation.test.ts — 43/43 (new 5 + all existing affinity suites untouched)
  • npm run typecheck (also via pre-commit hook)
  • npx eslint test/property/session-affinity.property.test.ts --max-warnings=0

Docs and Governance Checklist

  • Test-only; no behavior or docs surface changed

Risk and Rollback

  • Risk level: minimal — additive test file; conventions match the existing test/property/ suites (explicit vitest imports, plain fc.assert).
  • Rollback plan: revert the single commit.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB


Generated by Claude Code

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

adds test/property/session-affinity.property.test.ts, a 6-property fast-check suite for SessionAffinityStore driven through injectable now — no fake timers required. it addresses the clearAll coverage gap from the previous review round and fixes the model-divergence bug where lazy reaping during assertions caused responseId to drift.

  • property 1 (TTL model): routes all writes through whitespace-decorated keys and uses liveModel (not model.get) so the model correctly reflects lazy reaping after advance events — the regression fix from round 1.
  • properties 2–5: cap/LRU, stale-version conflicts, account-array splice, and prune exactness all use injected now and stay in sync with the SUT's internal eviction semantics.
  • property 6 (clearAll): closes the gap called out in the prior round; confirms size() === 0 immediately after and that the store accepts new writes without state corruption.

Confidence Score: 5/5

additive test-only file; no production code is touched, and all 6 properties correctly model the SUT's documented contracts.

the model-divergence fix (using liveModel instead of model.get in the remember branch) correctly handles lazy reaping during the assertion loop. the clearAll property closes the gap from the prior review round. the only open item is a narrow test-coverage gap in property 3 — the stale response-id channel test only exercises the null-baseline case, leaving the non-null preservation path untested — but no SUT bug is present.

test/property/session-affinity.property.test.ts — property 3's response-id stale-overwrite sub-test could be strengthened to catch regressions where a rejected write silently clears an existing response id.

Important Files Changed

Filename Overview
test/property/session-affinity.property.test.ts adds 6 fast-check properties covering TTL model equivalence, LRU capacity, stale-version conflicts, splice reindex, prune exactness, and clearAll invalidation; all logic is sound with one minor test-coverage gap in property 3's response-id channel

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[fc.assert arbSequence] --> B{event.kind}
    B -->|advance| C[now += ms]
    B -->|remember| D[store.remember\nspell key]
    B -->|updateResponse| E[store.updateLastResponseId\nspell key]
    B -->|forget| F[store.forgetSession\nspell key]

    D --> G[liveModel check\npreserve responseId only\nif entry still live]
    E --> H{entry live\nin model?}
    H -->|yes| I[model: update responseId\n+ refresh expiresAt]
    H -->|no| J[model: no-op\nlazy reap on next access]

    C --> K[assertion block\nafter every event]
    D --> K
    E --> K
    F --> K

    K --> L[for each KEYS\nliveModel vs\nstore.getPreferredAccountIndex\nstore.getLastResponseId]
    L --> M{match?}
    M -->|yes| N[continue]
    M -->|no| O[fast-check\ncounterexample]
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
test/property/session-affinity.property.test.ts:159-184
**response-id channel only tests null → null, misses non-null preservation**

the comment on line 171 claims the stale write loses on "both channels," but the response-id sub-test only checks that a stale `updateLastResponseId` can't set a value that was never there (`null → attempt → still null`). the `"resp-fresh" → stale attempt → still "resp-fresh"` path is never exercised — a regression where the rejected write path clobbers an existing id (e.g., clears `lastResponseId` to `undefined` before returning) would pass. to close the gap, call `store.updateLastResponseId("session", "resp-fresh", T0, freshVersion)` before the stale attempt and assert `getLastResponseId` still returns `"resp-fresh"` afterward.

Reviews (2): Last reviewed commit: "test: fix remember model after lazy reap..." | Re-trigger Greptile

…contracts

Five fast-check properties over the real store using its injectable
now parameters (1s TTL floor so sequences cross expiry often):

- model-based TTL/upsert equivalence: for any remember/update/forget/
  advance interleaving through whitespace-decorated key spellings,
  getPreferredAccountIndex and getLastResponseId match a trivial
  TTL map (remember preserves the continuation id, response-id writes
  refresh expiry and never create entries)
- capacity: size() never exceeds maxEntries, and LRU eviction can
  never evict the entry just written
- write-version conflicts: a stale version loses to a live entry on
  both the index and response-id channels, but may rebind once the
  entry expires
- forgetAccount + reindexAfterRemoval mirror an account-array splice,
  with both return counts pinned against the model
- prune removes exactly the expired entries; lazily-reaped sessions
  (touched while expired) correctly do not count as prunable

The prune model initially missed that updateLastResponseId deletes an
expired entry outright; fast-check found the 8-event counterexample
and the model now mirrors the lazy reap.

Companion to #574/#575/#579/#592/#593; same conventions.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ndycode, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 12 minutes and 24 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d37e879a-f3d7-412c-bcf7-79e08ca371d5

📥 Commits

Reviewing files that changed from the base of the PR and between c255e14 and 912836e.

📒 Files selected for processing (1)
  • test/property/session-affinity.property.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/audit-75-session-affinity-property
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/audit-75-session-affinity-property

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread test/property/session-affinity.property.test.ts Outdated
Comment thread test/property/session-affinity.property.test.ts
Comment thread test/property/session-affinity.property.test.ts Outdated
…une keys

Greptile P1: the model carried a responseId across expiry, but the
assertion block's reads lazily reap expired entries from the store, so
a remember after expiry finds no existing entry and the id is gone -
the model now inherits the id only from a live entry (verified at
FAST_CHECK_NUM_RUNS=1000, where the original 4-event counterexample
sequence reproduces without the fix).

P2s: a sixth property pins clearAll (#474 invalidation) - size drops
to zero, every read goes null, and the store stays usable - and the
prune property now routes remember/forget/updateLastResponseId
through decorated key spellings like the model property does.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants