protocol

package
v1.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func WithBus(b events.EventBus) Option

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

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger the Service logs admin actions and audit-emit failures to. A nil logger routes to slog.Default().

func WithRedactor

func WithRedactor(r audit.Redactor) Option

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

func NewService(projector Projector, opts ...Option) (*Service, error)

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

Inspect implements the `sessions.inspect` method — the full per-session snapshot the Console Sessions detail view renders.

func (*Service) List

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL