Skip to content

feat(session): TTL for session self-exemptions (default 30m)#56

Merged
i8ramin merged 1 commit into
mainfrom
feat/session-exemption-ttl
Jul 14, 2026
Merged

feat(session): TTL for session self-exemptions (default 30m)#56
i8ramin merged 1 commit into
mainfrom
feat/session-exemption-ttl

Conversation

@i8ramin

@i8ramin i8ramin commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

First of two PRs for item 3 of #52. This one bounds the exposure; the precise fix (Option C) follows in a second PR.

Problem

Session-scoped self-exemptions (selfExemptionScope: 'session') never expired. pruneExpiredSessions pruned state.sessions only, so a host's ExemptionRecord lived for the whole lifetime of the session state. That is an unbounded window for a single self-click grant, and it is the mechanism behind item 3: a later same-network click on an already-exempted host stays suppressed indefinitely.

Change

  • New sessionExemptionTtlMs option on StanddownSession, default 30 minutes, fixed from grant (not sliding). Threaded through the content / webext / url adapters the same way selfExemptionScope is.
  • ExemptionRecord gains expiresAt (set to grantedAt + ttl when the TTL is positive).
  • pruneExpiredSessionspruneExpiredState, now dropping lapsed exemptions alongside expired sessions. Runs on every ingest / shouldStandDown / recordActivity.
  • Set the TTL to 0 (or any non-positive value) to disable expiry and keep the previous lifetime behavior.

Defaults I chose (flag if you'd prefer otherwise)

  • 30 min default: long enough for a normal shopping session, short enough to bound a stale grant. Mirrors the CJ-style session-or-min minimum-duration ballpark.
  • Fixed-from-grant, not sliding: bounds the total window rather than letting repeated self-touches extend it indefinitely (safer direction).
  • Configurable + disable via 0: adopters who deliberately want the old lifetime semantics can opt back in explicitly.

Tests

  • Default TTL: our own click self-exempts and records the grant; a competitor's CJ click inside the window is still suppressed (documented within-window behavior); past 30 min the same click stands down.
  • TTL disabled (0): the exemption still applies hours later.

Full suite green (165 tests, +2), typecheck / lint / citations / build clean. No behavior change for selfExemptionScope: 'policy' (the default scope).

Note

This does not by itself fix the competitor-click case within the window. That is Option C in the write-up on #52, coming as a follow-up PR, and it changes documented ignore_param-parity behavior so it is scoped separately.

Session-scoped self-exemptions (`selfExemptionScope: 'session'`) never expired:
pruneExpiredSessions only pruned `state.sessions`, so a host exemption lived for
the lifetime of the session state. That is an unbounded blast radius for a
grant, and the sharp edge behind #52 item 3 (a later same-network click on an
already-exempted host stays suppressed indefinitely).

Add `sessionExemptionTtlMs` (default 30 minutes, fixed from grant, not sliding)
on the session and thread it through the content/webext/url adapters.
ExemptionRecord gains an `expiresAt`; pruneExpiredState (renamed from
pruneExpiredSessions) now drops lapsed exemptions alongside expired sessions.
Set the TTL to 0 (or any non-positive value) to disable expiry and keep the old
lifetime behavior.

This bounds the window from item 3; Option C (narrowing what a live exemption
suppresses) is the precise fix and lands separately.

Addresses item 3 of #52 (bound). Part of #52.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXZmuMFkq8dE2e2KLXvitx
@i8ramin
i8ramin merged commit 35233d3 into main Jul 14, 2026
2 checks passed
@i8ramin
i8ramin deleted the feat/session-exemption-ttl branch July 14, 2026 19:53
i8ramin added a commit that referenced this pull request Jul 14, 2026
…mpetitor click

The TTL tests (#56) used a competing same-network click to show the TTL
boundary. #57 (Option C) landed in parallel and now stands down on a competing
attribution param, so the within-window "still suppressed" assertion broke once
both merged (each PR was green against a base without the other). Switch the TTL
demonstration to the lingering-cookie signal, which a live exemption still
re-attributes to us under Option C, so the test isolates the TTL boundary
cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXZmuMFkq8dE2e2KLXvitx
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.

1 participant