Documentation
¶
Overview ¶
Package runtimecommand is the seam between a Host-admitted public command and the UUID-keyed runtime command Harness has always dispatched.
Two identities meet here and must never be confused for one another.
The public CommandID is Host's retry-stable identity. It is an OPAQUE bounded UTF-8 string: Harness validates exactly what Core's canonical sessionwire/v1 CommandID validates — non-empty, at most MaxCommandIDBytes bytes, valid UTF-8 — and does nothing else with it. It is never parsed as a UUID, never truncated, and never substituted for the runtime id; a public id that happens to render a valid UUID is still just a string here.
The parity with Core is a CORRECTNESS requirement, not tidiness. The admission authority upstream accepts an id under those three rules; an applier that additionally rejected, say, a leading space or an embedded tab would refuse a command that was already durably admitted, forever. It could only sit pending to its apply deadline and become rejected, with the refusal invisible on the admission side. Do not add a rule here that Core does not have.
The RuntimeCommandID is the core/uuid.UUID Harness stamps on command headers and on the events those commands cause. Host allocates it ONCE, when it admits the command, and hands it back on every delivery. Harness does not allocate a replacement: a second UUID for the same admitted command would silently split one command's correlation across two identities, and the split would be invisible — the events would look perfectly well formed under either id.
Application is the durable bridge between them. It is written to the session's PRIVATE journal, under the lease epoch that authorizes the effect, BEFORE any runtime-visible effect begins. That ordering is the crash-safety property: a delivery that finds an existing prefix knows the command was already applied and replays the original disposition instead of applying it a second time.
Index ¶
- Constants
- type Admitted
- type Application
- type Applier
- type AttemptCloser
- type AttemptID
- type CapabilityUnavailableError
- type Closure
- type ClosureNotAuthorizedError
- type ClosureResult
- type CommandDisposition
- type CommandID
- type Disposition
- type DispositionKind
- type DispositionUnsupportedError
- type EffectScan
- type EnduringEffectError
- type Kind
- type LeaseLostError
- type MappingConflictError
- type Provider
- type StaleLeaseEpochError
- type ValidationError
Constants ¶
const MaxAttemptIDBytes = 256
MaxAttemptIDBytes bounds a dispatch-attempt identity. It is Core's MaxIDBytes, which is what the durable envelope's attempt_id field is bounded by; the two must not drift, for exactly the reason CommandID's bound must not — an attempt Host has already durably recorded must not become unwritable here.
const MaxCommandIDBytes = 256
MaxCommandIDBytes bounds a public CommandID. The id is opaque, so the only thing Harness can assert about it is that it is bounded: an unbounded id would enter a durable idempotency key and a journal record body. It is Core's MaxIDBytes; the two must not drift.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Admitted ¶
type Admitted struct {
// CommandID is Host's opaque public identity for this command.
CommandID CommandID
// RuntimeCommandID is the once-allocated UUID this command's runtime headers
// and events carry. Harness uses it verbatim.
RuntimeCommandID uuid.UUID
// Kind selects the runtime dispatch path.
Kind Kind
// LeaseEpoch is the session-lease epoch this command was admitted against. A
// record admitted under a superseded epoch is refused.
LeaseEpoch uint64
// Blocks is the input payload: REQUIRED for KindInput, OPTIONAL for KindCreate
// (whose first message it carries, if there is one), and forbidden for every
// other kind — a payload a kind cannot carry would be silently dropped.
Blocks []content.Block
// GateResponse is the answer a KindGateResponse command applies, required for
// that kind and forbidden for every other, for the same reason as Blocks. It must
// name a gate. Everything else about it — whether the gate is open, whether the
// action is one of its controls, whether its values satisfy the gate's schema,
// and whether its source is one a caller may assert — is decided by the session
// at application time, because only the session knows the gate.
GateResponse *gate.GateResponse
// AttemptID is Host's immutable identity for the ONE authorized dispatch
// attempt this delivery belongs to. It is OPTIONAL for input and interrupt, and
// the option is the compatibility contract: a legacy admitted record carries
// none, and an applier handed one writes NO disposition, so a legacy session's
// journal is byte-for-byte what it was. It is REQUIRED for KindGateResponse,
// which has no legacy records: without it the answer would apply and never
// settle. A non-empty id is validated exactly as the durable boundary
// validates it.
//
// KindCreate and KindRestore are on INPUT AND INTERRUPT's footing, not
// gate_response's: optional. The requirement gate_response carries is a
// last-resort guard for a kind whose whole purpose is settlement, and it is not
// what makes a create settle — Host dispatches these two under an attempt, and
// the authority that decides whether a dispatch is authorized is Host's
// admission rather than this validator. A record with no attempt is a shape
// that writes no disposition, which is a legacy shape and not a malformed one;
// refusing it here would turn an old Host's create into a hard refusal instead
// of the unsettled command it already is.
AttemptID AttemptID
// Principal is Factory's assertion about the verified sender. Harness carries
// it unchanged and never places it on a model-facing content.Message.
Principal *coresessionwire.Principal
// Metadata is app-defined message data, permitted only on create and input.
// It is audit-only unless a presenter renders it.
Metadata coresessionwire.MessageMetadata
}
Admitted is one command Host has ALREADY admitted, handed to Harness for application. Harness re-validates it — an admitted record it cannot durably correlate is refused rather than applied on trust — but it never re-derives an identity from it.
func (Admitted) Application ¶
func (a Admitted) Application() Application
Application returns the durable correlation for this admitted record. It copies the identities rather than deriving new ones. This prefix is deliberately bodiless; Principal and Metadata live on the intent and disposition descriptor.
func (Admitted) DispositionFor ¶ added in v0.34.0
func (a Admitted) DispositionFor(kind DispositionKind, grantEpoch uint64) CommandDisposition
DispositionFor builds the durable disposition this admitted record's attempt resolved to, under grantEpoch — the journal grant the applier ACTUALLY held.
Both epochs come from that grant and neither is copied from Admitted.LeaseEpoch. The applier refuses an admitted record whose epoch is not the one it holds, so the two agree on every reachable path; taking the held grant rather than the record's claim is what keeps that true if the refusal is ever relaxed.
type Application ¶
type Application struct {
CommandID CommandID `json:"command_id"`
RuntimeCommandID uuid.UUID `json:"runtime_command_id"`
LeaseEpoch uint64 `json:"lease_epoch"`
// Kind is the admitted command's kind. It is part of the correlation because
// the released SessionStore reader correlates on it: a prefix whose kind
// disagrees with the inbox record's resolves CONFLICTED, not applied. Omitting
// it would make every application unmatchable by the counterparty.
Kind Kind `json:"command_kind"`
}
Application is the private durable correlation an applier writes BEFORE the effect: the public id, the one runtime id it maps to, and the lease epoch the application ran under. It is the whole content of the application prefix — no payload, no user content — because its only job is to answer "was this public command already applied, and under which runtime identity?".
Known gap, recorded so it is a decision rather than an oversight: it carries no loop id. KindInput dispatches to the session's ACTIVE loop, and which loop that was is not recoverable from this record — neither recovery nor an audit can say where an admitted command landed. Nothing in the current contract needs it (the runtime id correlates the events, and the events carry the loop), but a per-loop-addressed admitted command would need this field, and adding it later changes the persisted body and therefore every existing record's fingerprint.
func (Application) Validate ¶
func (a Application) Validate() error
Validate fails closed on a correlation that cannot have been produced by a valid admitted record. It is the decode-side guard for a prefix read back from storage.
type Applier ¶
type Applier interface {
// ApplyRuntimeCommand durably records the application prefix and then applies
// the command, returning the disposition. A duplicate delivery returns the
// original disposition and applies nothing.
//
// A non-nil error does not license a retry. The prefix is written BEFORE the
// effect, so an error raised by the effect leaves a durable prefix behind and
// every later delivery deduplicates against it. Re-deliver and read
// Disposition.Duplicate to learn what happened; do not assume an error means
// nothing was recorded.
//
// A returned Disposition also does not mean the application will SETTLE. The
// durable settlement correlation resolves an application by finding the public
// event adjacent to its prefix, and adjacency is not guaranteed for any command
// kind: another writer's record in that slot leaves the application unresolved
// forever. Unresolved never licenses a rejection, so nothing is applied twice and
// nothing is settled over — but a caller that BLOCKS on an application settling
// blocks indefinitely. Treat the Disposition as the answer, not as a promise that
// a later durable query will agree.
ApplyRuntimeCommand(context.Context, Admitted) (Disposition, error)
}
Applier is the SEGREGATED runtime-command capability. It is deliberately not a method on session.Session: almost every Session implementation — every test double, every adapter, the TUI's view — will never apply an admitted command, and widening the base contract would force all of them to grow a method they cannot honor. A caller obtains one through Provider.
type AttemptCloser ¶ added in v0.34.0
type AttemptCloser interface {
// CloseAttempt durably records not_applied for the named attempt, under THIS
// runtime's own grant, which must be strictly later than the attempt's.
//
// When the attempt's OWN disposition is already durable it writes nothing and
// succeeds, naming it in ClosureResult.AlreadyDisposed: the caller settles from
// that evidence (as of v0.37.0; earlier releases refused this shape).
//
// It refuses rather than closing when the runtime holds no live grant, when its
// grant is not strictly later, when the journal's own prefix binds the command
// to another mapping, and — the guard that matters most — when the journal holds
// an enduring event caused by that runtime command ANYWHERE IN THE JOURNAL,
// because a tombstone over a committed effect is the one error this protocol
// cannot recover from. It also refuses a journal it cannot fully read.
//
// "Anywhere" is deliberate and is not a looser restatement of "after the
// prefix". An event caused by that runtime id cannot exist unless the command
// was dispatched, so its POSITION proves nothing extra; and a guard that ignored
// an event before the prefix would be choosing, in the one journal shape nobody
// can explain, to tombstone rather than to refuse. Refusing an odd journal costs
// liveness; tombstoning a committed effect is unrecoverable.
CloseAttempt(context.Context, Closure) (ClosureResult, error)
}
AttemptCloser is the SEGREGATED recovery-closure capability: it writes the not_applied tombstone for an attempt a previous runtime never finished.
It is a separate interface from Applier, discovered by assertion, exactly as session.LeaseEpochReporter is. Folding CloseAttempt into Applier would break every existing implementer of that interface — the composed Host adapter, the test doubles — for a capability only a recovery path uses. A caller obtains one with
closer, ok := applier.(runtimecommand.AttemptCloser)
and an implementation that cannot honor it refuses at the call with a *CapabilityUnavailableError rather than advertising a closure it cannot write.
type AttemptID ¶ added in v0.34.0
type AttemptID string
AttemptID is Host's immutable identity for ONE authorized dispatch attempt. Like CommandID it is an OPAQUE bounded UTF-8 string: Harness never parses it, never derives anything from its shape, and validates exactly what the durable boundary validates.
It is the correlation key for settlement. A disposition names the attempt rather than the command because a command may be attempted more than once — a successor closing a predecessor's attempt writes about THAT attempt — and evidence about one attempt is not evidence about another.
type CapabilityUnavailableError ¶
CapabilityUnavailableError reports that ApplyRuntimeCommand was called on a session that does not advertise the capability. A caller that went through Provider.RuntimeCommands never sees it; it exists so a caller that reached the method by a bare type assertion fails loudly instead of silently applying a command with no durable correlation.
func (*CapabilityUnavailableError) Error ¶
func (e *CapabilityUnavailableError) Error() string
type Closure ¶ added in v0.34.0
type Closure struct {
CommandID CommandID
RuntimeCommandID uuid.UUID
Kind Kind
AttemptID AttemptID
AttemptJournalEpoch uint64
}
Closure is a successor runtime's request to close an unfinished attempt.
It carries NO author grant. The closer stamps that from the live lease it holds, which is the whole point of the capability: a caller-supplied author epoch would be a caller-authored proof, and the one thing a tombstone must not be is something a caller can assert.
Kind is the ADMITTED command's kind and Validate accepts every one of the five — input, interrupt, gate_response, create and restore. It tracks Kind.Valid rather than holding its own narrower set, and that is load-bearing: a kind a predecessor can apply but a closure refuses is a command no successor can ever close, so the record sits applying for good. That is precisely what happened to create and restore before v0.36.0, and it is the second half of the same defect — the first being that the applier refused them outright.
type ClosureNotAuthorizedError ¶ added in v0.34.0
type ClosureNotAuthorizedError struct {
AttemptID AttemptID
AttemptJournalEpoch uint64
Current uint64
Held bool
}
ClosureNotAuthorizedError reports a closure offered without a strictly later grant. Held distinguishes "this runtime holds no live grant at all" from "its grant is not later than the attempt's": the first is a lost or released lease, the second is a runtime trying to close its OWN attempt, and conflating them would send an operator looking for the wrong failure.
func (*ClosureNotAuthorizedError) Error ¶ added in v0.34.0
func (e *ClosureNotAuthorizedError) Error() string
type ClosureResult ¶ added in v0.34.0
type ClosureResult struct {
Sequence uint64
Appended bool
AlreadyDisposed DispositionKind
}
ClosureResult reports where the recovery closure landed. Appended=false means an identical closure was already durable — a redelivered recovery, not a second tombstone — and Sequence is the ORIGINAL append's.
AlreadyDisposed is non-empty when the ATTEMPT'S OWN disposition was already durable before any closure was considered: the predecessor recorded its outcome and died before the store settled it. Nothing is written — there is nothing to close — and Sequence is that disposition's. The caller settles from the evidence as for any disposed attempt. It is a success, not a refusal, because the alternative answers (an idempotency collision with the durable record, or an enduring-effect refusal once the effect the disposition promised lands) would halt a successor on a command whose outcome is already on the record.
type CommandDisposition ¶ added in v0.34.0
type CommandDisposition struct {
CommandID CommandID `json:"command_id"`
RuntimeCommandID uuid.UUID `json:"runtime_command_id"`
Kind Kind `json:"command_kind"`
LeaseEpoch uint64 `json:"lease_epoch"`
AttemptID AttemptID `json:"attempt_id"`
AttemptJournalEpoch uint64 `json:"attempt_journal_epoch"`
Disposition DispositionKind `json:"disposition_kind"`
}
CommandDisposition is the runtime's durable, bodiless statement about ONE attempt.
LeaseEpoch is the grant the AUTHOR actually held when it wrote the record, and getting it right matters more than it looks. Harness bypasses the durable store's own JournalWriter — it encodes the envelope and appends the bytes itself — so no field here is store-stamped and the reader treats this value as a CLAIM. It cross-checks it against the nearest preceding opening fence and fails closed when the two disagree, which reads exactly like a forged epoch. A wrong LeaseEpoch does not degrade settlement; it stops it.
AttemptJournalEpoch is the grant the ATTEMPT was authorized under, which the settlement verifier compares against its own immutable attempt record. For the three kinds authored by the attempt's grant the two epochs are EQUAL; for a recovery closure LeaseEpoch is strictly greater.
The struct tags are the JSON body this package's codec produces for fingerprinting and replay. They are NOT the durable frame: the frame is the store's binary envelope, whose field tags are its own.
func (CommandDisposition) Validate ¶ added in v0.34.0
func (d CommandDisposition) Validate() error
Validate fails closed on a disposition the durable boundary or the settlement verifier would refuse. It deliberately enforces the AUTHOR-GRANT rule as well as the per-field shape: the envelope codec cannot check it (it never sees the attempt record), so a record with the wrong grant relationship would encode and append cleanly and then be refused at settlement, where the refusal reads as an attack rather than as a writer bug.
type CommandID ¶
type CommandID string
CommandID is the public, retry-stable command identity Host owns. It is opaque to Harness; see the package doc for why it is never parsed as a UUID.
func (CommandID) Validate ¶
Validate reports whether id is a well-formed opaque public identity: non-empty, at most MaxCommandIDBytes bytes, and valid UTF-8. That is the WHOLE rule, and it is deliberately Core's rule byte for byte — see the package doc for why an extra rule here strands an already-admitted command. Everything else about the id — format, meaning, structure, whitespace, control characters — belongs to Host.
Nothing downstream needs a stricter id in the CHARACTER dimension: the durable idempotency key namespaces the public id rather than filtering it (journal.CommandApplicationRecord), and the record body is JSON-encoded, so no byte value is unsafe anywhere on the path.
The LENGTH dimension is enforced by the SAME authority, not by a second one, and that is structural rather than test-enforced. The application prefix is persisted as the released SessionStore's EnvelopeKindApplicationPrefix, whose identity field carries the RAW public id and is validated by Core's own sessionwire.CommandID.Validate. No prefix, namespace, or derived record id consumes any of the 256-byte budget, so Harness's acceptance and the durable boundary's acceptance cannot drift: they are one rule applied twice.
type Disposition ¶
type Disposition struct {
// CommandID echoes the public id this disposition answers for.
CommandID CommandID
// RuntimeCommandID is the durable runtime identity of the application.
RuntimeCommandID uuid.UUID
// PrefixSequence is the journal sequence of the application prefix. For a
// duplicate it is the ORIGINAL append's sequence, never a new one.
PrefixSequence uint64
// Duplicate reports that this delivery applied nothing because the command was
// already applied.
Duplicate bool
// Interrupted reports, for KindInterrupt, whether a running turn was cancelled.
// It is the TRANSIENT outcome of an application, not part of the durable prefix,
// so a duplicate delivery always reports false: the prefix records the
// correlation, never the outcome. Read it only alongside Duplicate.
Interrupted bool
}
Disposition is what an application resolved to. A duplicate delivery reports the ORIGINAL disposition — the same runtime id and the same prefix sequence the first delivery reported — with Duplicate set, so a caller can distinguish "applied by this call" from "already applied" without being able to confuse the two.
type DispositionKind ¶ added in v0.34.0
type DispositionKind string
DispositionKind is the closed vocabulary of durable runtime dispositions. The four spellings are DURABLE BYTES, not labels: they are written verbatim into the journal frame and compared verbatim by the settlement verifier, so renaming one is a wire change and not a refactor.
const ( // DispositionApplied is the narrow statement in this file's header doc: under // the attempt's grant, the runtime durably recorded that it accepted the command // into its execution path. // // For an INPUT (and a create's first message) delivered to a native loop it is // also a durable DEBT, as of v0.37.0: the record is written by the loop actor // after it takes the input and before the input can cause any effect, and it is // owed until a durable event caused by the input resolves it. A runtime that dies // or shuts down first leaves it to restore, which re-offers every applied input // with no caused event. It does NOT say the input will run: an idle start failure // (TurnRejected), a failed turn ahead of it (InputCancelled/TurnFailed) or a // retraction discharges the debt visibly without running it. DispositionApplied DispositionKind = "applied" // DispositionNoOp is an explicit SUCCESSFUL application with no effect — an // interrupt of an idle session. It settles applied. Reject-before-dispatch and // an applied no-op are different outcomes and must never be merged. DispositionNoOp DispositionKind = "no_op" // DispositionRefused is this runtime's own statement, under the attempt's OWN // grant, that it did not accept the command. It exists for LIVENESS: a command // whose effect failed after its prefix under a still-live lease has no other // terminal arm, because not_applied requires a strictly later grant and a // healthy Host never turns its lease over. Without it such a command sits // applying forever. DispositionRefused DispositionKind = "refused" // DispositionNotApplied is a SUCCESSOR's recovery closure, authored by a // strictly later grant. It is the tombstone late dispatch must consult. It is // never a substitute for a refusal and nothing may derive one from the other: a // refusal is a live runtime's own answer, a closure is a successor's conclusion // about a runtime that is gone. DispositionNotApplied DispositionKind = "not_applied" )
func (DispositionKind) Valid ¶ added in v0.34.0
func (k DispositionKind) Valid() bool
Valid reports whether k is one of the four known kinds.
type DispositionUnsupportedError ¶ added in v0.34.0
DispositionUnsupportedError reports that an attempt-bearing command reached a session whose durable log cannot record a disposition. It is raised BEFORE any durable write: a command applied with no evidence sits applying forever, settleable by nobody, which is strictly worse than a refusal Host can retry elsewhere.
IT LIVES HERE, BESIDE CapabilityUnavailableError, BECAUSE HOST IS TOLD TO ACT ON IT. It was originally declared in the unexported runtime package, which made that instruction unfollowable: a consumer outside this module could only recognise the refusal by matching its message text, and a message is not an API. A typed refusal a caller cannot name is a refusal a caller cannot distinguish from a transport failure, and the difference matters — nothing durable was written, so the command may be re-offered elsewhere.
func (*DispositionUnsupportedError) Error ¶ added in v0.34.0
func (e *DispositionUnsupportedError) Error() string
type EffectScan ¶ added in v0.34.0
type EffectScan struct {
PrefixSeq uint64
DurableRuntimeID uuid.UUID
DurableKind Kind
EffectFound bool
EffectSeq uint64
// DispositionSeq is non-zero when the journal holds a disposition naming the
// command's CommandID, and Disposition is the FIRST such record, whatever attempt
// it names. The protocol writes at most one disposition per command (every
// outcome is terminal once settled, and a successor closes only an attempt with
// none), so the first is the one. A scanner that predates the fields leaves them
// zero, which only costs the closer its already-disposed answer.
DispositionSeq uint64
Disposition CommandDisposition
}
EffectScan is what a privileged journal scan found about one command identity. It is the evidence the closer's second guard rests on, so every member is a fact about durable records and none is a conclusion.
PrefixSeq is zero when the journal holds NO application prefix for the command, which is the ordinary shape of an attempt that never reached its effect. DurableRuntimeID and DurableKind are the mapping that prefix holds, so a closure offered against a different mapping can be refused rather than tombstoning somebody else's command.
type EnduringEffectError ¶ added in v0.34.0
type EnduringEffectError struct {
AttemptID AttemptID
CommandID CommandID
RuntimeCommandID uuid.UUID
PrefixSeq uint64
EffectSeq uint64
}
EnduringEffectError reports that the journal holds an enduring event caused by the attempt's runtime command, so the predecessor's EFFECT committed even though its disposition did not.
PrefixSeq and EffectSeq are WHERE THE SCAN FOUND THINGS, not an ordering claim. PrefixSeq is zero when the journal holds no application prefix for the command, and EffectSeq is NOT guaranteed to be greater than it — the scan refuses on an event caused by that runtime id at any sequence, deliberately. Read each as a locator; do not read a relationship between the two.
This is the case the idempotency guard cannot catch. A successor's not_applied collides with a predecessor's durable APPLIED disposition and fails closed for free, because both key on the attempt id — but if the predecessor's effect committed and its disposition append then failed, there is no colliding record and nothing but this scan stands between a successor and a tombstone over a real effect.
func (*EnduringEffectError) Error ¶ added in v0.34.0
func (e *EnduringEffectError) Error() string
type Kind ¶
type Kind string
Kind is the bounded set of admitted commands Harness can apply through this seam. It is deliberately small: every kind here must have an existing runtime dispatch path, so a new kind is a new implementation, never a new default.
const ( // KindInput applies admitted user input to the session's active loop. KindInput Kind = "input" // KindInterrupt applies a session-wide interrupt. KindInterrupt Kind = "interrupt" // KindGateResponse answers one open gate through the session's own gate-response // path (the one Session.RespondGate uses), so every refusal RespondGate makes — // including its refusal of a classifier-sourced response — holds here as well. // The spelling is the admitted record's: Factory admits "gate_response", and the // settlement reader compares the kind as a string. // // It REQUIRES an AttemptID (see Admitted.Validate): an answer applied with no // attempt writes no disposition, which is the defect this kind exists to fix. // // ONE-WAY UPGRADE. Once a session journal holds ANY gate_response application — // whatever it resolved to, applied, no_op or refused; a no_op against a gate that // never existed is enough — harness v0.34.0 and older can neither replay nor // reopen that journal: OpenJournal and replay both fail closed with // `journal: encode command application: runtimecommand: invalid Kind: unknown kind // "gate_response"`. The failing check is the Marshal-side validation // (v0.34.0 pkg/journal/record_json.go:191, MarshalCommandApplicationRecord), // reached from the replay hydration of the prefix (v0.34.0 // pkg/sessionstore/replay.go:422). Nothing is lost — it fails closed — but the // session is stranded until a runtime at v0.35.0 or later opens it. Do not roll a // runtime back below v0.35.0 once it has applied a gate_response. KindGateResponse Kind = "gate_response" // KindCreate is the command that brings a session into existence on this // runtime. By the time it reaches this seam the session IS resident — Host makes // it resident before it dispatches — so what remains to apply is the first // message the create carries, if it carries one. // // It MAY carry Blocks and it is the only kind besides KindInput that may: a // create with a first message applies that message exactly as an input does, // through the same audit-intent-first ordering, and a create with none applies // nothing and still settles applied. // // The spelling is the admitted record's, byte for byte // (factory/internal/command/kind.go: "create"). // // WHY IT EXISTS. Factory admits EVERY session's first command as a create. // Before v0.36.0 this seam named three kinds, so Host's adapter refused the // create after Host had already durably begun its attempt: no disposition frame // was ever written, the store could never settle the record, a consumer blocked // at that command and never advanced its cursor, and no successor could close it // either because Closure.Validate refused the same kind. The session existed and // the agent was resident, and the user could never talk to it. // // ADAPTER OBLIGATION. An adapter that decodes a command's payload only for // KindInput will cross a create with NO Blocks. This validator accepts it, the // applier sends nothing, and the command settles applied — the user's first // message dropped in silence, with the record looking perfectly settled. Harness // cannot detect it: an undecoded payload and an absent one are the same value // here, and refusing a bare create would make every legitimately idle create // unsettleable. DECODE THE PAYLOAD FOR A CREATE THAT CARRIES ONE, and refuse a // create whose payload you have no decoder for, exactly as you would an input. KindCreate Kind = "create" // KindRestore is the command that resumes a session that is not resident. Like // a create, residency is Host's work and is already done here, and unlike a // create there is no first message: a restore carries NO Blocks, because a // restored session already holds its conversation and a payload riding one would // be silently dropped. // // The spelling is the admitted record's, byte for byte // (factory/internal/command/kind.go: "restore"). KindRestore Kind = "restore" )
func (Kind) Valid ¶
Valid reports whether k is one of the five admitted kinds.
The set is Factory's durable vocabulary (factory/internal/command/kind.go) and must stay equal to it. A kind Factory admits and this set omits is a command that can never settle — see KindCreate for what that cost.
ONE-WAY UPGRADE, exactly as gate_response was. Once a session journal holds any create or restore application prefix or disposition frame, harness v0.35.0 and older can neither replay nor reopen that journal: the Marshal-side validation fails closed with `journal: encode command application: runtimecommand: invalid Kind: unknown kind "create"` (or "restore"). Nothing is lost — it fails closed — but the session is stranded until a runtime at v0.36.0 or later opens it. Do not roll a runtime back below v0.36.0 once it has applied a create or a restore.
type LeaseLostError ¶
LeaseLostError reports that the applier's session lease is no longer held, so no effect may be applied under it.
func (*LeaseLostError) Error ¶
func (e *LeaseLostError) Error() string
type MappingConflictError ¶
type MappingConflictError struct {
CommandID CommandID
// RuntimeCommandID is the id the offered delivery carried.
RuntimeCommandID uuid.UUID
// DurableRuntimeID is the id the durable application prefix holds. It is zero
// when the prefix could not be read; see Cause.
DurableRuntimeID uuid.UUID
// Kind is the kind the offered delivery carried, and DurableKind the kind the
// durable prefix holds. They are compared because the RELEASED reader compares
// them: a prefix whose kind disagrees with the inbox record's resolves
// CONFLICTED. Ignoring the kind here would have Harness report already-applied
// for a shape the counterparty refuses.
Kind Kind
DurableKind Kind
// Sequence is the journal sequence of the durable prefix.
Sequence uint64
Cause error
}
MappingConflictError reports that the public CommandID is already durable under a mapping this delivery may not be applied against. It covers two situations that must both fail closed, and the two are distinguishable — do not collapse them.
A CONFLICT: the durable prefix was read and binds this public id to a DIFFERENT RuntimeCommandID. That is never a legitimate retry, because a retry of an admitted command carries the mapping Host allocated once. DurableRuntimeID is set.
An UNREADABLE prefix: the durable frame at Sequence could not be read at all, so the mapping is UNKNOWN. DurableRuntimeID is then zero and Cause is the read failure. Reporting this as a duplicate would tell Host "already applied" about a command that may never have been applied.
Error() distinguishes them. The unreadable arm must not name a mapping: printing an all-zero DurableRuntimeID as though it had been read is a message that contradicts its own Cause, and it sends a reader hunting for a runtime command that does not exist.
func (*MappingConflictError) Error ¶
func (e *MappingConflictError) Error() string
func (*MappingConflictError) Unwrap ¶
func (e *MappingConflictError) Unwrap() error
type Provider ¶
Provider is implemented by a session that MAY be able to apply admitted runtime commands. RuntimeCommands reports the capability: ok is false — with a nil Applier — for a session with no durable, deduplicating application-prefix log, which includes a headless/no-persistence session.
The two-result form is the point, exactly as it is for the committed-public-event capability: a single-result form would hand back an applier that fails only after Host has already acknowledged the command as accepted.
type StaleLeaseEpochError ¶
StaleLeaseEpochError reports an admitted record whose lease epoch is not the epoch the applier currently holds. Applying it would let a superseded owner's admission take effect under a lease it no longer holds.
func (*StaleLeaseEpochError) Error ¶
func (e *StaleLeaseEpochError) Error() string
type ValidationError ¶
ValidationError reports a malformed admitted record or correlation.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string