feat(v0.6): route lease-freshness callsites through IsStale

InspectLease, CleanupStaleLease, runActiveLeaseCheck, and statusAt now
use the PID-aware IsStale predicate. A dead local owner PID makes a lease
stale immediately; SessionStatus / list / info reflect this with no
display-code change.

Corrects three cmd-package session-display test fixtures that built
"active" leases with the local hostname but synthetic PIDs — now that
freshness is PID-aware, an active session must be owned by a live
process, so the fixtures use os.Getpid().
This commit is contained in:
2026-05-15 14:29:50 -04:00
parent f379a6d059
commit d575ddd0f5
7 changed files with 70 additions and 10 deletions
+45
View File
@@ -0,0 +1,45 @@
package session
import (
"testing"
"time"
)
// A fresh-by-wall-clock local lease whose owner PID is dead must be
// classified LeaseStateStale by InspectLease, so the persistent-mode
// dispatcher routes to adoption immediately rather than after the 60s
// wall-clock wait. (dispatchPersistent itself lives in cmd/ and is
// covered there; this test pins the InspectLease half of the contract.)
func TestInspectLeaseDeadLocalPIDIsStale(t *testing.T) {
withCheckProcess(t, func(int) ProcessState { return ProcessDead })
ws := t.TempDir()
now := time.Now().UTC()
writeLeaseAt(t, ws, &Lease{
SessionID: "test",
PID: 4242,
Hostname: currentHostname(),
LastHeartbeatAt: now, // fresh by wall-clock
StartedAt: now,
})
if got := InspectLease(ws); got != LeaseStateStale {
t.Errorf("dead-PID local lease: InspectLease = %v, want LeaseStateStale", got)
}
}
// The control case: a fresh local lease with a live PID stays FreshLocal,
// so passive reattach (not adoption) is chosen.
func TestInspectLeaseLiveLocalPIDIsFreshLocal(t *testing.T) {
withCheckProcess(t, func(int) ProcessState { return ProcessAlive })
ws := t.TempDir()
now := time.Now().UTC()
writeLeaseAt(t, ws, &Lease{
SessionID: "test",
PID: 4242,
Hostname: currentHostname(),
LastHeartbeatAt: now,
StartedAt: now,
})
if got := InspectLease(ws); got != LeaseStateFreshLocal {
t.Errorf("live-PID local lease: InspectLease = %v, want LeaseStateFreshLocal", got)
}
}
+1 -1
View File
@@ -189,7 +189,7 @@ func CleanupStaleLease(path string, staleAfter time.Duration) (*Lease, error) {
}
return nil, nil
}
if IsFresh(&l, time.Now(), staleAfter) {
if !IsStale(&l, time.Now(), staleAfter) {
return nil, nil
}
if rmErr := os.Remove(path); rmErr != nil && !errors.Is(rmErr, os.ErrNotExist) {
+1 -1
View File
@@ -56,7 +56,7 @@ func InspectLease(wsDir string) LeaseState {
if l == nil {
return LeaseStateNone
}
if !IsFresh(l, time.Now(), StaleLeaseAfter) {
if IsStale(l, time.Now(), StaleLeaseAfter) {
return LeaseStateStale
}
if l.Hostname != currentHostname() {
+1 -1
View File
@@ -101,7 +101,7 @@ func runActiveLeaseCheck(opts PreflightOpts) (bool, bool, error) {
existing, err := ReadLease(leasePath)
switch {
case err == nil && existing != nil:
if IsFresh(existing, time.Now(), StaleLeaseAfter) {
if !IsStale(existing, time.Now(), StaleLeaseAfter) {
if opts.Force {
return true, true, nil
}
+1 -1
View File
@@ -74,7 +74,7 @@ func statusAt(wsDir string, now time.Time) Status {
}
state := SessionStateStale
if IsFresh(&l, now, StaleLeaseAfter) {
if !IsStale(&l, now, StaleLeaseAfter) {
state = SessionStateActive
}