Documentation
¶
Overview ¶
Package remote is RFC 0003, the Agent Session Store Protocol: a store reached over HTTP, for a reader, a follower or a writer in another process or on another machine.
NewHandler serves a store the library offers. It publishes what the store supports at GET /v1/ and answers the rest with 501 `unsupported`, as RFC 0003's Conventions say of a store that lacks a feature:
GET /v1/ capabilities
GET /v1/sessions?…filter… list, NDJSON ending with end
POST /v1/sessions[?lease_ttl_ms=n] create, leased from its first instant
GET /v1/sessions/{id} header, head, length, incarnation, cursor
DELETE /v1/sessions/{id} delete, under the lease
GET /v1/sessions/{id}/path the path, NDJSON (contents=all, since-fold or none)
GET /v1/sessions/{id}/log the log's records after a cursor, NDJSON; durable=true
GET /v1/sessions/{id}/follow following, server-sent events; durable=true
POST /v1/sessions/{id}/lease acquire; PUT renews, DELETE releases
POST /v1/sessions/{id}/lease/break end another holder's lease (admin)
POST /v1/sessions/{id}/entries append, NDJSON lines, under the lease
POST /v1/sessions/{id}/commit commit, where the store has one
PUT /v1/sessions/{id}/head the head's compare-and-swap
GET /v1/refs?prefix= ref listing, NDJSON
GET /v1/refs/{name} resolve; PUT is the ref's compare-and-swap
GET /v1/refs/{name}?view=log the ref's log, NDJSON
GET /v1/refs/{name}?view=follow the ref's target as it moves, server-sent events
GET /v1/objects/{space}/{hash} an object; HEAD too; PUT uploads one
POST /v1/objects/missing which objects the caller cannot reach
POST /v1/sessions/{id}/push RFC 0002's push, admitted here
POST /v1/sessions/{id}/record declare this server the record
POST /v1/sessions/{id}/handover hand the session to a client store
Every route needs a Scope, and a request's principal must hold it. The package ships no authentication: WithAuthenticator takes the program's Authenticator, which resolves a request to a Principal and its scopes, and without one, or WithInsecureNoAuth, the handler refuses every request. Its 401s carry the WWW-Authenticate challenge the program names with WithChallenge, and none without it. A client's credentials are the program's too, added by the HTTP client WithHTTPClient gives it.
Every write to a session carries its lease's fencing token, and one whose lease is not the session's current one is refused, so a writer that lost its lease cannot write after the next holder. A push is not leased: it is governed by the record mark, as RFC 0002's exchange is, and the objects and exchange routes need a store with object spaces and marks (the cas store). The log read and durable=true need a store that keeps a log of records with a commit (the cas store); a shallow path is computed from the session, over any store.
One follower per session is shared by every client that follows it, and a client that falls behind is cut with an `overflow` error and resumes from the last event it read. A snapshot carries entries up to WithSnapshotLimit; past it a server with objects sends the records alone, which the client reads as objects, and one without refuses.
Dial returns a Client that is the library's agentsession.Store, agentsession.Writer, agentsession.Reader, agentsession.Follower and agentsession.RefStore over these calls. It verifies every entry it receives by its hash, treats a stream cut before its terminal frame as an error, never as a short result, and writes under leases it renews until it lets them go. Push sends a session a local cas store is the record for, uploading only the objects the server lacks: the helper of a local-first writer; Fetch reads one from the server's committed log into a local cas store, and TakeBack makes the local store the record again.
Index ¶
- Constants
- Variables
- func Fetch(ctx context.Context, local *cas.Store, c *Client, id string) (cas.Exchange, error)
- func Push(ctx context.Context, local *cas.Store, c *Client, id string, ...) (cas.Exchange, error)
- func TakeBack(ctx context.Context, local *cas.Store, c *Client, id string) (cas.Exchange, error)
- type Authenticator
- type Authorizer
- type Capabilities
- type Client
- func (c *Client) Append(ctx context.Context, sessionID string, e agentsession.Entry) (string, error)
- func (c *Client) Blob(ctx context.Context, hash string) ([]byte, error)
- func (c *Client) Capabilities(ctx context.Context) (Capabilities, error)
- func (c *Client) CheckPush(ctx context.Context, h agentsession.Header, entries, prefix []string) error
- func (c *Client) Close() error
- func (c *Client) Create(ctx context.Context, h agentsession.Header) (*agentsession.Session, error)
- func (c *Client) DeclareRecord(ctx context.Context, id, basis, head string) error
- func (c *Client) Delete(ctx context.Context, id string) error
- func (c *Client) Follow(ctx context.Context, id string, from agentsession.Cursor) iter.Seq2[agentsession.Change, error]
- func (c *Client) FollowCommitted(ctx context.Context, id string, from agentsession.Cursor) iter.Seq2[agentsession.Change, error]
- func (c *Client) FollowRef(ctx context.Context, name string) iter.Seq2[RefState, error]
- func (c *Client) HandOver(ctx context.Context, id, head string) error
- func (c *Client) Import(ctx context.Context, r io.Reader, asRecord bool) (*agentsession.Session, error)
- func (c *Client) ImportMedia(ctx context.Context, r io.Reader, media fs.FS, asRecord bool) (*agentsession.Session, []string, error)
- func (c *Client) List(ctx context.Context, f agentsession.ListFilter) iter.Seq2[agentsession.Summary, error]
- func (c *Client) ListRefs(ctx context.Context, prefix string) iter.Seq2[agentsession.Ref, error]
- func (c *Client) Log(ctx context.Context, id string, after agentsession.Cursor, durable bool) (Log, error)
- func (c *Client) Missing(ctx context.Context, o Objects) (Objects, error)
- func (c *Client) Object(ctx context.Context, space, hash string) ([]byte, error)
- func (c *Client) Open(ctx context.Context, id string) (*agentsession.Session, error)
- func (c *Client) Path(ctx context.Context, id, leaf, contents string) (Path, error)
- func (c *Client) PutBlob(ctx context.Context, data []byte) (string, error)
- func (c *Client) PutObject(ctx context.Context, space, hash string, data []byte) error
- func (c *Client) Read(ctx context.Context, id string) (*agentsession.Session, error)
- func (c *Client) RefLog(ctx context.Context, name string) iter.Seq2[agentsession.RefUpdate, error]
- func (c *Client) Release(id string) error
- func (c *Client) ResolveRef(ctx context.Context, name string) (agentsession.RefTarget, error)
- func (c *Client) Session(ctx context.Context, id string) (SessionInfo, error)
- func (c *Client) SetHead(ctx context.Context, sessionID, expected, to string) error
- func (c *Client) UpdateRef(ctx context.Context, name string, expected, next agentsession.RefTarget, ...) error
- func (c *Client) Write(ctx context.Context, sessionID string, e agentsession.Entry) (agentsession.Result, error)
- type ClientOption
- type Features
- type Handler
- type Limits
- type Log
- type LogRecord
- type Objects
- type Option
- func WithAuthenticator(a Authenticator) Option
- func WithAuthorize(a Authorizer) Option
- func WithChallenge(value string) Option
- func WithErrorLog(fn func(error)) Option
- func WithFollowBuffer(n int) Option
- func WithFollowReplay(n int) Option
- func WithHeartbeat(d time.Duration) Option
- func WithInsecureNoAuth() Option
- func WithLeaseLimits(def, max time.Duration) Option
- func WithLinger(d time.Duration) Option
- func WithRefPoll(d time.Duration) Option
- func WithSnapshotLimit(n int64) Option
- func WithUploadGrace(d time.Duration) Option
- type Path
- type PathEntry
- type Principal
- type Problem
- type PushHead
- type PushRef
- type PushRefResult
- type PushResult
- type RefState
- type Scope
- type SessionInfo
- type Target
Constants ¶
const ( CodeNoSession = "no_session" CodeSessionReplaced = "session_replaced" CodeNoEntry = "no_entry" CodeNoRef = "no_ref" CodeRefDangling = "ref_dangling" CodeRefName = "ref_name" CodeSessionLocked = "session_locked" CodeFormat = "format" CodeReadOnly = "read_only" CodeUnsupported = "unsupported" CodeStoreFault = "store_fault" CodeAuth = "auth" CodeScope = "scope" CodeOverflow = "overflow" CodeBadRequest = "bad_request" // The write side's. CodeSessionExists = "session_exists" CodeHeadMoved = "head_moved" CodeHeadTarget = "head_target" CodeLeaseLost = "lease_lost" CodeMirror = "mirror" CodeCallIDRepeated = "call_id_repeated" CodeSuperseded = "superseded" CodeSynthetic = "synthetic" CodeBadEntry = "bad_entry" CodeBaseRule = "base_rule" CodeBadBase = "bad_base" CodeRefMoved = "ref_moved" CodeNotRedacted = "not_redacted" // Exchange's. CodeNoObject = "no_object" CodeBadObject = "bad_object" CodeMissingObject = "missing_object" CodeHeaderDiffers = "header_differs" CodeNotRecord = "not_record" CodeBadRefTarget = "bad_ref_target" // The remaining reads': a snapshot past the server's bound from a // server that serves no objects. CodeTooLarge = "too_large" )
The error codes of RFC 0003's Errors table this slice uses, and `overflow`, which RFC 0003's Following adds for a follow the server cut because it fell behind.
const Version = "/v1"
Version is the path prefix every route is under.
Variables ¶
var ( // ErrUnsupported is a feature the server's store lacks, or one this // slice of the protocol does not serve. ErrUnsupported = errors.New("remote: the server does not support this") // ErrUnauthenticated is a request no authenticator accepted. ErrUnauthenticated = errors.New("remote: unauthenticated") // ErrForbidden is a request from a principal without the scope it // needs. ErrForbidden = errors.New("remote: the principal lacks the scope this needs") // ErrSessionReplaced is a session another was created again under // the ID of, refused for an If-Incarnation that names the old one. ErrSessionReplaced = errors.New("remote: the session was replaced") // ErrOverflow is a follow the server cut because the client fell // behind. A Client resumes from its cursor by itself. ErrOverflow = errors.New("remote: the follow fell behind and was cut") // ErrCut is a stream that ended without its terminal frame: the // connection was lost or the server stopped, and what was read is // not the whole answer. ErrCut = errors.New("remote: the stream was cut") // ErrBadRequest is a request the server could not parse. ErrBadRequest = errors.New("remote: bad request") // ErrLeaseLost is a write whose lease is no longer the session's // current one: it expired, was released or another holder took the // session. A writer that wants to go on takes a lease again, reads // the log to learn whether its last write landed, and resumes. ErrLeaseLost = errors.New("remote: the lease is not the session's current one") // ErrHeadMoved is a head compare-and-swap whose expected head is not // the session's: [agentsession.ErrHeadMoved], as a local store // reports it. ErrHeadMoved = agentsession.ErrHeadMoved // ErrHeadTarget is a head move to a leaf label: // [agentsession.ErrHeadTarget], as a local store reports it. ErrHeadTarget = agentsession.ErrHeadTarget // ErrMirror is a write to a session the server holds as a mirror. ErrMirror = errors.New("remote: the session is a mirror on this server") // ErrSynthetic is a leaf label carrying synthetic. ErrSynthetic = errors.New("remote: a synthetic leaf label is a projection's") // ErrMissingObject is a push naming objects the server cannot find // for the caller: the problem's Missing says which. Upload them and // push again. ErrMissingObject = errors.New("remote: the push names objects the server lacks") // ErrBadRefTarget is a pushed ref whose next is not the pushed // session. ErrBadRefTarget = errors.New("remote: a pushed ref names another session") // ErrTooLarge is a snapshot past the server's bound, from a server // that serves no objects to read the rest by. ErrTooLarge = errors.New("remote: the session is past what the server sends in a snapshot") // ErrBadEntry is an entry line the server refused as malformed. A // problem whose reason names a library rule matches that rule's // sentinel too. ErrBadEntry = errors.New("remote: a malformed entry") )
Errors a Client call returns, matched with errors.Is, beside the library's own sentinels a problem carries (agentsession.ErrNoSession for `no_session`, and so on).
var AllScopes = []Scope{ScopeRead, ScopeWrite, ScopeRefs, ScopeExchange, ScopeAdmin}
AllScopes are every scope this server checks.
Functions ¶
func Fetch ¶ added in v0.0.23
Fetch takes a session from the server into a local cas store, as cas.Store.Fetch takes one from another store: it reads the server's committed log (a durable read, so nothing a crash of the server could take back), the entries the log names and the media their contents name as objects, and a fork's prefix as its path, and admits them as a fetch. A session the local store lacked is created as a mirror; the head moves on a mirror only to a head that descends from its own.
func Push ¶ added in v0.0.23
func Push(ctx context.Context, local *cas.Store, c *Client, id string, opts cas.PushOptions) (cas.Exchange, error)
Push sends a session the local store is the record for to the server, RFC 0002's push over RFC 0003: it asks which objects of the session's closure the server lacks for this caller, uploads those, and posts the push, which the server admits as a mirror, or as the record for a handover. It is the helper of a local-first writer, which records into its own store with no network in its loop and pushes at commits, on a timer and at shutdown. opts are as for cas.Store.Push, and the result is what the server did, as Push between two stores reports it. A handover ends with the local store a mirror, once the server has committed its mark; a failure between the two leaves two records, never none. A server whose store holds only redacted entries refuses a push of a session whose header does not carry redacted or that carries an entry without it (agentsession.ErrNotRedacted).
Push asks first, by the push's pre-flight (Client.CheckPush), so a refusal that needs no object, a header without redacted at such a server among them, comes before any object is uploaded. One that needs the objects, an entry without redacted under a redacted header, comes at the post, after the objects were uploaded, which stay in the server's store, named by no session, until a sweep; WithRedactedOnly has the client refuse before it sends anything.
func TakeBack ¶ added in v0.0.23
TakeBack makes a local cas store the record of a session the server is the record for: it fetches the session, hands the server's record over at the head it fetched, which leaves the server a mirror and is refused with ErrHeadMoved if a writer moved the head since (fetch and try again), and declares the local store the record at that head. The handover takes and ends a lease, so no writer at the server goes on after it. A failure after the handover leaves no record; the local store holds everything, and DeclareRecordAt on it finishes the move.
Types ¶
type Authenticator ¶
Authenticator resolves the principal a request is from. An error refuses the request as `auth`; its text is not sent.
The package ships none: it says which scope each route needs and refuses a request whose principal lacks it, and the program decides who a request is from, by whatever credentials it issues, and passes that decision to WithAuthenticator. An authenticator that finds no credentials it accepts returns ErrUnauthenticated, or any error.
type Authorizer ¶ added in v0.0.31
Authorizer decides whether the principal p may act with scope on the session whose ID is session. A nil error allows it; any error refuses it, answered as `no_session` so a refusal does not tell the caller the session exists. Its text is not sent.
A Handler with one asks it on every request that names a session: each route under /v1/sessions/{id}, a follow resumed from an event ID or `after` as much as a fresh one, an append, a lease, a push and a handover; a create, for the session it creates and, for a fork, the session it names as its origin, and its base, which must be reachable through a session the principal may read (a base that is not is `no_entry`); a ref update and a pushed ref, for the session the ref is moved to and the sessions its current and expected targets name, refused as `no_ref` without naming them; and a ref's resolve, log and follow, for the session its target names, a ref the principal may not read being answered `no_ref` and a follow's move onto one reading as a move to none. A listing leaves out the sessions the principal may not read, and an object is reachable only through a session it may read. An event ID, like a hash, is not a capability. The package decides nothing here: the program says whose sessions are whose, as it says who a request is from.
type Capabilities ¶
type Capabilities struct {
// Reads is the newest format the server reads, as a header names it.
// It reads every earlier minor of that major too, as the library
// does.
Reads string `json:"reads"`
Features Features `json:"features"`
Limits Limits `json:"limits"`
}
Capabilities are what a server answers at GET /v1/: the format it reads and, for each optional feature of RFC 0003, whether its store supports it through this server.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a store a Handler serves, over the wire. It implements agentsession.Store, agentsession.Writer, agentsession.Reader, agentsession.Follower and agentsession.RefStore.
It writes under leases, RFC 0003's form of a store's hold: Create and Open take one on the session, which the client renews until Release, Delete or Close ends it, and every write carries its fencing token. The session Create and Open return is the client's own, kept up to date with the client's appends and not shared with the server, as a local store's session is shared with that store: moving its leaf (Session.Branch, ResetLeaf) steers the client's next append, and that append moves the server's head with it, by RFC 0003's compare-and-swap, so the record's head is the writer's leaf.
func Dial ¶
func Dial(baseURL string, opts ...ClientOption) (*Client, error)
Dial returns a client of the server at baseURL, the URL the /v1 routes are under. It does not contact the server.
func (*Client) Append ¶ added in v0.0.23
func (c *Client) Append(ctx context.Context, sessionID string, e agentsession.Entry) (string, error)
Append implements agentsession.Store.
func (*Client) Blob ¶ added in v0.0.31
Blob implements agentsession.BlobReader: a media object the caller may read, checked against its hash. One the server does not hold, or the caller may not read, is agentsession.ErrNoBlob.
func (*Client) Capabilities ¶
func (c *Client) Capabilities(ctx context.Context) (Capabilities, error)
Capabilities asks the server what it serves.
func (*Client) CheckPush ¶ added in v0.0.31
func (c *Client) CheckPush(ctx context.Context, h agentsession.Header, entries, prefix []string) error
CheckPush is RFC 0003's push pre-flight: it posts the session's header and its entries and prefix by hash to push?check, and returns the session-level refusal the push would meet, or nil, with nothing sent or stored but the check. It is advisory; the push is the authority.
func (*Client) Create ¶ added in v0.0.23
func (c *Client) Create(ctx context.Context, h agentsession.Header) (*agentsession.Session, error)
Create implements agentsession.Store: the session is created under a lease the client holds from its first instant, and returned as the client's own.
func (*Client) DeclareRecord ¶ added in v0.0.23
DeclareRecord makes the server the record for a session it holds as a mirror, RFC 0002's declare-record, when its head is head, the one the caller fetched; basis is "record_deleted" or "sole_copy". It is how a replacement takes over a session whose local-first writer died: it declares the record at the server, then writes under a lease.
func (*Client) Delete ¶ added in v0.0.23
Delete implements agentsession.Store: under the client's lease, taking one when it holds none, and the lease ends with the session.
func (*Client) Follow ¶
func (c *Client) Follow(ctx context.Context, id string, from agentsession.Cursor) iter.Seq2[agentsession.Change, error]
Follow implements agentsession.Follower over the server's follow.
Every entry is verified by its hash before it is yielded. A follow the server cuts because it fell behind resumes from its last event by itself; one cut otherwise ends with ErrCut, and the caller resumes from the last change's cursor. This client can resume only from a cursor of a follow it served: it holds the session there. For any other cursor it follows afresh and yields the session as a agentsession.Reset, which the contract allows for a cursor it cannot resume from, and which a consumer handles by dropping what it derived before.
func (*Client) FollowCommitted ¶ added in v0.0.23
func (c *Client) FollowCommitted(ctx context.Context, id string, from agentsession.Cursor) iter.Seq2[agentsession.Change, error]
FollowCommitted follows the session as Client.Follow does, with only what is committed: RFC 0003's durable=true. The server commits the session where it holds its writer before it sends, so what is yielded survives a crash of the server's store. It needs the capability `committed_view`.
func (*Client) FollowRef ¶ added in v0.0.23
FollowRef follows a ref: its target on connect, and again each time the target or its state changes, until ctx is done. A ref deleted is a zero target. It needs the capability `ref_follow`. A stream cut ends with ErrCut; a reconnect is sent the current state again, as a ref has no cursor.
func (*Client) HandOver ¶ added in v0.0.23
HandOver makes the server a mirror of a session it is the record for, when its head is head: the server's side of handing the session to a client store, which has fetched it and then declares the record in its own store. It is a write, under the client's lease, which ends with it.
func (*Client) Import ¶ added in v0.0.31
func (c *Client) Import(ctx context.Context, r io.Reader, asRecord bool) (*agentsession.Session, error)
Import implements agentsession.Importer over RFC 0003's import: it posts the file, which the server holds to RFC 0002's import checks and commits before it answers, and returns the session as the server holds it, read back. A sidecar session's blobs must be on the server already, uploaded or held by a session the caller may read, or the import is refused with ErrMissingObject, which matches agentsession.ErrNoBlob too; Client.ImportMedia uploads them first.
func (*Client) ImportMedia ¶ added in v0.0.31
func (c *Client) ImportMedia(ctx context.Context, r io.Reader, media fs.FS, asRecord bool) (*agentsession.Session, []string, error)
ImportMedia implements agentsession.MediaImporter: for a sidecar session it uploads each blob the items name from media, verified against its name, then imports the file. A blob media lacks is not uploaded; the server refuses the import with ErrMissingObject and agentsession.ErrNoBlob unless it holds the blob for the caller already, and nothing is imported. So over the wire a missing blob refuses the import, where a local store takes the session and reports the blob unresolved; the unresolved list returned here is always empty.
func (*Client) List ¶
func (c *Client) List(ctx context.Context, f agentsession.ListFilter) iter.Seq2[agentsession.Summary, error]
List lists the sessions matching f, newest first, as the store's List does. A summary carries no path.
func (*Client) ListRefs ¶
ListRefs implements agentsession.RefStore.
func (*Client) Log ¶ added in v0.0.23
func (c *Client) Log(ctx context.Context, id string, after agentsession.Cursor, durable bool) (Log, error)
Log reads the session's log after the cursor after, from its start when after is empty: RFC 0003's log read. A cursor is a log read's or a follow's. With durable it reads only what is committed, which the server commits first where it holds the session's writer. It is answered by a server whose store keeps a log of records (the capability `log`); durable needs `committed_view`.
func (*Client) Missing ¶ added in v0.0.23
Missing asks which of the objects the server lacks for this caller: every one no session the caller may read holds, and that the caller did not upload.
func (*Client) Object ¶ added in v0.0.23
Object reads an object the server holds and the caller may read: an envelope (cas.SpaceEntries), a content (cas.SpaceContents) or a media blob (cas.SpaceMedia). The bytes are checked against the hash, and kept, so each object is fetched once.
func (*Client) Open ¶ added in v0.0.23
Open implements agentsession.Store: it takes a lease on the session, unless the client holds one, and returns the client's session, the same one each time while the lease holds.
func (*Client) Path ¶ added in v0.0.23
Path reads the path from the session's root to leaf (the head when leaf is empty), root first, with contents "all", "since-fold" or "none" ("all" when empty): RFC 0003's path read. Every envelope is verified by its hash and its parent link, and every content carried by the hash its envelope names, before Path returns, so the chain holds from the response alone.
func (*Client) PutBlob ¶ added in v0.0.31
PutBlob implements agentsession.BlobWriter: it uploads a sidecar blob as a media object, which write scope may do, so a writer can upload what the sidecar items it appends name. A server whose store holds no sidecar media refuses it with ErrUnsupported.
func (*Client) Read ¶
Read implements agentsession.Reader: the whole session, as a follow's snapshot carries it, since this slice serves no objects to read it by. The session is the caller's own.
func (*Client) RefLog ¶
RefLog implements agentsession.RefStore.
func (*Client) Release ¶ added in v0.0.23
Release ends the client's lease on the session, so another writer can take it. The session Open returned no longer follows the server.
func (*Client) ResolveRef ¶
ResolveRef implements agentsession.RefStore. A dangling ref returns its target with an error wrapping agentsession.ErrNoSession, as the library's stores do.
func (*Client) SetHead ¶ added in v0.0.31
SetHead implements agentsession.HeadStore: the server's head compare-and-swap, durable, under the client's lease, taking one when it holds none. The client's session moves its leaf to to once the server has, so the next append continues from there. A server whose store has no compare-and-swap of its own refuses it as ErrUnsupported.
func (*Client) UpdateRef ¶
func (c *Client) UpdateRef(ctx context.Context, name string, expected, next agentsession.RefTarget, reason string) error
UpdateRef implements agentsession.RefStore: RFC 0003's ref compare-and-swap, which needs refs scope and no lease.
func (*Client) Write ¶ added in v0.0.23
func (c *Client) Write(ctx context.Context, sessionID string, e agentsession.Entry) (agentsession.Result, error)
Write implements agentsession.Writer. The entry is prepared on the client's session, so it goes as a complete line, the same entry on a retry; the server's head is first moved to the client's leaf when the two differ, so the append lands where the writer's session says; and the client's session takes the entry once the server has. An entry naming a sidecar blob the server cannot find for the caller is refused with ErrMissingObject, which matches agentsession.ErrNoBlob too, and nothing is appended: the caller uploads the blob with Client.PutBlob first.
type ClientOption ¶
type ClientOption func(*Client)
ClientOption configures Dial.
func WithHTTPClient ¶
func WithHTTPClient(hc *http.Client) ClientOption
WithHTTPClient sets the HTTP client requests go through. The client sends no credentials of its own: a program whose server authenticates its requests adds them here, in hc's Transport, as the server's Authenticator expects them.
func WithHolder ¶ added in v0.0.23
func WithHolder(label string) ClientOption
WithHolder labels the leases the client takes, which a refused acquire shows another writer. It is a label, not an identity.
func WithLeaseTTL ¶ added in v0.0.23
func WithLeaseTTL(d time.Duration) ClientOption
WithLeaseTTL sets the time to live the client asks for its leases (default 30s); it renews each at a third of it.
func WithRedactedOnly ¶ added in v0.0.27
func WithRedactedOnly() ClientOption
WithRedactedOnly makes the client send only redacted entries, as a publisher's client of a store that holds only redacted ones. Create refuses a header that does not carry redacted; Append and Write refuse an entry that carries no redacted member, and an entry to a session whose header does not carry redacted; Push refuses a session whose header does not carry redacted or that holds an entry, prefix included, without it: each with agentsession.ErrNotRedacted before the entry or the session is sent. A fork's own entries and a resumed redacted session's native entries are refused alike. The server's store refuses them too when it is set to hold only redacted entries, answering not_redacted, a push at its pre-flight before any object is uploaded when the header does not carry redacted; the option makes the client fail closed against a server that is not set so, and refuse an entry without redacted, which the pre-flight cannot see, before it uploads anything.
type Features ¶
type Features struct {
Objects bool `json:"objects"`
Shallow bool `json:"shallow"`
Exchange bool `json:"exchange"`
Marks bool `json:"marks"`
Commit bool `json:"commit"`
CommittedView bool `json:"committed_view"`
Refs bool `json:"refs"`
RefFollow bool `json:"ref_follow"`
Leases bool `json:"leases"`
// Log is the log read, record by record with a cursor after each,
// which a store that keeps a log of records serves (of the
// reference stores, cas).
Log bool `json:"log"`
// DurableMarks is durable on each event of a follow that is not
// durable=true, and the committed event. This server does not send
// them: its follow is a store's follower, which does not say; a
// client that needs committed records follows with durable=true.
DurableMarks bool `json:"durable_marks"`
// Media is set when the server's store holds sidecar blobs, so it
// takes sidecar sessions and a media object's PUT; one that does not
// refuses a sidecar session with unsupported (RFC 0002, Media).
Media bool `json:"media"`
}
Features are RFC 0003's optional features. A request for one the server does not support is 501 `unsupported`.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler serves a store over RFC 0003: reading, following and refs, and writing under leases. Close stops the followers it runs and ends its leases.
func NewHandler ¶
func NewHandler(store agentsession.Store, opts ...Option) *Handler
NewHandler serves store. It reads sessions through the store's agentsession.Reader and follows them through its agentsession.Follower, so a store without both serves neither, and refs through its agentsession.RefStore when it keeps them.
func (*Handler) Capabilities ¶
func (h *Handler) Capabilities() Capabilities
Capabilities are what this handler serves of its store.
type Limits ¶
type Limits struct {
// FollowBuffer is how many events a follow may fall behind before
// the server cuts it with `overflow`.
FollowBuffer int `json:"follow_buffer"`
// FollowReplay is how many recent events a resume can be served from
// without loading the session again.
FollowReplay int `json:"follow_replay"`
// UploadGraceMS is how long an object uploaded for a push is kept
// for its uploader's push before nothing reaches it: a sweep of the
// store gives unreferenced objects more than it, a margin for a write
// admitted on an upload to land. Zero where the server takes no
// objects or media.
UploadGraceMS int64 `json:"upload_grace_ms,omitempty"`
// SnapshotBytes is the most entry-line bytes a follow's snapshot or
// reset carries inline: past it a server that serves objects sends
// records without their lines, and one that serves none refuses the
// snapshot with `too_large`.
SnapshotBytes int64 `json:"snapshot_bytes,omitempty"`
}
Limits are the server's bounds a client can plan around.
type Log ¶ added in v0.0.23
type Log struct {
Header agentsession.Header
Incarnation string
// Mark is the record mark where the server's store keeps one.
Mark string
// Reset is set when the cursor asked for named no place in the log:
// Records are then the whole log.
Reset bool
Records []LogRecord
// Head is the session's head at the read's end, "" for none.
Head string
// Cursor is where the read ended.
Cursor agentsession.Cursor
}
Log is a session's log as one log read answered it.
type LogRecord ¶ added in v0.0.23
type LogRecord struct {
// Seq is the entry's place among the session's entries, from 1; for a
// head move, the number of entries before it.
Seq int
// Entry is the ID of the entry the record appended, "" for a head
// move.
Entry string
// Head is the session's head after the record, "" for none.
Head string
// Outcome is what the append did: continued, branched, leaf_moved or
// leaf_not_moved; "" for a head move.
Outcome string
// Durable is set when the record was committed when it was read.
Durable bool
// Cursor is the position after the record, to read or follow from.
Cursor agentsession.Cursor
}
LogRecord is one record of a log read: an append, with the entry it appended, or a head move, with Entry empty.
type Objects ¶ added in v0.0.23
type Objects struct {
Entries []string `json:"entries"`
Contents []string `json:"contents"`
Media []string `json:"media"`
}
Objects are object hashes by RFC 0003's space: the body of a missing request and its answer, and a missing_object problem's member.
type Option ¶
type Option func(*Handler)
Option configures a Handler.
func WithAuthenticator ¶
func WithAuthenticator(a Authenticator) Option
WithAuthenticator sets how a request's principal is resolved. Without it, or WithInsecureNoAuth, the handler refuses every request.
func WithAuthorize ¶ added in v0.0.31
func WithAuthorize(a Authorizer) Option
WithAuthorize sets how a request is authorized against the session it names, beyond the scope its route needs; see Authorizer. Without it a principal with a scope holds it over every session of the store.
func WithChallenge ¶ added in v0.0.30
WithChallenge sets the WWW-Authenticate challenge sent on every 401 the handler writes, whether its authenticator refused the request, no authenticator is configured or a store's error is the 401. RFC 9110 §15.5.2 requires a 401 to carry one; the package ships no authentication, so the scheme is the program's to name, for example WithChallenge(`Bearer realm="dax"`). Without it no challenge is sent.
func WithErrorLog ¶
WithErrorLog receives the errors the handler answers as `store_fault`, whose text a client is not sent.
func WithFollowBuffer ¶
WithFollowBuffer sets how many events a follow may fall behind before it is cut with `overflow` (default 256).
func WithFollowReplay ¶
WithFollowReplay sets how many recent events of a session the handler keeps, so a client that resumes from one of them is served without loading the session again (default 1024).
func WithHeartbeat ¶
WithHeartbeat sets how often an idle follow is sent a comment, so intermediaries do not close it (default 15s).
func WithInsecureNoAuth ¶
func WithInsecureNoAuth() Option
WithInsecureNoAuth serves every request as a principal with every scope, for tests and for a socket only the host's user can reach. Everything a session holds is readable by whoever can connect.
func WithLeaseLimits ¶ added in v0.0.23
WithLeaseLimits sets a lease's time to live when an acquire names none (default 30s), and the most an acquire or renew may ask for (default 5m). A lease not renewed within its time ends.
func WithLinger ¶
WithLinger sets how long a session's follower outlives its last client, so reads close together share one load (default 2s).
func WithRefPoll ¶ added in v0.0.23
WithRefPoll sets how often a followed ref is resolved again, the reference stores having no watch on a ref (default 1s).
func WithSnapshotLimit ¶ added in v0.0.23
WithSnapshotLimit bounds the entry lines a follow's snapshot or reset carries inline, in bytes (default 64 MiB). Past it, a server that serves objects sends the rest of the records without their lines, which a client reads as objects; one that serves none refuses the snapshot with `too_large`, since its client has no other way to read the whole session. It is published in the capabilities.
func WithUploadGrace ¶ added in v0.0.23
WithUploadGrace sets how long an object uploaded for a push stays usable by its uploader's push (default 1h). It is published in the capabilities, and a Sweep of the store must give unreferenced objects more than it, since an upload is one until a push names it and a push admitted on it near its end lands after it: an operator sweeps a cas store with a margin, as twice the grace. Over a store that holds blobs and keeps no objects, an agentsession.BlobSweeper, the server sweeps with it itself: a media upload no session names is removed once older than twice the grace, the second a margin for a write admitted on the upload's reach.
type Path ¶ added in v0.0.23
type Path struct {
Head string
Cursor agentsession.Cursor
Incarnation string
// Contents is what the server answered: all, since-fold or none. A
// since-fold read the server could not make shallow comes back all.
Contents string
Entries []PathEntry
}
Path is a session's path as one path read answered it, root first. A shallow path (Contents "since-fold") is not the session: it lacks the contents the fold replaced, which Lacks names, and is no RFC 0001 file; what it holds rebuilds the request at its leaf.
type PathEntry ¶ added in v0.0.23
type PathEntry struct {
ID string
Envelope json.RawMessage
// Content is nil for an entry whose content the read left out.
Content json.RawMessage
// Entry is the entry decoded, nil when Content is.
Entry agentsession.Entry
}
PathEntry is one entry of a path read: its envelope always, its content and the decoded entry when the read carried the content.
Entry is decoded from its line alone, as agentsession.UnmarshalEntry decodes one: a path read does not carry the session's header, so Entry is read as this package writes it. In a session whose header names a minor before 0.13, an entry's redacted member is one that minor does not define; a caller that needs it read so takes the format from Client.Session or a log read's header and applies agentsession.GateEntry.
type Principal ¶
Principal is who a request is from, as an Authenticator resolved it, and what they may do.
func PrincipalFrom ¶
PrincipalFrom returns the principal a Handler resolved for the request whose context ctx is.
type Problem ¶
type Problem struct {
Status int `json:"status"`
Code string `json:"code"`
Title string `json:"title"`
Detail string `json:"detail,omitempty"`
// Target is a dangling ref's target, so a client can see which
// session it was.
Target *Target `json:"target,omitempty"`
// Incarnation is the session's, on a problem about one.
Incarnation string `json:"incarnation,omitempty"`
// Reason names the library rule a bad_entry or bad_base broke.
Reason string `json:"reason,omitempty"`
// Head is the session's head on a head_moved refusal, "" for none.
Head *string `json:"head,omitempty"`
// Holder is the label of the lease holder on a session_locked
// refusal, for a caller with write scope.
Holder string `json:"holder,omitempty"`
// Missing is the objects a push named that the server could not
// find for the caller, on missing_object.
Missing *Objects `json:"missing,omitempty"`
// Push is what a push did before it was refused, on a handover's
// head_moved after its entries were admitted: the push's response,
// its refs not attempted.
Push *PushResult `json:"push,omitempty"`
}
Problem is an error that crossed the wire, as RFC 9457's problem details with RFC 0003's stable code. It matches, with errors.Is, the sentinel its code names.
type PushHead ¶ added in v0.0.23
type PushHead struct {
Moved bool `json:"moved"`
Forced bool `json:"forced"`
Current *string `json:"current"`
Why string `json:"why,omitempty"`
}
PushHead is a push's head step: whether the head moved, whether a force moved one that was not the expected head, the head now, and why it did not move.
type PushRef ¶ added in v0.0.23
type PushRef struct {
Name string `json:"name"`
Expected *Target `json:"expected"`
Next *Target `json:"next"`
}
PushRef is a ref a push carries: the receiver's ref of that name is moved from Expected to Next by compare-and-swap once the entries and the head are done. Next names the pushed session.
type PushRefResult ¶ added in v0.0.23
type PushRefResult struct {
Name string `json:"name"`
Moved bool `json:"moved"`
Current *Target `json:"current"`
Why string `json:"why,omitempty"`
}
PushRefResult is what became of one pushed ref.
type PushResult ¶ added in v0.0.23
type PushResult struct {
// Admitted is how many of the session's own entries the server took
// in; the rest it held. Created is set when it lacked the session.
Admitted int `json:"admitted"`
Created bool `json:"created"`
// Head is the head step.
Head PushHead `json:"head"`
// Mark is the server's mark for the session afterwards.
Mark string `json:"mark"`
// Refs is what became of each ref, in order; nil when a handover
// failed and refs were not attempted.
Refs []PushRefResult `json:"refs"`
}
PushResult is what a push did at the server.
type RefState ¶ added in v0.0.23
type RefState struct {
Name string
Target agentsession.RefTarget
Dangling bool
}
RefState is a followed ref's target, and whether its session is gone or another now holds its ID.
type Scope ¶
type Scope string
Scope is what a principal may do through a Handler: RFC 0003's read, write (lease), refs, exchange and admin.
const ( // ScopeRead reads and follows sessions and refs. ScopeRead Scope = "read" // ScopeWrite creates sessions, takes leases on them and, under a // lease, appends, moves the head, commits and deletes. ScopeWrite Scope = "write" // ScopeRefs updates refs. A ref is the store's state, not a // session's, so an update needs this and no lease. ScopeRefs Scope = "refs" // ScopeExchange uploads objects, asks which a push needs, pushes a // session (RFC 0002's exchange, over the wire) and declares this // server the record for a session it holds as a mirror. A push's // refs need ScopeRefs as well. ScopeExchange Scope = "exchange" // ScopeAdmin breaks a lease another holder has. ScopeAdmin Scope = "admin" )
type SessionInfo ¶
type SessionInfo struct {
Header agentsession.Header
Head string
Length int
Incarnation string
// Cursor is where a follow from this state resumes.
Cursor agentsession.Cursor
// Mark is the server's record mark for the session, record or
// mirror, where its store keeps one, and "" where it does not.
Mark string
}
SessionInfo is a session's state as the server holds it: not a read of its entries.