Skip to content

Sessions expire after 30 days unused, and stop storing IP address and user agent (#228) - #240

Merged
plainprogrammer merged 23 commits into
mainfrom
228-session-idle-expiry
Nov 9, 2026
Merged

plainprogrammer merged 23 commits into
mainfrom
228-session-idle-expiry

Conversation

@plainprogrammer

@plainprogrammer plainprogrammer commented Nov 9, 2026 •

Copy link
Copy Markdown
Owner

Closes #228. Refs #218 (rulings 1 and 2).

What changes

  • Sessions expire after 30 days unused. A session whose stamp (sessions.last_used_at) is 30 days old or more is no session: a collector's GET is redirected to sign-in with the page kept, a non-GET is redirected and changes nothing, an open catalog page is shown as to a visitor. The request writes nothing and the response removes the cookie. No absolute lifetime.
  • Use is recorded about once a day. Any request that resolves the session counts; it writes the stamp only when it is 24 hours old or more (or later than now), and sets the cookie again for 30 days. The cookie is no longer permanent.
  • A nightly job deletes expired sessions. Session::SweepJob (sweep_sessions, 4:10 am, development and production), batches of 1,000, one at a time (limits_concurrency), retries SQLite3::BusyException and ActiveRecord::StatementTimeout. The database cascades each session's bulk selection and Undo.
  • sessions.ip_address and user_agent are dropped, with their values. Three migrations (add the stamp, backfill it with the upgrade's time so nobody is signed out, drop the columns and make the stamp NOT NULL), all without a transaction, because SQLite rebuilds sessions and the rebuild would otherwise cascade-delete bulk selections and Undo records. A migration spec proves a session, its selection, mark and Undo survive down and up.
  • Spec 049 (approved at Gate A; plan approved at Gate B), the tenancy area doc (States, Named constants, Decisions, Accepted limits, shared Accounts and sessions: six behaviours no spec describes #218 lines), three lessons.

Ruling made during review

The daily stamp write on a GET could turn a request into a 500 if SQLite stays busy past its timeout. Authentication#record_use_of now rescues ActiveRecord::StatementTimeout and SQLite3::BusyException, logs one line and skips the stamp and the new cookie. It only skips the stamp: expiry is decided before it, so the sign-in decision is untouched. Two examples in spec/requests/session_expiry_spec.rb (red first, with the exception propagating).

Verification

  • Pre-push hook (the full bin/ci) passed on ee9ba43; the suite has 2,873 examples at the earlier head (bin/rspec --dry-run).
  • Implementation review (Mode B, Opus): SPEC-ALIGNED, recorded in docs/specs/049-session-idle-expiry/verification.md; its major finding is the ruling above, its test gaps are closed.
  • Export format: no change, no version change (sessions stays "Left out"); spec/backup_guards_spec.rb green.

CI and what is left

🤖 Generated with Claude Code

plainprogrammer and others added 23 commits November 9, 2026 11:27
…address and user agent (#228)

Draft for Gate A: decisions 1-5 ruled by the maintainer, 6-11 recommended.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ns no entry (#228)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…dress and user agent (#228)

Task 1: Migrations, schema, and the callers of the two columns

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Task 2: `Session`'s expiry rule

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… once a day (#228)

Task 3: An expired session is no session; use is recorded once a day

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Task 4: `Session.sweep!`, the job, and the schedule

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Task 5: The tenancy area doc, the index, the verification

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… session (#228)

The once-a-day stamp write in Authentication#record_use_of is bookkeeping,
and spec 049 decision 2 says a missed write is harmless: the stamp then lags
by under 24 hours more. A busy database (SQLite3::BusyException or
ActiveRecord::StatementTimeout) must therefore never fail the request.

The write now rescues both, logs one warning line and returns without
re-issuing the cookie; the next request tries again. Ruling by the
supervisor, from the implementation review's major risk.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… shows no collection signals (#228)

The AC-3.5 example now also expects ActiveRecord::StatementTimeout to be
retried (checked by removing it from retry_on: the example failed, and the
line was restored). The AC-1.4 example asserts the collector's account menu
("Sign out") is absent for the expired session and present for a fresh
sign-in in the same example (checked by disabling the expiry test: it fails).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…usy database (#228)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…a doc

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…c (spec 027 D4)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… 027)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	docs/architecture/tenancy-and-accounts.md
# Conflicts:
#	db/schema.rb
#	docs/architecture/tenancy-and-accounts.md
@plainprogrammer plainprogrammer self-assigned this Nov 9, 2026
@plainprogrammer
plainprogrammer marked this pull request as ready for review November 9, 2026 17:41
@plainprogrammer
plainprogrammer merged commit 57d35d3 into main Nov 9, 2026
9 checks passed
@plainprogrammer
plainprogrammer deleted the 228-session-idle-expiry branch November 9, 2026 17:51
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.

Sessions expire after 30 days unused, and stop storing IP address and user agent

1 participant