Summary
Add a Session module implemented over the existing bounded SQLite pool. The browser receives only an opaque bearer identifier; application session data remains in SQLite.
Illustrative API:
store = Session.init!({
db,
max_payload_bytes: 16 * 1024,
idle_timeout_seconds: 30 * 60,
absolute_timeout_seconds: 24 * 60 * 60,
})?
created = Session.create!({ store, now, payload })?
loaded = Session.load!({ store, now, id })?
rotated = Session.rotate!({ store, now, id })?
Session.invalidate!({ store, id })?
Session.prune!({ store, now, max_rows: 500 })?
Dependencies
Why this belongs in the platform
Server-side sessions are a conventional web framework facility and fit the platform's explicit preference for durable domain state in SQLite rather than arbitrary mutable process-local state.
The existing Sqlite.Db already provides bounded pooling, timeouts, typed errors, and transactions. The session facility can therefore be a Roc module over SQLite rather than a new host subsystem.
Required contract
- Depend on
Random.bytes! and the typed cookie module.
- Generate identifiers from at least 32 OS-random bytes.
- Encode them as a fixed, validated cookie-safe representation.
- Make the identifier opaque and redact it from normal inspection.
- Store only a one-way digest of the bearer identifier in SQLite.
- Store application data as a bounded
List(U8) payload; serialization is application policy.
- Use a fixed, namespaced, versioned table schema.
- Schema creation or migration occurs only through an explicit
Session.init! call.
- Enforce idle and absolute expiry server-side.
- Atomically rotate the identifier and invalidate the old identifier to prevent session fixation.
- Support explicit invalidation/logout.
- Provide bounded pruning rather than a detached cleanup worker.
- Use SQLite transactions for each lifecycle operation.
- Document that session operations are not automatically atomic with arbitrary application-table updates in the first version.
- Return typed missing, expired, malformed, saturated, timeout, and SQLite failures.
- Never use an in-memory session cache as the source of truth.
The cookie helper should be used to emit an explicitly configured Secure, HttpOnly, SameSite cookie. The session module should not guess whether the public deployment uses HTTPS.
Tests
- Create, load, update, rotate, invalidate, and expire.
- Idle versus absolute expiry.
- Concurrent loads, rotations, and invalidations.
- Old identifier unusable immediately after rotation.
- Payload exact-limit and over-limit behavior.
- Bounded prune batches.
- SQLite busy, timeout, and rollback paths.
- Restart and multi-process behavior against the same database.
- No identifier values in diagnostics.
Non-goals
- Users, authentication, roles, or authorization
- In-memory sessions
- A generic key/value map with hidden serialization
- Client-side encrypted session cookies
- Background cleanup jobs
- Automatic session creation on every request
- Flash messages or login redirects
Acceptance criteria
References
Summary
Add a
Sessionmodule implemented over the existing bounded SQLite pool. The browser receives only an opaque bearer identifier; application session data remains in SQLite.Illustrative API:
Dependencies
Set-CookierenderingWhy this belongs in the platform
Server-side sessions are a conventional web framework facility and fit the platform's explicit preference for durable domain state in SQLite rather than arbitrary mutable process-local state.
The existing
Sqlite.Dbalready provides bounded pooling, timeouts, typed errors, and transactions. The session facility can therefore be a Roc module over SQLite rather than a new host subsystem.Required contract
Random.bytes!and the typed cookie module.List(U8)payload; serialization is application policy.Session.init!call.The cookie helper should be used to emit an explicitly configured
Secure,HttpOnly,SameSitecookie. The session module should not guess whether the public deployment uses HTTPS.Tests
Non-goals
Acceptance criteria
References