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:
Jakub Zych
2026-10-23 15:59:54 +02:00
parent c1c0c77ddc
commit 240457291f
10 changed files with 217 additions and 39 deletions

View File

@@ -236,9 +236,13 @@ var ErrMCPTokenInvalid = errors.New("pact: mcp token invalid")
// MCPPersonalTokenResolver resolves a personal API token (a frontend user
// token; the admin's g15_ tokens are resolved by the framework) into an MCP
// subject. The boolean is false when the token's prefix is not the
// resolver's, so the next resolver is tried; an error means the token has
// this resolver's prefix but is not usable.
// subject. An application has one resolver, published through app.Lookup
// by the plugin that owns the frontend tokens. The boolean is false when
// the token's prefix is not the resolver's, and the endpoint then answers
// unauthorized; an error means the token has this resolver's prefix but is
// not usable. The subject's UserID and TokenID must both be set: TokenID
// identifies the token row and keys the session binding
// (pat:user:<TokenID>), so a subject without a TokenID is refused.
type MCPPersonalTokenResolver interface {
MCPResolvePersonalToken(ctx context.Context, raw string) (MCPSubject, bool, error)
}