Documentation
¶
Overview ¶
Package protocol implements the two `sessions.*` Protocol methods the Console Sessions page (Phase 73c / D-122) consumes:
- sessions.list — paginated, filtered SessionRegistry projection.
- sessions.inspect — full per-session snapshot for the detail view.
The seam (CLAUDE.md §4.4) ¶
The Service depends on the `Projector` interface, not on a concrete session registry. The V1 production implementation is `ListerProjector` (lister_projector.go) — a thin read-only projection over a `sessions.SessionLister`. A future remote / cross-runtime projector slots in behind the same interface without reshaping the Service.
Identity is mandatory (CLAUDE.md §6 rule 9) ¶
Every method takes the wire request's `IdentityScope`. An incomplete triple fails closed with `ErrIdentityRequired` — there is no identity-downgrading knob. The Service NEVER reads identity from a package-level global; the triple flows in via the request.
Cross-tenant gating (D-079) ¶
A `sessions.list` whose `Filter.TenantIDs` names a tenant other than the caller's verified tenant requires the verified `auth.ScopeAdmin` claim. The Service receives an `adminScoped bool` the wire handler computes from the verified JWT scope set; a false value on a cross-tenant filter fails closed with `ErrCrossTenantScope`. There is NO `sessions.admin` scope — the closed two-scope set (`admin` + `console:fleet`) is the only admit surface (D-079). On a successful admin-scope query the Service emits an `audit.admin_scope_used` event.
Concurrent reuse (D-025) ¶
A constructed *Service is immutable after NewService and safe to share across N concurrent goroutines: it holds only the Projector reference + an optional bus + redactor + logger; every method's per-call state lives in the call's arguments and locals, never on the Service.
Index ¶
- Variables
- type ListerProjector
- func (p *ListerProjector) InspectSession(ctx context.Context, id identity.Identity, sessionID string, adminScoped bool) (prototypes.SessionsInspectResponse, error)
- func (p *ListerProjector) ListSessions(ctx context.Context, id identity.Identity, f prototypes.SessionFilter, ...) ([]prototypes.SessionRow, error)
- type Option
- type Projector
- type Service
- type SessionsAdminQueryPayload
Constants ¶
This section is empty.
Variables ¶
var ( // ErrIdentityRequired — the request carried an incomplete identity // triple. RFC §5.5 / CLAUDE.md §6 rule 9 — fails closed. ErrIdentityRequired = errors.New("sessions/protocol: identity scope incomplete") // ErrCrossTenantScope — a `sessions.list` filter named a tenant // outside the caller's verified tenant without the verified // `auth.ScopeAdmin` claim (D-079). ErrCrossTenantScope = errors.New("sessions/protocol: cross-tenant filter requires the admin scope claim") // ErrInvalidRequest — the request was structurally invalid (an // out-of-range limit, an unknown enum, a malformed cursor). ErrInvalidRequest = errors.New("sessions/protocol: invalid request") // ErrSessionNotFound — `sessions.inspect` targeted a session id with // no record visible to the caller's identity scope. ErrSessionNotFound = errors.New("sessions/protocol: session not found") // ErrMisconfigured — NewService was called with a nil Projector. ErrMisconfigured = errors.New("sessions/protocol: NewService missing a mandatory dependency") )
Sentinel errors the Service returns. The wire handler maps each onto a canonical Protocol Code + HTTP status; in-process callers compare with errors.Is.
Functions ¶
This section is empty.
Types ¶
type ListerProjector ¶
type ListerProjector struct {
// contains filtered or unexported fields
}
ListerProjector is the V1 production Projector — a thin read-only projection over a `sessions.SessionLister` (the Phase 08 Registry's `ListSnapshots` surface). It maps the runtime `sessions.SessionSnapshot` onto the flat Protocol `SessionRow` wire shape (RFC §5.1 single-source rule: the Console never reads `sessions.Session`).
Identity scoping (CLAUDE.md §6) ¶
ListSessions builds the `sessions.SessionListFilter` so the registry scopes by tenant: a non-admin caller is restricted to its own `(tenant, user)`; an admin caller MAY widen via the request's `TenantIDs`. The registry's `ListSnapshots` does NOT re-check scope — the gate is the Service's `ErrCrossTenantScope` check; this projector only translates the gate decision into the filter shape.
Concurrent reuse (D-025) ¶
A constructed *ListerProjector is immutable after NewListerProjector and safe to share across N concurrent goroutines — it holds only the SessionLister reference; every method's per-call state lives in the call's arguments and locals.
func NewListerProjector ¶
func NewListerProjector(lister sessions.SessionLister) (*ListerProjector, error)
NewListerProjector builds the V1 Projector over a SessionLister. The lister is mandatory — a nil fails loud rather than building a projector that nil-panics on the first request (CLAUDE.md §5).
func (*ListerProjector) InspectSession ¶
func (p *ListerProjector) InspectSession(ctx context.Context, id identity.Identity, sessionID string, adminScoped bool) (prototypes.SessionsInspectResponse, error)
InspectSession implements Projector.InspectSession. It lists the one session id and projects the snapshot plus the (currently empty) recent-interventions / recent-artifacts slices.
V1 scope note: the recent-interventions / recent-artifacts cards are fed by the Console's own event-stream subscription on the detail route (the page consumes `pause.*` / `artifacts.*` events filtered to the session — page spec §5). `sessions.inspect` ships the Row projection + empty capped slices; the cards populate from the live event stream client-side. A future StateStore-backed enrichment can pre-fill the slices without a wire-shape break (the fields are already on the response).
func (*ListerProjector) ListSessions ¶
func (p *ListerProjector) ListSessions(ctx context.Context, id identity.Identity, f prototypes.SessionFilter, adminScoped bool) ([]prototypes.SessionRow, error)
ListSessions implements Projector.ListSessions. It builds the identity-scoped registry filter, lists the snapshots, and projects each onto a SessionRow.
type Option ¶
type Option func(*Service)
Option configures NewService.
func WithBus ¶
WithBus wires the canonical events.EventBus the Service publishes the `audit.admin_scope_used` event onto when an admin-scope query succeeds. A nil bus is treated as "WithBus not supplied" — the admin path still works, but the audit observation is logged at Info instead of published (the admin action is NEVER fully silent — CLAUDE.md §13).
func WithLogger ¶
WithLogger sets the slog.Logger the Service logs admin actions and audit-emit failures to. A nil logger routes to slog.Default().
func WithRedactor ¶
WithRedactor wires the audit.Redactor the Service runs the `audit.admin_scope_used` payload through before publishing. A nil redactor is treated as "WithRedactor not supplied".
type Projector ¶
type Projector interface {
// ListSessions returns every session row visible to the caller,
// already identity-scoped: when adminScoped is false the
// implementation MUST restrict to the caller's own (tenant, user);
// when true it MAY honour a cross-tenant TenantIDs filter. The
// Service applies the facet filter + sort + pagination on top.
ListSessions(ctx context.Context, id identity.Identity, f prototypes.SessionFilter, adminScoped bool) ([]prototypes.SessionRow, error)
// InspectSession returns the full snapshot for sessionID, or
// ErrSessionNotFound. adminScoped widens the lookup across tenants.
InspectSession(ctx context.Context, id identity.Identity, sessionID string, adminScoped bool) (prototypes.SessionsInspectResponse, error)
}
Projector is the read seam the Service depends on. The V1 production implementation is ListerProjector. Every method takes the verified identity triple plus the resolved admin-scope flag so the implementation scopes its reads.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements the two `sessions.*` Protocol methods. It is a D-025-safe compiled artifact — immutable after NewService.
func NewService ¶
NewService builds the Sessions Protocol service over a Projector. The projector is mandatory — a nil fails loud with ErrMisconfigured rather than building a Service that would nil-panic on the first request (CLAUDE.md §5). The returned *Service is immutable after construction (D-025) and safe for concurrent use by N goroutines.
func (*Service) Inspect ¶
func (s *Service) Inspect(ctx context.Context, req prototypes.SessionsInspectRequest, adminScoped bool) (prototypes.SessionsInspectResponse, error)
Inspect implements the `sessions.inspect` method — the full per-session snapshot the Console Sessions detail view renders.
func (*Service) List ¶
func (s *Service) List(ctx context.Context, req prototypes.SessionsListRequest, adminScoped bool) (prototypes.SessionsListResponse, error)
List implements the `sessions.list` method. It validates identity, enforces the D-079 cross-tenant gate, resolves the identity-scoped rows from the Projector, applies the facet filter + sort + cursor pagination, and emits an `audit.admin_scope_used` event on a successful admin-scope query.
type SessionsAdminQueryPayload ¶
type SessionsAdminQueryPayload struct {
events.SafeSealed
// Actor is the verified admin identity at the Protocol edge — the
// (tenant, user, session) triple the JWT carried.
Actor identity.Identity
// Method is the Protocol method that carried the cross-tenant query
// (`sessions.list` or `sessions.inspect`).
Method string
}
SessionsAdminQueryPayload is the typed SafePayload published on the canonical `audit.admin_scope_used` event when an operator runs a cross-tenant `sessions.list` / `sessions.inspect` query under the verified `auth.ScopeAdmin` claim. Phase 73c / D-122.
SafePayload by construction: every field is a bounded identity component or a Protocol method name — no caller-supplied bytes reach the bus. The Sessions wire surface rejects malformed requests at the Protocol edge before the emit.
The payload is distinct from `auth.AdminScopeUsedPayload` (Phase 72b impersonation), `events.AdminScopeUsedPayload` (Phase 05 Subscribe), and `toolsprotocol.ToolsAdminActionPayload` (Phase 73f) — all ride the same canonical `audit.admin_scope_used` event type, but each emit source declares its own typed payload. A subscriber type-switches.