Documentation
¶
Overview ¶
Package harnessruntime is the Host runtime backed by harness: it binds Host's department seams to the released github.com/looprig/harness rig and session API, so a product registers a harness rig with Host instead of hand-writing the adapter.
Use ¶
target, err := harnessruntime.Target(
harnessruntime.RigPerTenant(map[sessionwire.TenantID]*rig.Rig{"acme": acmeRig}),
"my-product-build-7", // department.CompatibilityID, product-owned
department.Capabilities{SupportsPooled: true, AdmissionWeight: 1,
CaptureSafety: department.CaptureSafetyStreaming},
)
registrar := host.RegistrarFunc(func(context.Context) ([]department.Registration, error) {
return []department.Registration{harnessruntime.Registration("assistant", target)}, nil
})
Target declares both recovery capabilities (department.Recovery), so the target passes host.Compose's default RuntimeProfileDurable, and holds the declaration true at every bind: a session whose applier is not a runtimecommand.AttemptCloser, or whose fault channel is nil, is refused. Any session bind refuses is released first (detached from the launch context; a failed release is joined to the refusal). Every session the adapter binds has, and Host sees:
- the six required capabilities, each discovered on harness's own published interface (a harness signature change is a build failure here);
- session.PersistenceFaultReporter and session.ResidencyAbandoner, REQUIRED at bind — stricter than department, which treats them as optional;
- department.AttemptCloser, forwarding to harness's runtimecommand.AttemptCloser, so a successor closes a predecessor's stranded attempt;
- department.LiveOptionsSubscriber and the older live/reasoning pair, so host.Composition.LiveText can relay text, reasoning and tool steps;
- EphemeralDrops() uint64, the count of preview frames dropped or refused.
Every admitted command crosses with its Principal (all kinds) and Metadata (create and input); a create's first message is re-presented in the input-shaped body the BlockDecoder reads; a gate_response is decoded from Core's stored body; an unresolved PayloadRef is refused (Host resolves referenced bodies before the attempt, see department.RuntimeCommand); and a create launches under the binding's RigSessionID (rig.WithSessionID).
Customisation points ¶
- Per-tenant or per-launch rigs, journal stores and workspaces go through Rigs: the product assembles each rig with rig.WithSessionStore over the tenant's harness store, its workspace root, rig.WithToolResultObjects, presenters, MCP and so on. SharedRig, RigPerTenant and RigsFunc cover the common shapes. The adapter never opens a store.
- Presenters, live options and tool-result capture are rig and loop options in the product's Rigs; host.Composition.LiveText decides what Host relays. There is no presenter option here.
- Body encoding: WithBlockDecoder, only when the product's create/input body is not Core's InputRequest (the default, DecodeInputBlocks).
- The compatibility id is product-owned.
The rest of this comment is the adapter's design record: the inventory of what it binds to and the findings (H1–H9) each non-obvious choice answers.
Step-1 inventory ¶
LOCAL SEAM RELEASED COUNTERPART
department.Rig.NewSession rig.Rig.NewSession (H1, H2)
department.Rig.RestoreSession rig.Rig.RestoreSession (H2)
department.RigSession.ID session.Session.SessionID
department.IdleWaiter.WaitIdle session.IdleWaiter (H0)
department.Liveness.Done session.Liveness (H0)
department.Releaser.ReleaseResidency session.Releaser (H0)
department.PublicationSubscriber.Sub… session.CommittedPublicEvent…
…Provider/Source (H0, H3, H4)
department.CommandApplier.ApplyCommand runtimecommand.Provider/Applier
department.LeaseEpochReporter.Lease… session.LeaseEpochReporter (H5)
(H0, H5, H6, H7, H8)
SEAMS NOT IN THE TABLE ABOVE, stated because the sessionstore side states its exclusions and an inventory without one cannot be read as complete. department.Runtime, department.Department, department.Target and department.CompatibilityID are Host types describing what Host requires of a runtime; they stand in for no released type and binding them here would invent a dependency rather than record one. department.RigCreateRequest and department.RigRestoreRequest are the arguments to the two rows that ARE bound, not seams of their own. The Harness identities Host cannot adopt — core/uuid.UUID against the opaque sessionwire strings — are the subject of CLAUDE.md's paragraph on why an adapter is mandatory, not a row that could be bound differently.
NONE OF THE FIVE IS ON SessionController, AND ALL FIVE ARE PUBLISHED. That distinction is the whole of H0 and an earlier version of this file got it wrong. session.SessionController — the type rig.NewSession and rig.RestoreSession return — declares neither WaitIdle, nor Done, nor ReleaseResidency, nor a committed-public subscription, nor a command applier. But harness SEGREGATES them rather than omitting them: session.IdleWaiter, session.Liveness, session.Releaser, session.CommittedPublicEventSource with its session.CommittedPublicEventProvider, and runtimecommand.Applier with its runtimecommand.Provider are all exported, and each documents the assertion as the way a caller is meant to discover it.
SO THIS PACKAGE ASSERTS ON HARNESS'S OWN NAMES, never on a private structural twin. A private interface cannot produce a compile failure against a foreign type — a released signature change would arrive as a silent ok == false and an IncapableSessionError at run time, which is the opposite of the guard such a declaration appears to offer.
WHAT REMAINS UNENFORCED IS NARROWER, AND IT IS HARNESS'S OWN STATED HAZARD: the assertion is on the DYNAMIC type, so a wrapper that fails to forward a method silently opts its session out. session.IdleWaiter's doc says exactly that, and adds that nothing pins rig to keep returning the runtime type unwrapped.
Findings — the both-directions audit of step 2 ¶
H1. NEWSESSION TAKES NO IDENTITIES, NO WORKSPACE ROOT AND NO STORAGE CONTEXT. department.RigCreateRequest carries TenantID, SessionID, AgentID, Placement, WorkspaceRoot and Storage.Namespace. rig.Rig.NewSession takes only SessionOptions, of which the released set is one: WithSeedSnapshot. The workspace root and the resource storage are fixed when the Rig is DEFINED, not when a session is created. So a Host that materializes a per-session workspace cannot hand it to a shared rig, and this adapter takes a per-request rig instead — see Rigs. The fake accepts all six fields and asserts on them.
H2. RESTORESESSION TAKES ONLY A UUID. department.RigRestoreRequest carries the same six fields; rig.Rig.RestoreSession(ctx, id) accepts none of them. Every one is dropped. This adapter does not hide the drop and it cannot repair it: the request is forwarded to Rigs.RigForRestore, which is the one place a composition can honour a workspace root or a storage namespace, and past that seam only the UUID crosses. An adapter that took the request and forwarded nothing would look like it propagated six fields.
H3. THE LIVE SUBSCRIPTION CANNOT BE POSITIONED. Host's PublicationSubscriber delivers "committed public journal events AFTER the given event", which is how a client joins the live path with a bounded journal read. event.EventFilter selects loops and classes; it has no start position and there is no other parameter. A non-empty resume point is therefore refused rather than silently served from now — a silently-repositioned subscription is a gap in a client's journal that neither side can see.
H4. THE PUBLICATION IS ASSEMBLED, NOT PROJECTED. event.Delivery carries EventID, JournalSeq, PublicBody and CoveredThrough — everything sessionwire.EnduringPublication needs except the tenant and the session, which harness does not know. This adapter supplies them from the launch request. It also drops deliveries that are not committed publications, which the fake's closed channel never produces.
H5. ADMITTED REQUIRES A LEASE EPOCH AND THE COMMAND SEAM CARRIES NONE. runtimecommand.Admitted.Validate refuses a zero LeaseEpoch outright. department.RuntimeCommand has no epoch member at all, and commands.Applier constructs it from a record without one.
H5 AS RESOLVED BY O3.4, AND THE FIRST ANSWER WAS A DEFECT. The epoch used to be supplied to this adapter at bind time, through WithLeaseEpoch, which made it "a property of the BINDING rather than of the command". That was the weaker half of the problem; the stronger half went unsaid. THE COMPOSITION DOES NOT HAVE THIS NUMBER. harness checks an admitted command's LeaseEpoch for EQUALITY against the single-writer lease the session itself holds (internal/sessionruntime/runtime_command.go:140), and the only lease Host holds is its residency grant — a different issuer over a different namespace, which sessionstore.ResidencyEpoch says in terms must never be used as a journal epoch. The bound number was therefore right exactly while two independent per-session counters happened to agree, which under memstore they do because both start at 1. It would have failed deterministically the first time the two were acquired out of step.
harness v0.33.0 publishes session.LeaseEpochReporter, and the epoch is now read from THE RUNTIME, per command, through that capability. WithLeaseEpoch is gone. A session that reports no held grant — headless, no persistence, not wired for durable commands, or a lease already released — is refused with an UnsupportedCommandError rather than stamped with a plausible zero, because the capability's two results exist to separate "no epoch" from "epoch 0" and harness gates the report on the lease still being Valid.
H6. HARNESS APPLIES ALL FIVE KINDS SINCE v0.36.0, AND H6 IS LIFTED. runtimecommand.Kind named input and interrupt, then gate_response at v0.35.0, then create and restore at v0.36.0; commands.Kind has the same five. Until then create and restore were refused here — and that refusal was the wedge v0.36.0 exists to end: Host had already begun a durable dispatch attempt by the time the adapter said no, no disposition frame was ever written, the store could never settle the record, and the consumer blocked at the session's FIRST command forever. The gate is Kind.Valid() and always was, so nothing in this package had to change for the kinds to start crossing.
WHAT DID HAVE TO CHANGE IS THE PAYLOAD, AND MISSING IT IS SILENT DATA LOSS. The decode below used to be reached only by KindInput. A create crossing with no Blocks is a command harness accepts — Core's CreateRequest.Blocks is optional, so an empty one is a legitimate idle create — which the applier then settles `applied` having sent nothing. The user's first message would be dropped with the record looking perfectly settled, which is worse than the wedge it replaced because the wedge was visible. harness cannot detect it: an undecoded payload and an absent one are the same value on its side of the seam. So a create's body is read here, as Core's CreateRequest, and its blocks are re-presented in the input-shaped body a BlockDecoder reads; see internal/createbody for why there is one encoding rather than two. A create carrying a first message with no decoder bound is refused, exactly as an input is by H7. A product BlockDecoder that strictly decodes Core's InputRequest must use core v0.12.0 or later: input bytes and re-presented create bytes may carry principal and metadata, and the older strict decoder refuses them.
A RESTORE CROSSES CARRYING NOTHING. Core's RestoreRequest has no blocks member and Admitted.Validate refuses a restore that carries any. harness gates its input preparation on the PAYLOAD rather than the kind, so a restore never borrows the input path's loop refusals and a resident session's resume is not refused because some loop had exited.
A gate_response is admitted with harness's decoded answer (Admitted.GateResponse), built from the stored Core body by internal/gateresponse — the same decode the applier ran before the attempt — with the user as its source and the attempt the record authorized, which harness requires for this kind.
ONE-WAY UPGRADE, and it is Host's to state as well as harness's. Once a session journal holds a create or restore application prefix or disposition frame, harness v0.35.0 and older can neither replay nor reopen it. Do not roll a Host back below harness v0.36.0 once it has applied either kind.
H7. AN input COMMAND MUST CARRY DECODED content.Blocks, AND HOST'S PAYLOAD IS OPAQUE BYTES. department.RuntimeCommand documents the body as travelling opaque because "Host is not the semantic validator of a command body — Harness is". Admitted.Blocks is []content.Block and Validate refuses an input with none, so somebody must decode. This adapter takes a decoder, and since it became public (host v0.16.0) defaults to DecodeInputBlocks — Core's own InputRequest, the body Factory admits — rather than refusing every input a product that named no decoder receives. A bound session with no decoder at all (unreachable through New) still refuses.
H8. AN OBJECT-REFERENCED PAYLOAD IS DEREFERENCED BY THE APPLIER, NEVER BY THE ADAPTER. The applier reads and verifies the private body in the command's tenant/session scope and passes bytes to RuntimeCommand.Payload. Admitted has no reference member and the adapter never reads objects. A caller bypassing the applier and passing PayloadRef directly is still refused.
H9. HARNESS WRITES THE APPLICATION PREFIX ITSELF, AND BY DEFAULT WRITES IT UNDER ITS OWN SESSION SCOPE. harness/pkg/sessionstore opens the released store with WithLegacySingleTenant and derives the session id from the Harness UUID, so every record a session commits — the opening fence, the public events, and the EnvelopeKindApplicationPrefix that Applier.ApplyRuntimeCommand writes before each effect — lands under (<tenant>, <uuid>) rather than under Host's (TenantID, SessionID). ON THE DEFAULTS the two layouts are mutually exclusive at the backend: whichever store initializes one first, the other refuses it with KeyspaceError{layout_mismatch}. Measured in both directions by TestHostCannotOpenABackendHarnessInitialized and TestHarnessCannotOpenABackendHostInitialized, both of which still pass because the defaults did not move.
H9 AT harness v0.32.0 — THE TENANT HALF IS DISCHARGED, THE IDENTITY HALF IS NOT. The paragraph that used to close this finding said Host and its runtime "cannot share one storage backend AT ALL", that "nothing in this package can fix" it, and that it was "a release owed". The first two are now FALSE and the third is narrower than it was. v0.32.0's only production change is pkg/sessionstore, and it adds WithTenant, which replaces the hardcoded "local" so a Store files under the tenant it is opened with. The rebind measured the consequence rather than trusting the doc: see TestHostAndHarnessCanShareABackendOnTheLegacyLayout, which opens ONE backend from both sides, in both initialization orders, and reads harness's session scope through Host's store.
IT IS A PARTIAL DISCHARGE AND THE TWO COSTS ARE THE FINDING. First, sharing requires Host to open with WithLegacySingleTenant; Host's native multi-tenant open over a harness-initialized backend still fails layout_mismatch, so a Host that shares a backend with its runtime is a single-tenant Host for it. Second — and this is what is still owed — the legacy layout addresses a session by the UUID's canonical rendering and refuses anything else with KeyspaceError{legacy_session}, while Host's SessionID is the opaque sessionwire identity Factory admitted the session under, which CLAUDE.md states is NOT a UUID by contract. Sharing therefore works exactly when Host's session identity happens to be the rig's UUID, which Host does not control. Both costs are asserted, not described. What remains owed is no longer "a way for two stores to share a keyspace" — that exists — but Harness ceasing to impose its own identity grammar on the shared one.
COST 2 IS RELATED TO, AND NOT THE SAME AS, sessionstoreadapter's F5. That row is Host's uuid.UUID seam being too narrow for an opaque value the store will hand it; this is Harness's keyspace being too narrow for the opaque identity Factory admitted. Opposite directions, different subjects, separately fixable — but one decision about whether a Host session identity is a UUID probably settles both, so do not fix one and assume the other went with it.
THE CONSEQUENCE IS NOT "the correlation reports absent". It cannot: commands.Applier appends its own prefix under Host's scope BEFORE driving the runtime, so Host's scope always holds a prefix and the walk always finds one. The two states it does reach are these, and they are different failures.
- IN THE LIVE PATH THE OUTCOME IS "unresolved": the walk finds the prefix, and behind it neither the effect — which harness committed in its own scope — nor a superseding fence. Unresolved refuses BOTH settlements, so the command is stuck in applying, completable by nobody and rejectable by nobody. Under the released modules the apply path does not terminate at all. Measured by sessionstoreadapter's TestApplierRunsTheWholeProtocolAgainstTheReleasedStore, which drives two passes and finds the record still applying after both.
- AFTER A TAKEOVER THE OUTCOME IS "abandoned": a successor Host's OpenJournal commits an opening fence above the prefix's epoch, which is precisely the shape the walk reports as abandoned. commands.Application.provesNoEffect admits abandoned, so the settlement that licenses a REJECTION opens over a command whose effect harness durably committed. That is a rejection written over a committed effect, and it is reached by crash-and-takeover rather than in steady state.
THE TWO OUTCOMES ABOVE REMAIN REACHABLE, as a configuration rather than as a wall: they are what happens when the two sides do NOT share a scope, which is still the default and is still the only option when Host's session identity is not the rig's UUID. Closing them needs the identity half of H9 above. This package cannot fix that alone; what changed is that it is no longer the whole of the finding.
Index ¶
- Variables
- func DecodeInputBlocks(body []byte) ([]content.Block, error)
- func Registration(agent sessionwire.AgentID, target department.LaunchTarget) department.Registration
- func Target(rigs Rigs, compatibility department.CompatibilityID, ...) (department.LaunchTarget, error)
- type Adapter
- type BlockDecoder
- type IncapableSessionError
- type Launcher
- type MissingCommittedFieldError
- type Option
- type Rigs
- type UnknownTenantError
- type UnsupportedCommandError
Constants ¶
This section is empty.
Variables ¶
var ErrNilOption = errors.New("harnessruntime: a nil option was supplied")
ErrNilOption is the refusal New returns for a nil option, or for WithBlockDecoder given a nil decoder.
var ErrNoRig = errors.New("harnessruntime: the rig resolver reported success and returned no rig")
ErrNoRig is the refusal returned when a resolver reports success and hands back nothing. It is department.ErrNoRigSession one layer up, and it exists for the same reason: a nil rig would otherwise be dereferenced by NewSession.
var ErrNoRigs = errors.New("harnessruntime: no rig resolver to adapt")
ErrNoRigs is the refusal New returns when it is handed no resolver.
var ErrNoSession = errors.New("harnessruntime: the rig reported success and returned no session")
ErrNoSession is the refusal returned when a rig reports success and hands back a nil controller.
var ErrResumeUnsupported = errors.New(
"harnessruntime: the released subscription cannot be positioned after an event")
ErrResumeUnsupported is the refusal SubscribeCommitted returns for a non-empty resume point.
FINDING H3. event.EventFilter selects loops and event classes and carries no position; nothing else in the released subscription API does either. Serving a positioned subscription from the live tail instead would open a gap between the caller's last durable event and the first live one, and neither side could see it — the caller believes it resumed, and the stream believes it started.
var ErrUnknownTenant = errors.New("harnessruntime: no rig is registered for the tenant")
ErrUnknownTenant is the cause RigPerTenant reports for a tenant it has no rig for. Reach it with errors.Is; the error itself is an *UnknownTenantError.
Functions ¶
func DecodeInputBlocks ¶
DecodeInputBlocks strictly decodes Core's InputRequest (the input-shaped body Host re-presents a create in) and unmarshals its Blocks. It is the default so a product that names no decoder gets the byte-identical body Factory admitted, not a refusal.
Both halves are Core's own strict decoders: sessionwire.InputRequest refuses an unknown member or a body with no blocks, and content.UnmarshalBlocks decodes every Core block type, so a multi-block or non-text message crosses faithfully.
func Registration ¶
func Registration(agent sessionwire.AgentID, target department.LaunchTarget) department.Registration
Registration is the one-line Registrar entry a single-agent product needs.
target, err := harnessruntime.Target(harnessruntime.SharedRig(r), "my-build-1", caps)
...
Registrar: host.RegistrarFunc(func(context.Context) ([]department.Registration, error) {
return []department.Registration{harnessruntime.Registration("assistant", target)}, nil
}),
func Target ¶
func Target( rigs Rigs, compatibility department.CompatibilityID, capabilities department.Capabilities, options ...Option, ) (department.LaunchTarget, error)
Target builds the launch target a harness-backed product registers with Host: department.NewRigTarget over New(rigs, options...), with capabilities.Recovery = {AttemptCloser: true, PersistenceFaults: true} forced on.
THE DECLARATION IS FORCED, AND THEN HELD TRUE AT EVERY BIND. A Target's adapter refuses (and releases) a launched session that cannot honour it: one that is not a session.PersistenceFaultReporter and ResidencyAbandoner, whose fault channel is nil, or whose runtime-command applier is not a runtimecommand.AttemptCloser. So a Target always passes the durable-profile check in host.Compose and never runs a session under a false declaration. Whatever the caller put in capabilities.Recovery is overwritten.
The compatibility id is the product's: it names the runtime build, and a restore onto a different one is refused before launch.
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter is a department.Rig over the released rig API.
func New ¶
New adapts a rig resolver into the department.Rig Host launches through.
Most products want Target instead, which also builds the launch target and declares the recovery capabilities every adapted session offers.
func (*Adapter) NewSession ¶
func (a *Adapter) NewSession(ctx context.Context, request department.RigCreateRequest) (department.RigSession, error)
NewSession launches a session for a create request.
func (*Adapter) RestoreSession ¶
func (a *Adapter) RestoreSession( ctx context.Context, id uuid.UUID, request department.RigRestoreRequest, ) (department.RigSession, error)
RestoreSession relaunches a session over existing durable state.
FINDING H2: every member of the request is dropped, because rig.Rig.RestoreSession takes the UUID and nothing else. The request is still taken — it is the only place an assembly can see what is being restored — and is forwarded to RigForRestore, so the drop happens at a seam a composition can act on rather than silently inside this call.
type BlockDecoder ¶
BlockDecoder turns a command's private body into the content blocks Harness requires.
IT IS A PARAMETER FOR FINDING H7'S REASON. Host carries the body opaque on purpose — department.RuntimeCommand says so in as many words — and runtimecommand.Admitted.Validate refuses an input command carrying no content.Block. Those two cannot both hold, so an encoding has to be named somewhere. The default is DecodeInputBlocks, Core's own InputRequest, which is the body Factory admits and the shape Host re-presents a create's first message in; a product names another only when its bodies are not Core's.
type IncapableSessionError ¶
type IncapableSessionError struct {
Missing []string
}
IncapableSessionError reports a launched harness session missing capabilities Host requires.
func (*IncapableSessionError) Error ¶
func (e *IncapableSessionError) Error() string
type Launcher ¶
type Launcher interface {
NewSession(context.Context, ...rig.SessionOption) (session.SessionController, error)
RestoreSession(context.Context, uuid.UUID) (session.SessionController, error)
}
Launcher is the pair of released rig methods this adapter calls.
It is an interface over *rig.Rig rather than the concrete type, and the assertion below is what makes that safe rather than merely convenient: the released type must satisfy it or this package does not build, so the seam cannot drift away from the API it stands for. What it buys is that every path through NewSession, RestoreSession and bind is reachable from a test, which *rig.Rig is not — assembling one needs loops, primers and a model adapter that no unit test can supply, and 460 lines went untested behind exactly that.
type MissingCommittedFieldError ¶
MissingCommittedFieldError ends a mixed subscription when an enduring delivery lacks the durable fields required for journal repair.
func (*MissingCommittedFieldError) Error ¶
func (e *MissingCommittedFieldError) Error() string
type Option ¶
Option configures an Adapter. An option that returns an error fails New.
func WithBlockDecoder ¶
func WithBlockDecoder(decode BlockDecoder) Option
WithBlockDecoder replaces DecodeInputBlocks as the decoder an input command's body, and a create's re-presented first message, is read with. A nil decoder is refused with ErrNilOption rather than silently keeping the default.
type Rigs ¶
type Rigs interface {
// RigForCreate assembles the launcher a new session launches on.
RigForCreate(context.Context, department.RigCreateRequest) (Launcher, error)
// RigForRestore assembles the launcher a restored session relaunches on. The
// UUID is Harness's identity for the session and is passed so an assembly can
// refuse a session it has no state for.
RigForRestore(context.Context, uuid.UUID, department.RigRestoreRequest) (Launcher, error)
}
Rigs resolves the assembled Rig one launch runs on.
IT IS PER-REQUEST BECAUSE FINDING H1 LEAVES NO ALTERNATIVE. A rig's workspace root and resource storage are fixed by rig.Define, and rig.NewSession accepts neither; department.CreateRequest carries a WorkspaceRoot that Host has just materialized and a storage namespace the session's objects must live under. The only place both can be honoured is the assembly, so the composition root assembles per launch. A shared rig is still expressible — return the same pointer — and the difference is then visible rather than assumed.
func RigPerTenant ¶
func RigPerTenant(rigs map[sessionwire.TenantID]*rig.Rig) Rigs
RigPerTenant resolves each launch onto the rig registered for the request's tenant, and refuses a tenant it does not know with ErrUnknownTenant. The map is copied, so a later write to the caller's map changes nothing.
It is the shape a product with one harness journal store per tenant needs: each rig is defined with rig.WithSessionStore over that tenant's store, and a session can never be launched onto another tenant's journal.
func RigsFunc ¶
func RigsFunc( create func(context.Context, department.RigCreateRequest) (Launcher, error), restore func(context.Context, uuid.UUID, department.RigRestoreRequest) (Launcher, error), ) Rigs
RigsFunc adapts a pair of functions to Rigs. Both are required; New refuses a RigsFunc missing either with ErrNoRigs.
func SharedRig ¶
SharedRig resolves every launch, create and restore alike, onto one rig.
It is the right shape only when nothing about the rig varies per launch: the rig's workspace root and journal store are fixed by rig.Define (H1), so a product that materializes a workspace per session, or journals each tenant into its own store, wants RigPerTenant or RigsFunc instead.
type UnknownTenantError ¶
type UnknownTenantError struct {
TenantID sessionwire.TenantID
}
UnknownTenantError names the tenant RigPerTenant could not resolve.
func (*UnknownTenantError) Error ¶
func (e *UnknownTenantError) Error() string
func (*UnknownTenantError) Is ¶
func (e *UnknownTenantError) Is(target error) bool
Is makes an *UnknownTenantError match ErrUnknownTenant.
type UnsupportedCommandError ¶
type UnsupportedCommandError struct {
CommandID sessionwire.CommandID
Kind string
Reason string
}
UnsupportedCommandError reports an admitted command the released runtime command API cannot express.
func (*UnsupportedCommandError) Error ¶
func (e *UnsupportedCommandError) Error() string