fix(pier): widen returning identities, warm the Redis store, end stateless listen streams, refuse personal tokens without a TokenID
- Identity.fingerprint covers scopes, both subjects, the ceiling and the token kind; a stateful lookup or stateless hit whose fingerprint changed re-filters the server with the event diff under its own mutex (A-WR-04) - Mount warms a Redis store in the background with a 1 s to 30 s backoff and every request kicks a pending open without waiting, so stateless-only instances subscribe to fan-out (A-WR-05) - a stateless subscriptions/listen POST re-verifies its bearer every recheck interval; an LRU-evicted stateless server closes its sessions (A-WR-06) - resolvePersonalToken refuses a subject with TokenID 0, and the pact.MCPPersonalTokenResolver contract says one resolver per application, false means unauthorized and TokenID keys the binding (A-WR-09, A-IN-03)
This commit is contained in:
@@ -232,10 +232,12 @@ func (s *redisStore) Subscribe(ctx context.Context, fn func(Event)) error {
|
||||
// (route:list and tests never dial) and a missing redis.url is a 503 on the
|
||||
// first request, logged once, not a boot failure (cooler's rule).
|
||||
type lazyStore struct {
|
||||
open func(ctx context.Context) (Store, error)
|
||||
mu sync.Mutex
|
||||
store Store
|
||||
warned atomic.Bool
|
||||
open func(ctx context.Context) (Store, error)
|
||||
mu sync.Mutex
|
||||
store Store
|
||||
warned atomic.Bool
|
||||
ready atomic.Bool // the store is open
|
||||
opening atomic.Bool // a kick is opening it
|
||||
}
|
||||
|
||||
// get returns the store, opening it on the first call and retrying on a
|
||||
@@ -254,9 +256,43 @@ func (l *lazyStore) get(ctx context.Context) (Store, error) {
|
||||
return nil, err
|
||||
}
|
||||
l.store = s
|
||||
l.ready.Store(true)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// kick opens the store in the background when it is not open yet and no
|
||||
// other kick is opening it; the caller never waits.
|
||||
func (l *lazyStore) kick() {
|
||||
if l.ready.Load() || !l.opening.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer l.opening.Store(false)
|
||||
_, _ = l.get(context.Background())
|
||||
}()
|
||||
}
|
||||
|
||||
// warm opens the store at mount in the background, retrying with a backoff
|
||||
// from one second doubling to thirty, so the event subscription exists
|
||||
// before traffic arrives (A-WR-05). It returns once the store is open or
|
||||
// ctx ends; each failure is logged once per backoff step.
|
||||
func (l *lazyStore) warm(ctx context.Context) {
|
||||
backoff := time.Second
|
||||
for {
|
||||
if _, err := l.get(ctx); err == nil {
|
||||
return
|
||||
} else if ctx.Err() == nil {
|
||||
logger().Warn("pier: session store not open yet, retrying", "retry_in", backoff.String(), "error", err)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(backoff):
|
||||
}
|
||||
backoff = min(backoff*2, 30*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lazyStore) Put(ctx context.Context, id string, rec Record, ttl time.Duration) error {
|
||||
s, err := l.get(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user