Documentation
¶
Overview ¶
Package admission accepts commands durably and reconciles the ones with no live owner.
The interfaces here are the ones THIS package calls. Directory and PlacementController are declared here, not in internal/routing and internal/placement, because admission is the caller: routing and placement supply implementations of a contract their consumer states.
Index ¶
- Constants
- Variables
- func IsCode(err error, code sessionwire.ErrorCode) bool
- type Authorizer
- type Catalog
- type Claims
- type Clock
- type CommandStore
- type Commands
- type Config
- type Directory
- type Disposition
- type DispositionDue
- type DispositionReconciler
- type DispositionReconcilerConfig
- type DispositionSettlement
- type DueCommands
- type Error
- type GateResponders
- type LegacyCreateRequest
- type LegacyCreateResult
- type OwnerDirectory
- type PlacementController
- type PrincipalResponders
- type PublicCreateStore
- type Reconciler
- type ReconcilerConfig
- type Service
- func (s *Service) AdmitCreate(ctx context.Context, principal identity.Principal, ...) (sessionstore.DispositionInboxEntry, bool, error)
- func (s *Service) AdmitGateResponse(ctx context.Context, principal identity.Principal, ...) (sessionstore.DispositionInboxEntry, bool, error)
- func (s *Service) AdmitInput(ctx context.Context, principal identity.Principal, ...) (sessionstore.DispositionInboxEntry, bool, error)
- func (s *Service) AdmitInterrupt(ctx context.Context, principal identity.Principal, ...) (sessionstore.DispositionInboxEntry, bool, error)
- func (s *Service) AdmitLegacyCreate(ctx context.Context, principal identity.Principal, req LegacyCreateRequest) (LegacyCreateResult, error)
- func (s *Service) AdmitRestore(ctx context.Context, principal identity.Principal, ...) (sessionstore.DispositionInboxEntry, bool, error)
- func (s *Service) FenceAdmissions() <-chan struct{}
- func (s *Service) GateResponsesAnswerable(ctx context.Context, tenant sessionwire.TenantID, ...) (bool, error)
- func (s *Service) Quiesce(ctx context.Context) error
- type SessionBindingTemplate
- type Settlement
- type SweepResult
- type Target
- type TargetResolver
- type UUIDSource
Constants ¶
const ( CommandCreate = command.KindCreateSession CommandInput = command.KindInput CommandInterrupt = command.KindInterrupt CommandRestore = command.KindRestore CommandGateResponse = command.KindGateResponse )
The kinds this service admits.
They are ALIASES of internal/command's, not a private copy, and this package is the one where that matters most: the REST control routes and the ClientLink RPCs are two spellings of one admission contract (specification section 8.1), and both are routed here. Two private copies would satisfy every test either package could write while letting an RPC be authorized under one spelling and admitted under another. They were a private copy until A6.1's gate found it.
Variables ¶
var ErrAdmissionQuiesced = errors.New("admission: this replica is quiescing")
ErrAdmissionQuiesced is a transient local shutdown condition. It is a fault, not a durable command refusal: the caller may retry on another replica.
var ErrCreateBindingUnconfigured = errors.New("admission: V1 create requires a configured session binding")
ErrCreateBindingUnconfigured reports a V1 create reached a composition that named no SessionBindingTemplate. It is NOT a protocol gap -- the released store admits a public create -- it is a deployment that did not say which storage configuration its sessions are pinned to. Guessing one would be irreversible.
ErrGateResponderUnavailable classifies a failure to ASK the owner whether it applies gate responses that is a transient condition of the path to it -- its link reconnecting, this replica's link ceiling full, a dial that failed, a pool shutting down -- rather than an answer about the Host. The composition's GateResponders wraps it; admission returns it as a fault (no public code), and the HTTP edge answers it 503 unavailable, which is RETRYABLE, for storeUnavailable's reason: a transient deployment condition answered as a non-retryable 500 tells a client not to retry at exactly the moment retrying works (quality gate F1). Nothing is written before it, and a retry under the same CommandID is idempotent.
var ErrGateResponseTooLarge = errors.New("admission: a gate response larger than the inline inbox payload bound is refused")
ErrGateResponseTooLarge is the cause of the invalid_request refusal a NEW gate response gets when its canonical command payload is larger than sessionstore.MaxInboxPayloadBytes (64 KiB), the most the disposition inbox keeps inline.
Every other kind above that size is stored by reference (runbook A3.1 step 5). A gate response is not, because host v0.4.0 treats a gate response whose body is held by reference as BLOCKING: the session's whole command stream waits behind it until its apply deadline, and every command admitted after it expires with it (host v0.4.0 spec gate C1). So it is refused before anything is written.
The size measured is the CANONICAL ENCODED size -- len(canonicalCommand), the very bytes admit stores and the store checks against the bound -- not the size of the body the caller sent (quality gate F5). encoding/json escapes '<', '>' and '&' to six bytes each and compacts raw values, so a body of about 11 KiB of '<' canonicalises past 64 KiB and is refused; the refusal message carries the canonical size. It is still a function of the caller's own request alone, which is why the code is invalid_request (400) and not a state conflict. A retry of a gate response already stored -- which only an earlier Factory could have admitted by reference -- still answers from its record.
var ErrGateResponseUnsupported = errors.New("admission: the session's owner cannot apply a gate response")
ErrGateResponseUnsupported is the cause of the gate_not_resumable refusal a gate response gets when the session's live owner cannot APPLY one.
A gate response is a durable command the owning Host reads from the session's disposition stream, so admitting it IS delivering it: once it is in the inbox, the Host consumes it. A Host that cannot apply the kind would refuse it (host v0.3.0) or, worse, one that predates the refusal would hold it -- so the question is asked BEFORE anything is written, of the owner the fresh-owner check just accepted, and answered by the one capability predicate (hostlink.GateResponseCapable, through GateResponders).
The predicate admits exactly a Host whose connect reply carries Core's token sessionwire.HostLinkCapabilityGateResponse (host >= v0.4.0). A v0.3.0 Host does not, so a gate response to a session it owns is refused here -- its behaviour unchanged. The catalog's LeaseEpoch is NOT consulted: it is never a capability signal (owner ruling, 2026-09-19).
var ErrInvalidReconcilerConfig = errors.New("admission: invalid reconciler configuration")
ErrInvalidReconcilerConfig reports a reconciler configuration refused before it could reach a store.
var ErrLegacyCreateUnsupported = errors.New("admission: legacy create unsupported")
ErrLegacyCreateUnsupported reports that Factory cannot create a session on the legacy protocol. No runtime this program ships can host such a session.
var ErrLegacySessionUnsupported = errors.New("admission: the session is bound to the legacy protocol, which no Host can take residency on")
ErrLegacySessionUnsupported reports a command for a session whose immutable binding names the LEGACY protocol.
Every command this service admits is admitted into the DISPOSITION family, because that is the only family a Host can reach: a Host takes residency through AcquireResidency, which pins ProtocolModeDisposition, so a command admitted into the legacy inbox is one no Host will ever apply. A legacy-bound session cannot be created by this module at all (ErrLegacyCreateUnsupported), so the refusal is reached only for a session some other writer created. It is runtime_unavailable for the reason every other "no runtime this deployment runs can serve this" is: nothing about the request can change the answer.
ErrPrincipalResponderUnavailable is a transient failure to ask a Host about its principal and metadata capability. It is not a command refusal.
Functions ¶
Types ¶
type Authorizer ¶
type Authorizer interface {
AuthorizeControl(ctx context.Context, principal identity.Principal, session sessionwire.SessionID, kind sessionstore.CommandKind) error
// AuthorizeServiceSweep covers the cross-tenant due-work sweep over the
// service-control shards. It is never reachable by a tenant principal and
// its records never become a cross-tenant public response.
AuthorizeServiceSweep(ctx context.Context, principal identity.Principal) error
}
Authorizer decides both tenant command admission and the service sweep. Admission repeats the edge's control decision deliberately: it is a durable mutation boundary that can later be called by transports other than HTTP and ClientLink, and no such caller may acquire an authorization-free path.
type Catalog ¶
type Catalog interface {
GetCatalogEntry(context.Context, sessionstore.GetCatalogEntryRequest) (sessionstore.CatalogEntry, error)
CreateCatalogEntry(context.Context, sessionstore.CreateCatalogEntryRequest) (sessionstore.CatalogEntry, bool, error)
}
type Claims ¶
type Claims interface {
AcquireReconciliationClaim(context.Context, sessionstore.AcquireReconciliationClaimRequest) (sessionstore.ReconciliationClaimEntry, error)
ReleaseReconciliationClaim(context.Context, sessionstore.ReleaseReconciliationClaimRequest) (sessionstore.ReconciliationClaimEntry, error)
}
Claims is the short-lived reconciliation claim of specification section 10.
It SUPPRESSES DUPLICATE WORK AND IS NEVER A FENCE, which is a property of SessionStore's record rather than a promise made here: the record cannot name ownership, and nothing in that package reads a claim to decide a write. What makes two replicas sweeping one shard safe is the compare-and-swap on the command's own revision and the store's evidence rule; the claim only stops them both paying for it.
type Clock ¶
type Clock interface {
Now() time.Time
// AfterFunc runs f after d and returns a stop function reporting whether
// it prevented the call.
AfterFunc(d time.Duration, f func()) (stop func() bool)
}
Clock is the time seam.
It names only standard-library and builtin types on purpose. Factory's public option surface declares an identical Clock, and a shape with a named Timer type would make the two interfaces mutually unassignable across the internal/ boundary; AfterFunc's stop function is an unnamed func type, so they remain the same interface without an adapter.
type CommandStore ¶
type CommandStore interface {
AdmitDispositionCommand(context.Context, sessionstore.AdmitDispositionCommandRequest) (sessionstore.DispositionInboxEntry, bool, error)
GetDispositionCommand(context.Context, sessionstore.GetDispositionCommandRequest) (sessionstore.DispositionInboxEntry, error)
PutCommandPayload(context.Context, sessionstore.PutCommandPayloadRequest) (sessionwire.ObjectMetadata, error)
}
CommandStore is the durable command plane every command but a create is admitted into. It is the DISPOSITION family and only that family: see ErrLegacySessionUnsupported for why a legacy inbox write would be a command no Host could ever apply.
PutCommandPayload is here, and not borrowed from PublicCreateStore, because an oversized input is stored by reference exactly as an oversized create is (runbook A3.1 step 5), and PublicCreates is an OPTIONAL seam: a composition serving no creates must still be able to admit a large input.
type Commands ¶
type Commands interface {
AdmitDispositionCommand(ctx context.Context, req sessionstore.AdmitDispositionCommandRequest) (sessionstore.DispositionInboxEntry, bool, error)
GetDispositionCommand(ctx context.Context, req sessionstore.GetDispositionCommandRequest) (sessionstore.DispositionInboxEntry, error)
PutCommandPayload(ctx context.Context, req sessionstore.PutCommandPayloadRequest) (sessionwire.ObjectMetadata, error)
RejectDispositionCommand(ctx context.Context, req sessionstore.RejectDispositionCommandRequest) (sessionstore.DispositionInboxEntry, bool, error)
ListDueDispositionCommands(ctx context.Context, req sessionstore.ListDueDispositionCommandsRequest) (sessionstore.DispositionDueCommandPage, error)
RejectCommand(ctx context.Context, req sessionstore.RejectCommandRequest) (sessionstore.InboxEntry, error)
ListDueCommands(ctx context.Context, req sessionstore.ListDueCommandsRequest) (sessionstore.DueCommandPage, error)
AcquireReconciliationClaim(ctx context.Context, req sessionstore.AcquireReconciliationClaimRequest) (sessionstore.ReconciliationClaimEntry, error)
ReleaseReconciliationClaim(ctx context.Context, req sessionstore.ReleaseReconciliationClaimRequest) (sessionstore.ReconciliationClaimEntry, error)
}
Commands is the durable command plane admission uses.
Admission writes and reads the DISPOSITION family only. The two legacy methods left here, RejectCommand and ListDueCommands, belong to the legacy deadline sweep (Reconciler), which still settles legacy rows a store may already hold; nothing in this package admits into that family any more.
type Config ¶
type Config struct {
Authorizer Authorizer
Targets TargetResolver
Catalog Catalog
Commands CommandStore
Directory OwnerDirectory
Clock Clock
IDs UUIDSource
// GateResponders decides whether a gate response may be admitted for the
// owner that would apply it. OPTIONAL, and nil REFUSES every gate response
// with gate_not_resumable (ErrGateResponseUnsupported): the safe default is
// the one that never hands a Host a command it cannot apply.
GateResponders GateResponders
StampPrincipal bool
PrincipalResponders PrincipalResponders
// PublicCreates is the durable public-create plane a V1 create admits
// into. It is OPTIONAL, and its absence is a supported composition rather
// than a broken one: without it, and without Binding, AdmitCreate refuses
// and every other entry point is unaffected.
PublicCreates PublicCreateStore
// Binding is the deployment-configuration half of the immutable binding a
// V1 create pins. Optional for the same reason.
Binding SessionBindingTemplate
ApplyDeadline time.Duration
// OnAdmitted is a best-effort notification after a durable admission.
// The durable pending sweep remains responsible for lost notifications.
OnAdmitted func(placement.AdmissionNotice)
}
type Directory ¶
type Directory interface {
// Owner reports the observed registration for a session. The boolean is
// false when no owner is observed, which is not an error.
Owner(ctx context.Context, tenant sessionwire.TenantID, session sessionwire.SessionID) (sessionwire.HostLinkRegistryObservation, bool, error)
// Candidates pages compatible accepting Hosts. Capacity is not authority:
// a candidate confers nothing until the session lease is acquired.
Candidates(ctx context.Context, req sessionstore.ListCompatibleHostsRequest) (sessionstore.HostTargetPage, error)
}
Directory is the observed target directory: which Host currently owns a session, and which Hosts could accept one.
type Disposition ¶
type Disposition uint8
Disposition is what one sweep did about one due row, or why it did nothing.
It is a closed vocabulary with a rendering for every member including an unrecognized one, for the reason placement.Outcome is: a row's fate is operational data, and "" is not a fate.
const ( // DispositionSettleable is the predicate's positive answer and never a // recorded outcome: a row that reaches it goes on to be settled, deferred // or refused, and the recorded disposition is one of those. DispositionSettleable Disposition = iota // DispositionRejected means this sweep settled the command as rejected. DispositionRejected // DispositionUnexpired means the apply deadline has not passed. A Host may // still claim and apply the command, so nothing here may settle it. DispositionUnexpired // DispositionClaimLive means a live command claim holds the row: a Host is // working on it now. DispositionClaimLive // DispositionApplying means an application is in flight. Only the NEXT // lease holder can settle it, because the safety of that settlement rests // on a journal fence written at an epoch above the applying one -- and this // reconciler holds no lease and names no epoch. DispositionApplying // DispositionTerminal means the command had already reached applied or // rejected. inboxDue files a terminal command not-due, so this is a row // that settled between the page and the write. DispositionTerminal // DispositionEvidence means the store refused the settlement because the // session's journal does not prove the command had no effect. That is the // prefix winning, and it is an ordinary outcome rather than a fault. DispositionEvidence // DispositionRaceLost means the record moved between the page and the // compare-and-swap. DispositionRaceLost // DispositionDeferred means another replica holds this session's // reconciliation claim, so this one did nothing about the row. DispositionDeferred // DispositionUnrecognized means the record is in a state this build does // not know. It is refused rather than guessed at, so a newer store's state // cannot be rejected by an older sweeper. Only the disposition sweep // reports it: the legacy predicate's arms are total over its own states. DispositionUnrecognized )
func (Disposition) String ¶
func (d Disposition) String() string
type DispositionDue ¶ added in v0.3.0
type DispositionDue interface {
ControlShards() int
ListDueDispositionCommands(context.Context, sessionstore.ListDueDispositionCommandsRequest) (sessionstore.DispositionDueCommandPage, error)
}
DispositionDue is the disposition family's service-plane due query. It is the same two methods as DueCommands for the same reasons, over the other family's index.
type DispositionReconciler ¶ added in v0.3.0
type DispositionReconciler struct {
// contains filtered or unexported fields
}
DispositionReconciler sweeps expired disposition commands in fixed control shards, round-robin, one shard per pass and at most MaxPages*PageLimit rows.
It REMEMBERS WHERE EACH SHARD'S PASS STOPPED, and that is what keeps it from starving. The due view is deadline-ordered from the head, and the head is exactly where the rows this sweep may NOT reject accumulate: applying commands (a successor's business) and claimed ones whose claim is still live, each filed at its long-past deadline. A sweep that re-read from the head every pass would, once a shard held more than MaxPages*PageLimit of those, never again reach a rejectable row behind them. So a truncated pass keeps the store's continuation for its shard and the next pass over that shard resumes from it; only a pass that reaches the end re-arms at the head against a fresh bound. The position is advice, like the rotor: a restarted replica starts every shard at the head and loses nothing but time.
func NewDispositionReconciler ¶ added in v0.3.0
func NewDispositionReconciler(cfg DispositionReconcilerConfig) (*DispositionReconciler, error)
NewDispositionReconciler validates a configuration before it can reach a store.
func (*DispositionReconciler) Sweep ¶ added in v0.3.0
func (r *DispositionReconciler) Sweep(ctx context.Context, principal identity.Principal) (SweepResult, error)
Sweep reconciles the next control shard's expired disposition commands.
It is authorized once, as the cross-tenant sweep it is, for Reconciler.Sweep's reasons, and it releases every claim it took on every path.
type DispositionReconcilerConfig ¶ added in v0.3.0
type DispositionReconcilerConfig struct {
Authorizer Authorizer
Due DispositionDue
Settlement DispositionSettlement
Claims Claims
Clock Clock
HolderID string
ClaimTTL time.Duration
PageLimit int
MaxPages int
}
DispositionReconcilerConfig is one replica's disposition deadline sweep. Its members mean exactly what ReconcilerConfig's do.
type DispositionSettlement ¶ added in v0.3.0
type DispositionSettlement interface {
RejectDispositionCommand(context.Context, sessionstore.RejectDispositionCommandRequest) (sessionstore.DispositionInboxEntry, bool, error)
}
DispositionSettlement is the one durable write the disposition sweep makes. It is the pre-dispatch rejection and nothing else, for Settlement's reason: every other disposition transition belongs to a residency holder.
type DueCommands ¶
type DueCommands interface {
ControlShards() int
ListDueCommands(context.Context, sessionstore.ListDueCommandsRequest) (sessionstore.DueCommandPage, error)
}
DueCommands is the service-plane due-work query.
It is TWO methods because a sweeper needs both halves and neither can come from anywhere else. ControlShards is a READ of the count the backend is committed to, not a setting: the shard is a pure function of (TenantID, SessionID) computed identically by the writer and the sweeper, so a sweeper configured with its own count would silently never visit some shards. ListDueCommands answers one shard at a time, deliberately -- a store that swept every shard in one call would hold the whole deployment's reconciliation in one request's latency.
type Error ¶
type Error struct {
Code sessionwire.ErrorCode
Cause error
}
type GateResponders ¶ added in v0.5.0
type GateResponders interface {
// AcceptsGateResponses reports whether owner can apply a gate response.
// (false, nil) is a decision about the Host; an error is a fault -- the
// Host could not be asked -- and is never read as "cannot".
AcceptsGateResponses(ctx context.Context, owner sessionwire.HostLinkRegistryObservation) (bool, error)
}
GateResponders answers whether a session's live owner can apply a gate_response command. The composition implements it over the HostLink pool, asking the owner's link for the session's tenant and deciding with hostlink.GateResponseCapable -- the one predicate. See ErrGateResponseUnsupported.
type LegacyCreateRequest ¶
type LegacyCreateRequest struct {
AgentID sessionwire.AgentID
Blocks json.RawMessage
}
type LegacyCreateResult ¶
type LegacyCreateResult struct {
SessionID sessionwire.SessionID
Entry sessionstore.InboxEntry
}
type OwnerDirectory ¶
type OwnerDirectory interface {
Owner(context.Context, sessionwire.TenantID, sessionwire.SessionID) (sessionwire.HostLinkRegistryObservation, bool, error)
}
type PlacementController ¶
type PlacementController interface {
EnsurePlacement(ctx context.Context, desired sessionstore.DesiredWorkload) error
ReleasePlacement(ctx context.Context, tenant sessionwire.TenantID, session sessionwire.SessionID) error
}
PlacementController drives desired placement. It is idempotent in both directions: a repeated Ensure for the same desired workload creates nothing twice, and a repeated Release is not an error.
type PrincipalResponders ¶ added in v0.12.0
type PrincipalResponders interface {
AcceptsCommandPrincipal(context.Context, sessionwire.HostLinkRegistryObservation) (bool, error)
}
PrincipalResponders asks whether a resident Host reads attribution members.
type PublicCreateStore ¶
type PublicCreateStore interface {
PreparePublicCreate(context.Context, sessionstore.PreparePublicCreateRequest) (sessionstore.PublicCreatePreparation, error)
PutCommandPayload(context.Context, sessionstore.PutCommandPayloadRequest) (sessionwire.ObjectMetadata, error)
AdmitPublicCreate(context.Context, sessionstore.AdmitPublicCreateRequest) (sessionstore.DispositionInboxEntry, bool, error)
}
PublicCreateStore is the durable public-create plane.
It is the disposition family, not the legacy inbox, and that is forced rather than chosen: a Host takes residency through AcquireResidency, which pins ProtocolModeDisposition, so a legacy-bound session is one no Host can ever run.
type Reconciler ¶
type Reconciler struct {
// contains filtered or unexported fields
}
Reconciler sweeps due commands in fixed control shards, round-robin.
It holds exactly one piece of state, the rotor, and that state is advice rather than position: a restarted replica resumes at shard zero and re-derives everything else from the store within the call.
func NewReconciler ¶
func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error)
NewReconciler validates a configuration before it can reach a store.
func (*Reconciler) Sweep ¶
func (r *Reconciler) Sweep(ctx context.Context, principal identity.Principal) (SweepResult, error)
Sweep reconciles the next control shard, round-robin.
STEP ONE'S TWO HALVES ARE ONE CALL. This is a cross-tenant control-plane query -- a shard holds whichever tenants hash into it, so a page can mix them -- and AuthorizeServiceSweep is the decision that a tenant principal can never reach one. AuthorizeControl is deliberately NOT called here, and not because it would be redundant: a command arrives at the httpapi or clientlink edge already authorized, admission repeats that decision as the durable mutation boundary, and a THIRD authorizing caller is how an unauthorized caller acquires a path. What this sweep authorizes is the sweep, once, and the rows it then settles belong to no caller at all.
type ReconcilerConfig ¶
type ReconcilerConfig struct {
// Authorizer decides the sweep. The method called is AuthorizeServiceSweep
// and never AuthorizeControl: see Reconciler.Sweep.
Authorizer Authorizer
Due DueCommands
Settlement Settlement
Claims Claims
Clock Clock
// HolderID names this replica in a reconciliation claim. It is not
// authority -- the claim licenses nothing -- but it must be stable for a
// process, because extending one's own claim and taking over a crashed
// replica's lapsed one are the same store write and the holder is what
// tells them apart.
HolderID string
// ClaimTTL is how long this replica claims a session's reconciliation work
// for.
ClaimTTL time.Duration
// PageLimit bounds one due page.
PageLimit int
// MaxPages bounds ONE sweep of ONE shard.
//
// It is the periodic bound, and it is a bound on a PASS rather than on the
// work: a shard with more due rows than MaxPages*PageLimit is reported
// Truncated and finished by the next pass over that shard. That is chosen
// over draining the shard because the sweep is periodic and the alternative
// lets one busy shard hold the rotor -- and therefore every other shard's
// reconciliation -- for as long as its backlog lasts.
MaxPages int
}
ReconcilerConfig is one replica's due-command sweep configuration.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func NewService ¶
func (*Service) AdmitCreate ¶
func (s *Service) AdmitCreate(ctx context.Context, principal identity.Principal, req sessionwire.CreateRequest) (sessionstore.DispositionInboxEntry, bool, error)
AdmitCreate admits a V1 create: runbook A3.1 steps 2 and 5.
It returns a DISPOSITION entry, and the difference from the legacy inbox is the whole point rather than a type detail. A create is the one command that CHOOSES a session's protocol, and the only choice that produces a session a Host can ever take residency on is disposition. Every other command is then admitted into the same family (see admit), so there is no mixed-family session.
An oversized create is stored by reference instead of refused. That is step 5, and admit applies the same rule to the other four kinds.
func (*Service) AdmitGateResponse ¶
func (s *Service) AdmitGateResponse(ctx context.Context, principal identity.Principal, req sessionwire.GateResponseRequest) (sessionstore.DispositionInboxEntry, bool, error)
AdmitGateResponse is runbook A3.1 step 4: a gate response needs the matching durable gate projection AND a fresh resident, accepting owner, or it is gate_not_resumable before anything is written. Moving the command into the disposition family changed where it is written and nothing about that rule.
func (*Service) AdmitInput ¶
func (s *Service) AdmitInput(ctx context.Context, principal identity.Principal, req sessionwire.InputRequest) (sessionstore.DispositionInboxEntry, bool, error)
func (*Service) AdmitInterrupt ¶
func (s *Service) AdmitInterrupt(ctx context.Context, principal identity.Principal, req sessionwire.InterruptRequest) (sessionstore.DispositionInboxEntry, bool, error)
func (*Service) AdmitLegacyCreate ¶
func (s *Service) AdmitLegacyCreate(ctx context.Context, principal identity.Principal, req LegacyCreateRequest) (LegacyCreateResult, error)
func (*Service) AdmitRestore ¶
func (s *Service) AdmitRestore(ctx context.Context, principal identity.Principal, req sessionwire.RestoreRequest) (sessionstore.DispositionInboxEntry, bool, error)
func (*Service) FenceAdmissions ¶ added in v0.7.0
func (s *Service) FenceAdmissions() <-chan struct{}
FenceAdmissions permanently refuses new commands and returns a signal that closes after every command already inside the service has returned. It does not wait, so the Server can serialize the fence with Start and then release its lifecycle lock before joining active calls.
func (*Service) GateResponsesAnswerable ¶ added in v0.9.0
func (s *Service) GateResponsesAnswerable(ctx context.Context, tenant sessionwire.TenantID, session sessionwire.SessionID, record sessionstore.CatalogRecord) (bool, error)
GateResponsesAnswerable reports, for the gates READ, whether a gate response for this session would pass AdmitGateResponse's owner check now: (true, nil) when it would, (false, nil) when the write would refuse it gate_not_resumable, and (false, err) when the owner could not be asked -- where the write would answer a fault. It writes nothing and takes no admission slot: it is a read.
type SessionBindingTemplate ¶
SessionBindingTemplate is the deployment-configuration half of the immutable SessionBinding a V1 create pins into its session, forever.
It carries only the two members a DEPLOYMENT owns. The other two are not configuration and are deliberately absent: RuntimeSessionID is derived per create (see deriveRuntimeSessionID) and ProtocolMode is this module's own choice, not a knob -- see bind.
Why these two are exactly the deployment's: they are the two members ObjectStoreResolver keys on. A resolver "refuses any configuration this deployment does not know", so a create that pinned a StorageBindingID the resolver cannot resolve would write a session whose objects are permanently unreadable -- and SessionBinding is immutable after create, so there is no repair. The composition therefore refuses this template without a resolver, which is what makes this configuration rather than a guess.
type Settlement ¶
type Settlement interface {
RejectCommand(context.Context, sessionstore.RejectCommandRequest) (sessionstore.InboxEntry, error)
}
Settlement is the one durable write this reconciler makes.
It is exactly one method, and the narrowness is the point rather than tidiness: the settlements a due row could invite are reject, claim, begin applying and complete, and three of those are a LEASE HOLDER's. A reconciler holds no session lease and names no epoch, so a seam offering them would be authority this package cannot legitimately exercise.
type SweepResult ¶
type SweepResult struct {
// Shard is the control shard this sweep visited.
Shard int
// Pages, Examined and Unreadable are the store's own cost report,
// accumulated across the pages this sweep read. Examined is rows the
// provider returned and Unreadable is rows the store could not vouch for;
// both are reported rather than folded, because Examined == Pages*PageLimit
// with nothing returned is a different state from "nothing is due".
Pages int
Examined int
Unreadable int
// Due is the number of readable due rows this sweep considered.
Due int
// Rejected counts the commands this sweep settled.
Rejected int
// Claimed and Released count the reconciliation claims this sweep took and
// gave back. They are separate counters rather than one, because a
// difference between them is the thing worth seeing.
Claimed int
Released int
// Dispositions counts every recorded outcome by kind. DispositionSettleable
// never appears in it.
Dispositions map[Disposition]int
// Queries counts the store calls this sweep made that reach a provider:
// due pages, claim acquisitions, claim releases and settlements.
// ControlShards is deliberately not counted -- it is a read of a decision
// the store already holds in memory and makes no provider request.
Queries int
// ReleaseFailures counts releases that failed.
//
// A release failure is deliberately SWALLOWED -- replacing a correct
// settlement with a release error would report the wrong thing about the
// command, and a claim licenses nothing, so a claim left to lapse costs a
// delayed takeover of work that was already safe to do concurrently. This
// counter is what keeps "swallowed" from meaning "invisible", which is the
// shape a destructive call site with no observable takes.
ReleaseFailures int
// Truncated reports that MaxPages was reached with a continuation still
// outstanding. The remaining rows are still due and are met by the next
// pass over this shard.
Truncated bool
// Resumed reports that this pass continued from the position an earlier
// truncated pass over the same shard kept, rather than from the head.
// Only the disposition sweep keeps positions; the legacy one always reads
// from the head.
Resumed bool
}
SweepResult is what one sweep of one shard did, and what producing it cost.
type Target ¶
type Target struct {
Key sessionstore.HostTargetKey
Workload sessionstore.DesiredWorkload
}
Target is the configured immutable launch identity selected for a create. It is configuration, not current Host capacity and not an ownership claim.
type TargetResolver ¶
type TargetResolver interface {
ResolveAgent(context.Context, sessionwire.AgentID) (Target, bool, error)
IsKnown(context.Context, sessionstore.HostTargetKey) (bool, error)
}
type UUIDSource ¶
UUIDSource supplies the random identifiers Factory proposes, notably the SessionID it allocates before a Host exists to allocate one.