fix(pier): reap stateful sessions whose store record expired

- a reaper started at Mount checks every local session against the store
  every min(idle_ttl/2, 1m) and closes each copy whose record is gone,
  logging the expired step (A-CR-01, A-IN-06)
- the GET stream re-check ends the stream when the record is gone (A-IN-01)
- tool calls served without a session on the stateful path log the
  session as ephemeral (A-IN-06)
- TestPierReaperSmoke and the ReapNow, LocalSessions, StopReaper seams
This commit is contained in:
Jakub Zych
2026-10-23 15:50:04 +02:00
parent d18d8f0dc2
commit 2bf9dca603
6 changed files with 190 additions and 8 deletions

View File

@@ -22,11 +22,15 @@ func logger() *slog.Logger {
}
// sessionPrefix shortens a session id for logging; a stateless call logs
// "stateless".
// "stateless" and a call served without a session on the stateful path
// "ephemeral".
func sessionPrefix(id string) string {
if id == "" {
return stateless
}
if id == ephemeralSession {
return ephemeralSession
}
if len(id) > sessionPrefixLen {
return id[:sessionPrefixLen]
}
@@ -63,9 +67,12 @@ func logToolCall(log *slog.Logger, id Identity, t *tool, session string, status
)
}
// logSession writes one line per session lifecycle step (open, rehydrate,
// end, binding_refused, expired) with the session prefix and the binding,
// never the token.
// logSession writes one line per session lifecycle step with the session
// prefix and the binding, never the token. The steps: open (serveNew),
// rehydrate (lookup), end (DELETE), binding_refused (another binding
// presented the id), revoked (a revocation or deletion event),
// stream_closed (a GET stream whose bearer or record is gone) and expired
// (the reaper closed a local copy whose record expired).
func logSession(log *slog.Logger, step, session, binding string) {
log.Info("pier: session", "step", step, "session", sessionPrefix(session), "binding", binding)
}