Documentation
¶
Overview ¶
Package model defines the records that flow through Atlas and their on-disk binary encoding.
Everything the engine processes is a keyed Record discriminated by a (ValueType, Intent) pair. Commands are intentions and are never persisted; events are facts and are appended to the write-ahead log. State is the fold of all events. See docs/architecture/data-model.md for the full reference.
The encoding here is the internal log format: a hand-written binary layout behind an explicit version byte (ADR-0009). It is deliberately reflection- free so that encoding an event on the processor hot path does not allocate (invariant I1); callers reuse a single buffer across records.
Index ¶
- Constants
- Variables
- func AppendRecord(dst []byte, r *Record) []byte
- func AppendValue(dst []byte, v Value) []byte
- func CounterOf(key uint64) uint64
- func DecodeValueInto(v Value, src []byte) error
- func NewKey(partition uint16, counter uint64) uint64
- func PartitionOf(key uint64) uint16
- type CompensableValue
- type DataObjectValue
- type DecisionEvaluationValue
- type ElementInstanceValue
- type ElementMapping
- type InboundDeliveryValue
- type IncidentReason
- type IncidentValue
- type Intent
- type JobTimerKind
- type JobValue
- type MessageFlowValue
- type MessageSubscriptionValue
- type OperatorActionKind
- type OperatorActionValue
- type ProcessInstanceState
- type ProcessInstanceValue
- type ProcessMigrationValue
- type Record
- type RecordHeader
- type RecordType
- type SignalSubscriptionValue
- type TimerValue
- type ToolCall
- type Value
- type ValueType
- type VarKind
- type VariableAuditValue
- type VariableValue
Constants ¶
const HeaderSize = 1 + 8 + 8 + 8 + 8 + 1 + 1 + 1 + 2 // PartitionId
HeaderSize is the encoded size of the version byte plus the fixed header.
const MaxIndexedValueBytes = 256
MaxIndexedValueBytes bounds what the variable value index stores per entry. The index exists to find an instance by a business value — an identity, an order number, a case reference — and those are short. The bound is what keeps one pathological write from dominating the index, and it is checked rather than truncated: a truncated key would answer an exact-match query with a wrong row.
const MaxMigrationMappings = 4096
MaxMigrationMappings bounds the element mapping a single migration event may carry. The mapping covers the element indices an instance's *live* records reference — a handful for an ordinary instance, more for a wide multi-instance activity — never the size of either graph, so this is far above anything real and exists to keep a corrupt or hostile record from asking the decoder for an unbounded allocation.
Variables ¶
var ( // ErrShortBuffer is returned when a buffer is too small to hold the record // or payload being read. ErrShortBuffer = errors.New("model: buffer too short") // ErrUnknownVersion is returned when a record's version byte is not one this // build can decode. ErrUnknownVersion = errors.New("model: unknown codec version") )
Functions ¶
func AppendRecord ¶
AppendRecord encodes r and appends it to dst, returning the extended slice. Passing a reused buffer (e.g. buf[:0]) makes encoding allocation-free, which the processor hot path relies on (invariant I1).
func AppendValue ¶
AppendValue appends v's payload bytes (no record header) to dst and returns the extended slice. The state store uses this to persist materialized values; the log uses AppendRecord, which frames a full header around the payload.
func DecodeValueInto ¶
DecodeValueInto decodes a payload from src into the caller-provided v. Unlike DecodeValue it allocates nothing, so a hot-path reader can decode into a reused struct (invariant I1). v must be the concrete type matching the bytes.
func NewKey ¶
NewKey composes a globally unique 64-bit key from a partition id and a per-partition monotonic counter.
func PartitionOf ¶
PartitionOf returns the partition that owns key.
Types ¶
type CompensableValue ¶
type CompensableValue struct {
ProcessInstanceKey uint64
ProcessDefKey uint64
ScopeKey uint64 // FlowScopeKey the compensable activity lived in
ElementInstanceKey uint64 // the completed activity's element-instance key
Seq uint64 // the record's key sequence (log position), set on consume
ElementId int32 // the compensable activity's compiled node id
HandlerNode int32 // the compensation handler's compiled node id
}
CompensableValue is one completed compensable activity: an activity that bore a compensation boundary and finished successfully, retained so a later compensation throw can run its handler (ADR-0103). It is keyed under ScopeKey in completion order (by the event's log position), so a reverse scan yields reverse completion order. ElementId identifies the compensated activity (for activityRef matching); HandlerNode is the compensation handler to activate; Seq carries the record's key sequence back on the consume event so its index entry can be deleted. All fields are fixed-width.
func (*CompensableValue) ValueType ¶
func (*CompensableValue) ValueType() ValueType
type DataObjectValue ¶
type DataObjectValue struct {
ScopeKey uint64 // owning scope (process instance key today)
Name string
State string // BPMN data state; "" when the object declares none
Kind VarKind
Bool bool
Text string // number canonical string, string contents, or canonical JSON; empty otherwise
// ProducerKey names the element instance whose processing wrote this value: the
// activity whose data output association produced it (ADR-0058/0060). It is the
// fact that says *who wrote this*, which no diff of two data-object snapshots can
// recover — on two parallel branches both branches see both writes, so both appear
// to have made them. Data objects take the same answer variables took (ADR-0219).
//
// 0 means no element wrote it: the seeding at instance creation, or a record
// written before attribution existed. Stamped at command time and frozen into the
// event, never recomputed on replay (I6). Append-compatible: an old record ends
// after Text and decodes to 0.
ProducerKey uint64
}
DataObjectValue is a BPMN data object owned by a scope (the process instance root today). It is variable-shaped — a name and a typed value reusing the same VarKind machinery, so a data object can hold a structured VarJSON payload (ADR-0037) — plus a State: the BPMN data state (e.g. "received", "approved"). Its encoding mirrors VariableValue with the extra State string between the name and the kind byte; like a variable it carries genuine runtime data, so it is length-prefixed rather than fixed-size (ADR-0053).
func (*DataObjectValue) ValueType ¶
func (*DataObjectValue) ValueType() ValueType
type DecisionEvaluationValue ¶
type DecisionEvaluationValue struct {
ProcessInstanceKey uint64 // owning instance (the scope this record is keyed under)
ElementInstanceKey uint64 // the business rule task instance that evaluated
ProcessDefKey uint64 // the process definition, to map ElementId onto the diagram
ElementId int32 // interned diagram node id of the business rule task
DecisionId string // the evaluated DMN decision id
InputsJSON string // canonical JSON object: the decision's input context
OutputsJSON string // canonical JSON object: the decision's outputs (name → value)
TraceJSON string // temis trace JSON (which rules fired); "" when none
}
DecisionEvaluationValue is one business rule task's DMN decision evaluation, retained for debugging (ADR-0066). It freezes what the worker computed off the processor goroutine: the input context the decision was given, the outputs it produced, and the temis trace explaining which rules fired. The three payloads are canonical JSON text (InputsJSON and OutputsJSON are objects; TraceJSON is the temis trace tree, or "" when the evaluator produced none — e.g. a literal- expression decision or a remote decision whose worker returned no trace).
It is keyed under its owning ProcessInstanceKey as append-only history — one record per evaluation, never overwritten — so an operator can inspect after the fact exactly how a decision was made. ElementInstanceKey, ProcessDefKey and ElementId locate the business rule task on its instance and diagram. Like a variable it carries genuine runtime data, so its encoding is length-prefixed.
func (*DecisionEvaluationValue) ValueType ¶
func (*DecisionEvaluationValue) ValueType() ValueType
type ElementInstanceValue ¶
type ElementInstanceValue struct {
ProcessInstanceKey uint64
ProcessDefKey uint64
ElementId int32 // INDEX into the compiled graph, not a string
FlowScopeKey uint64 // parent scope (subprocess instance), 0 = root
BpmnElementType uint8 // for fast dispatch
// AttachedToKey links a boundary event's element instance to the host activity
// instance it is attached to (0 for every non-boundary element). It lets an
// interrupting boundary find and terminate its host, and a completing host find
// and disarm its boundary events (ADR-0040).
AttachedToKey uint64
TokenID uint64
ParentTokenID uint64
SourceFlowId int32
// MultiInstance marks an element instance's role in a multi-instance activity
// (ADR-0077): 0 = not multi-instance, 1 = the body (the scope that seeds the
// iterations), 2 = an inner iteration (running the node's real behavior, scoped
// under the body). Append-compatible: an old record without it decodes to 0.
MultiInstance uint8
// EventGatewayKey labels a catch event armed by an event-based gateway with the
// gateway's element-instance key — its race group (ADR-0110). The first armed catch to
// fire cancels every other live instance sharing this key. 0 for every element not armed
// by an event gateway. Append-compatible: an old record without it decodes to 0.
EventGatewayKey uint64
}
ElementInstanceValue is the token-carrying state of one active BPMN element.
func (*ElementInstanceValue) ValueType ¶
func (*ElementInstanceValue) ValueType() ValueType
type ElementMapping ¶ added in v0.3.0
ElementMapping is one element index in the source definition's compiled graph and the index that means the same element in the target's. Indices, never ids: element ids are interned at compile time and never written to the log as text (invariant I5), so the API resolves an operator's element-id overrides into indices before the command is ever submitted.
type InboundDeliveryValue ¶
InboundDeliveryValue advances an external event source's inbound high-water mark (ADR-0075). SourceID is an opaque per-source identifier (e.g. a clio worker + watched subject) the engine never interprets; SourceSeq is that source's monotonic sequence up to which delivery has been applied. Folding these into a per-source high-water mark lets a replayed at-least-once publish be skipped.
func (*InboundDeliveryValue) ValueType ¶
func (*InboundDeliveryValue) ValueType() ValueType
type IncidentReason ¶ added in v0.5.0
type IncidentReason uint8
IncidentReason classifies why an incident was raised, for the resolve path and for an operator reading a list of them.
const ( // IncidentUnclassified is "not recorded": an older record, or a source whose // resolution the element's node type still decides. IncidentUnclassified IncidentReason = 0 // IncidentOverBudget marks an element that was activated but never ran, because // its token had used up the execution budget for this run // (ADR-0272). Resolving it runs the behavior that never ran. IncidentOverBudget IncidentReason = 1 // IncidentTooManyIterations marks a multi-instance activity that asked for more // iterations than the instance budget allows (ADR-0276). Like // IncidentOverBudget it names an element that never ran, so resolving it runs the // behavior — which re-evaluates the count, and parks again if it is still too // large. IncidentTooManyIterations IncidentReason = 2 )
type IncidentValue ¶
type IncidentValue struct {
ProcessInstanceKey uint64
ElementInstanceKey uint64
JobKey uint64
ElementId int32 // the compiled-graph element the token is stuck on (maps to a BPMN element id for the operator)
RaisedAt int64 // unix nanoseconds the incident was raised; read at command time and frozen into the event (I6)
Message string
// Reason says what raised the incident, where the message says it in words. It
// exists because resolving one is not one act: an incident the element never got
// to run past needs its behavior run, where a failed job needs the job re-created.
// The engine used to infer that from the element's node type, which works only as
// long as each node type has one way of getting stuck.
//
// Zero is IncidentUnclassified and means exactly that — nothing was recorded —
// which is what every incident written before this field existed decodes as, and
// what the sources still resolved by node type write today.
Reason IncidentReason
}
IncidentValue is a durable fault attached to the element instance where progress stalled — today, a job whose retries were exhausted (ADR-0061). It is keyed by ElementInstanceKey (one activity holds at most one job, so at most one incident) and points at the job, so resolving the incident can re-activate it. Message is the worker-reported failure reason; it is genuine runtime data, so the value is length-prefixed rather than fixed-size.
func (*IncidentValue) ValueType ¶
func (*IncidentValue) ValueType() ValueType
type Intent ¶
type Intent uint8
Intent is the verb of a record: what transition it represents. The element- instance lifecycle (Activating → Activated → Completing → Completed, plus Terminating → Terminated) is the heart of the model.
const ( // ElementInstance lifecycle (every BPMN element goes through this). IntentActivating Intent = iota IntentActivated IntentCompleting IntentCompleted IntentTerminating IntentTerminated // SequenceFlow. IntentSequenceFlowTaken // Job. IntentJobCreated IntentJobActivated // picked up by a worker IntentJobCompleted IntentJobFailed IntentJobTimedOut IntentJobAssigned // user-task assignee set/changed/cleared (claim/unclaim, ADR-0042) // Timer. IntentTimerCreated IntentTimerTriggered // Message / Signal. IntentSubscriptionCreated IntentMessagePublished // Variable. IntentVariableCreated IntentVariableUpdated // Incident. IntentIncidentCreated IntentIncidentResolved // IntentJobCanceled retires a job whose element was interrupted (e.g. by an // interrupting boundary event) rather than completed by a worker. It applies // like JobCompleted/JobFailed (the job is deleted), and is appended here rather // than beside the other job intents so the existing intents keep their numeric // values on the log. IntentJobCanceled // IntentTimerCanceled retires a timer without firing it — used when a new // version of a process supersedes a prior version's timer start event, so the // old schedule stops (ADR-0051). It applies like TimerTriggered (the timer is // deleted from the due-date index) but, unlike it, drives no side effect: no // instance is created. Appended at the end so existing intents keep their log // values. IntentTimerCanceled // IntentTimerStartArm is a command-only intent (never persisted as an event): // it directs the processor to arm a freshly deployed definition's timer start // events, creating their durable timers and retiring any that a prior version // left armed (ADR-0051). Because commands are not replayed (invariant I6), its // numeric value never reaches the log. IntentTimerStartArm // DataObject. Appended after the existing intents so every prior intent keeps // its numeric value on the log (ADR-0053). Created seeds a data object under a // scope with its declared initial data state; StateChanged transitions the data // state (and, later, the value) as an activity writes to it. IntentDataObjectCreated IntentDataObjectStateChanged // IntentDecisionEvaluated records that a business rule task's DMN decision was // evaluated (ADR-0066). Appended after the existing intents so every prior // intent keeps its numeric value on the log. It is a pure history event: the // worker has already evaluated the decision off the processor goroutine, and the // resulting inputs/outputs/trace are frozen into the event so replay re-applies // them without re-evaluating (invariant I6). IntentDecisionEvaluated // IntentVariableDeleted removes a variable from its scope. It applies like the // inverse of VariableCreated (the variable is deleted rather than put), and is // appended at the end so every prior intent keeps its numeric value on the log. // Its use is dropping an activity-local variable scope when the activity // completes, after I/O output mappings have promoted the values that escape // (ADR-0068): the drop is emitted as one VariableDeleted per local variable, so // replay reproduces it exactly (invariant I6) rather than recomputing it. IntentVariableDeleted // IntentInboundDeliveryApplied advances an external source's inbound high-water // mark (ADR-0075). It applies by upserting the source's last-applied sequence, // and is appended at the end so every prior intent keeps its numeric value on the // log. It rides in the same batch as the message publish it guards, so the // dedup mark and the correlate/start effects it authorizes commit atomically. IntentInboundDeliveryApplied // IntentVariableModify is a command-only intent (never persisted as an event), // like IntentTimerStartArm: it directs the processor to set or overwrite // variables on a running instance's scope from outside the model — an operator // correction to a live instance (ADR-0095). The handler validates the target // scope, then emits a VariableCreated event for a new name or a VariableUpdated // event for an existing one, so the change is a durable, replayable fact recorded // in the instance's variable timeline (the audit trail), not a raw store write. // Because commands are not replayed (invariant I6), its numeric value never // reaches the log, so it is appended at the end without disturbing the persisted // intents' values. IntentVariableModify // IntentVariableAudited records that an external actor set a variable on a running // instance (ADR-0098). It is a pure history event, like IntentDecisionEvaluated: // emitted alongside the VariableCreated/VariableUpdated the override produces, it // freezes who made the change into the log so replay rebuilds the identical audit // trail without re-running the command (invariant I6). Appended at the end so every // prior intent keeps its numeric value on the log. IntentVariableAudited // IntentJobErrorThrown is a command-only intent (never persisted as an event): a // worker reports that its job threw a BPMN error code (ADR-0089). Its handler cancels // the job and propagates the error from the job's element to the nearest matching error // handler — so, unlike IntentJobFailed, it is control flow, not a retry/incident. // Because commands are not replayed (invariant I6), its numeric value never reaches the // log. Appended at the end so every prior intent keeps its numeric value. IntentJobErrorThrown // IntentCompensableRecorded records that a compensable activity (one bearing a // compensation boundary) completed successfully, so a later compensation throw can // run its handler (ADR-0103). It is a pure state event, keyed under the activity's // scope in completion order: applyToState writes it into the compensable index off // the completion event, so recovery rebuilds the identical index (invariant I6). // Appended at the end so every prior intent keeps its numeric value on the log. IntentCompensableRecorded // IntentCompensableConsumed removes a compensable record once its activity has been // compensated (the handler was activated), so it is compensated at most once (ADR-0103). // It carries the record's scope and sequence; applyToState deletes that index entry. IntentCompensableConsumed // IntentOperatorActed records that an operator intervened on a running instance — // completing a parked job by hand, say (ADR-0159). Like IntentVariableAudited it is a // pure history event emitted alongside the events the intervention produces, freezing // who acted and why into the log so replay rebuilds the identical audit trail without // re-running the command (invariant I6). Appended at the end so every prior intent // keeps its numeric value on the log. IntentOperatorActed // IntentPurging is a command-only intent (never persisted as an event), like // IntentTimerStartArm: the retention sweep directs the processor to hard-delete a // finished instance's history (ADR-0115). Its handler emits IntentPurged for the // instance it carries. Because commands are not replayed (invariant I6), its numeric // value never reaches the log. Appended at the end so every prior intent keeps its // numeric value. IntentPurging // IntentPurged removes a finished process instance's history from the state store — // the terminal record and every per-instance family (ADR-0115). It is the durable // delete: applyToState folds it into the removals, so recovery reproduces the purge // (invariants I4/I6). Appended at the end so every prior intent keeps its numeric // value on the log. IntentPurged // IntentConditionRecheck is a command-only intent (never persisted as an event), like // IntentJobErrorThrown: after a batch writes one or more variables in an instance, the // processor schedules a re-check of that instance's armed conditional events (ADR-0137). // Its handler evaluates each armed conditional's FEEL condition over its scope chain and // drives the ones now true to Completing. Because commands are not replayed (invariant // I6) — the fire is the persisted Completing→Completed chain — its numeric value never // reaches the log. Appended at the end so every prior intent keeps its numeric value. IntentConditionRecheck // IntentMigrating is a command-only intent (never persisted as an event), like // IntentPurging: an operator directs the processor to rebind a running instance to // another deployed version of its process (ADR-0162). Its handler re-checks what the // API validated, then emits IntentMigrated for the instance it carries. Because // commands are not replayed (invariant I6), its numeric value never reaches the log. // Appended at the end so every prior intent keeps its numeric value. IntentMigrating // IntentMigrated rebinds a running instance to another deployed version: the // durable fact applyToState folds, carrying both definition keys and the element // mapping it rewrites the instance's live records through (ADR-0162). Appended at // the end so every prior intent keeps its numeric value on the log. IntentMigrated )
type JobTimerKind ¶ added in v0.3.0
type JobTimerKind int32
JobTimerKind distinguishes the holds a job timer releases.
const ( // JobTimerRetry releases a retry backoff (ADR-0111). Zero so pre-existing records, // which are all retry timers, decode correctly. JobTimerRetry JobTimerKind = 0 // JobTimerLease releases a worker's lease when it expires (ADR-0007). JobTimerLease JobTimerKind = 1 )
type JobValue ¶
type JobValue struct {
ProcessInstanceKey uint64
ElementInstanceKey uint64
JobType int32 // interned string → index
Retries int32
// Deadline is the *user task* due date (ADR-0032) — when the work is due, not
// anything about a worker. It is displayed and sorted on, and a job carrying one is
// as pullable as any other. The worker lease lives in LeaseExpiresAt below; the two
// were nearly conflated, and a user task with a due date would then have been
// invisible to every worker.
Deadline int64
Assignee string
// RetryDueDate is the unix-nano instant a failed-but-retryable job may be handed to a
// worker again — a retry backoff (ADR-0111). While it is non-zero and in the future the
// job is held OFF the activatable index; a retry timer clears it when the backoff elapses.
// 0 means "pullable now" (no backoff), which is every job's steady state. Append-compatible:
// an old record without it decodes to 0.
RetryDueDate int64
// LeaseExpiresAt is the unix-nano instant an external worker's claim on this job runs
// out (ADR-0007). While it is non-zero the job is held OFF the activatable index and
// Assignee names the holder; a lease timer clears it when the deadline passes, and the
// job is offered again. 0 means unheld, which is every job's steady state.
// Append-compatible: an old record without it decodes to 0.
LeaseExpiresAt int64
// LeaseEpoch counts how many times this job has been leased, and is the fencing
// token a worker presents when it reports an outcome (ADR-0007's third open item).
// Assignee alone does not fence: two instances of one worker deployment share a
// name, so a completion from a holder whose lease expired would be accepted while
// the second instance still holds the job. The epoch differs per lease, so a
// stale report presents a number the job has moved past and is refused.
//
// It is incremented at command time and written into the JobActivated event, never
// recomputed on replay (I6). Append-compatible: an old record decodes to 0, which
// reads correctly as "never leased".
LeaseEpoch uint64
}
JobValue is service-task work waiting for an external worker. Variables are referenced via the element/instance scope, not embedded here. Assignee is the user-task assignee (ADR-0042): empty for a service-task job, and for a user task it starts at the model's default and is rewritten by claim/unclaim. It is the one variable-length field; for a service job it encodes as a 4-byte zero length and decodes to "" with no allocation, keeping the hot path clean (I1).
type MessageFlowValue ¶
type MessageFlowValue struct {
SenderProcessInstanceKey uint64
ReceiverProcessInstanceKey uint64
ReceiverProcessDefKey uint64
ReceiverElementId int32 // INDEX into the receiver definition's graph
MessageName string
CorrelationKey string
}
MessageFlowValue is one delivered message flow, retained as history so the collaboration replay can show which message crossed to which receiving element and when (ADR-0038). It is produced when a message correlates a catch event or instantiates a message-start process. The receiving element identifies the message-flow edge on the diagram; the sender/receiver instance keys tie the two pools' instances together. ReceiverProcessInstanceKey is 0 when the message created the receiver via a message start event (no instance existed yet).
func (*MessageFlowValue) ValueType ¶
func (*MessageFlowValue) ValueType() ValueType
type MessageSubscriptionValue ¶
type MessageSubscriptionValue struct {
ProcessInstanceKey uint64
ElementInstanceKey uint64
MessageName string
CorrelationKey string // FEEL correlation key, evaluated at subscribe time
// ProcessDefKey and ElementId identify the waiting catch event on its diagram.
// They are carried so that when the subscription correlates, the retained
// message-flow history record can name the receiving element without a lookup
// (ADR-0038); they are set at subscribe time from the element instance.
ProcessDefKey uint64
ElementId int32
}
MessageSubscriptionValue is an open subscription: an element instance (a message intermediate catch event) waiting for a named message whose correlation key matches. Like a variable it carries genuine runtime data (the message name and the evaluated correlation key), so its encoding is length-prefixed rather than fixed-size. The (MessageName, CorrelationKey) pair is the match key a publish scans for; see ADR-0020.
func (*MessageSubscriptionValue) ValueType ¶
func (*MessageSubscriptionValue) ValueType() ValueType
type OperatorActionKind ¶ added in v0.3.0
type OperatorActionKind uint8
OperatorActionKind is the verb of an operator intervention. It is a small closed vocabulary encoded as a byte rather than free text, so the log never carries a model string (invariant I5) and the record stays fixed-width apart from its two genuinely free-form fields.
const ( // OperatorActionCompleteJob is an operator completing a parked job by hand, the // job a worker would otherwise have completed (ADR-0159). OperatorActionCompleteJob OperatorActionKind = iota + 1 // OperatorActionMigrate is an operator rebinding a running instance to another // deployed version of its process (ADR-0162). It is the audit half of the // migration: the rebinding itself is VTProcessMigration, and this record is what // makes it visible — who, why, and at which log position the instance stopped being // the old version and started being the new one, which is what lets the replay // resolve each step through the definition that was in force at it. OperatorActionMigrate )
func (OperatorActionKind) String ¶ added in v0.3.0
func (k OperatorActionKind) String() string
type OperatorActionValue ¶ added in v0.3.0
type OperatorActionValue struct {
ProcessInstanceKey uint64 // owning instance (the scope this record is keyed under)
ElementInstanceKey uint64 // the element the action was applied to; 0 when instance-wide
JobKey uint64 // the job that was acted on; 0 when the action is not job-scoped
Kind OperatorActionKind
Actor string // who performed it; "" when auth is off / unidentified
Reason string // why — free text supplied by the operator
// FromProcessDefKey is the definition the instance was on *before* the action, set
// only by OperatorActionMigrate and 0 for every other kind (ADR-0162). The instance
// record names the definition it is on now; each migration record names the one
// before it, so a chain of migrations reads back as a chain of log-position ranges
// — which is how the replay knows to resolve a step recorded under the old version
// through the old version's compiled graph. Append-compatible: a record written
// before this field decodes to 0, which is what every non-migration record means.
FromProcessDefKey uint64
}
OperatorActionValue records one operator intervention on a running instance for audit (ADR-0159): who forced what, on which element, and why. ADR-0098 made an operator's variable corrections durable and replayable; this is its counterpart for the act itself — completing a parked job by hand — so a step the engine did not drive on its own is never indistinguishable from one it did. It is keyed under its owning ProcessInstanceKey as append-only history, folds into the same instance timeline at its log position, and survives the instance finishing.
The element is referenced by ElementInstanceKey, never by its id: element ids are interned at compile time and never written to the log as text (invariant I5), so a reader resolves the id from the element instance exactly as the timeline already does. Actor is the acting principal's username, or "" when auth is off (single-user) or the caller is unidentified; Reason is the operator's justification, required by the surfaces that mint these records. Both are genuine runtime data, so — like a variable — the encoding is length-prefixed.
func (*OperatorActionValue) ValueType ¶ added in v0.3.0
func (*OperatorActionValue) ValueType() ValueType
type ProcessInstanceState ¶
type ProcessInstanceState uint8
ProcessInstanceState marks where an instance is in its lifecycle. The zero value is Active; the terminal states are only ever stored in the history index (an active instance's record always carries Active). See ADR-0017.
const ( PIActive ProcessInstanceState = iota // running PICompleted // reached its end normally PITerminated // ended by termination )
func (ProcessInstanceState) String ¶
func (s ProcessInstanceState) String() string
type ProcessInstanceValue ¶
type ProcessInstanceValue struct {
ProcessDefKey uint64
State ProcessInstanceState
CompletedAt int64 // unix nano when it reached a terminal state; 0 while active
CreatedAt int64 // unix nano when the instance was activated
CorrelationKey string // message correlation key a message-start instance began with; "" otherwise
// ParentElementInstanceKey is the call-activity element instance that started
// this instance as its child, 0 for a root instance (API/message/timer start).
// A completing child resumes its caller through it (ADR-0076).
ParentElementInstanceKey uint64
// ExpiryDueDate is the due date (unix nano) of this instance's TTL expiry timer,
// 0 when the definition has no TTL (ADR-0085). Stored so completion/termination can
// cancel that timer by key (the instance key) without scanning the timer index.
ExpiryDueDate int64
// CompletedPosition is the log position of the instance's terminal event, set only
// on the history record (0 while active, and 0 on records written before this field,
// ADR-0115). Since the terminal event is an instance's last, it is the instance's
// highest position — so history retention can prove every event is exported
// (CompletedPosition <= exported position) before hard-deleting the instance.
CompletedPosition uint64
// PurgeDueDate is when history retention is scheduled to hard-delete this finished
// instance: CompletedAt + the definition's atlas:historyTtl, 0 when it declares none
// (ADR-0146). Frozen on the terminal event so applyToState can index the instance by
// it — and read back from the purge event to drop that index entry — without either
// fold reading a clock or a definition.
PurgeDueDate int64
}
ProcessInstanceValue is the running instance as a whole — the root scope a process's element instances live under. CreatedAt is set at activation (the activation event's timestamp, so it replays identically — invariant I6); CorrelationKey records the message key a message-start instance was created with ("" for API/timer/none starts, ADR-0035). State and CompletedAt are set only on the history record written when an instance ends (ADR-0017); while live, they carry their zero values (Active, 0).
func (*ProcessInstanceValue) ValueType ¶
func (*ProcessInstanceValue) ValueType() ValueType
type ProcessMigrationValue ¶ added in v0.3.0
type ProcessMigrationValue struct {
ProcessInstanceKey uint64
FromProcessDefKey uint64
ToProcessDefKey uint64
// Mapping is ordered by From and carries no duplicate From, so a fold can binary
// search it and a reader can compare two migrations without normalizing first.
// NewProcessMigration establishes both.
Mapping []ElementMapping
}
ProcessMigrationValue rebinds a running instance from one deployed version of its process to another (ADR-0162). It carries the two definition keys and the fully materialized element mapping — every index the instance's live records reference — because applyToState must reproduce the rebinding from the event alone.
That is the whole design constraint. A fold that *derived* the mapping from the two compiled processes would depend on the matching algorithm's code, so an algorithm improved in a later release would replay an old log into a different state, and two builds of Atlas could disagree about where a token is (invariants I4/I6). The matching runs once, at command time, where it can also be refused.
Element instance keys are *not* in the mapping and never change: a migration rewrites bindings, it does not terminate and recreate, which is what lets variables, data objects, jobs and the whole scope tree ride through untouched — they are keyed by element instance key, not by element index.
func NewProcessMigration ¶ added in v0.3.0
func NewProcessMigration(piKey, fromDefKey, toDefKey uint64, mapping map[int32]int32) ProcessMigrationValue
NewProcessMigration builds a migration value from a from→to index map, ordering the mapping by source index so the encoding of a given migration is byte-identical whatever order the caller discovered it in. A map has no order, and an event whose bytes depend on Go's map iteration is an event whose log is not reproducible.
func (*ProcessMigrationValue) Target ¶ added in v0.3.0
func (v *ProcessMigrationValue) Target(from int32) (int32, bool)
Target returns the index the given source element index maps to, and whether the mapping covers it. An uncovered index is not an error here — the fold leaves such a record alone — because validation at command time is what guarantees no *live* record carries one (ADR-0162).
func (*ProcessMigrationValue) ValueType ¶ added in v0.3.0
func (*ProcessMigrationValue) ValueType() ValueType
type Record ¶
type Record struct {
Header RecordHeader
Value Value
}
Record bundles a header with its typed payload. Value is nil for records whose ValueType carries no payload yet.
func ReadRecord ¶
ReadRecord decodes a single record from the front of src. The payload (if any) is chosen by the header's ValueType.
type RecordHeader ¶
type RecordHeader struct {
Position uint64 // monotonic log position (sequence number)
SourcePos uint64 // position of the record that caused this one (causality)
Key uint64 // entity key
Timestamp int64 // unix nano
RecordType RecordType
ValueType ValueType
Intent Intent
PartitionId uint16
}
RecordHeader is the fixed-size metadata every record carries. SourcePos threads a causal chain through the log: every record points at the record that produced it.
type RecordType ¶
type RecordType uint8
RecordType distinguishes the three kinds of record. Only events are written to the log; commands and rejections are processed but never persisted.
const ( // RecordCommand is an intention submitted to the processor. It may be // rejected and is never persisted. RecordCommand RecordType = iota // RecordEvent is a fact that already happened. It is appended to the log // and is the only record type that is persisted. RecordEvent // RecordCommandRejection records that a command was refused. It is not // persisted; it is surfaced to the submitter. RecordCommandRejection )
func (RecordType) String ¶
func (t RecordType) String() string
type SignalSubscriptionValue ¶
type SignalSubscriptionValue struct {
ProcessInstanceKey uint64
ElementInstanceKey uint64
SignalName string
// ProcessDefKey and ElementId identify the waiting catch on its diagram; set at
// subscribe time from the element instance and carried so the record locates its
// own state index entry (invariant I4), mirroring MessageSubscriptionValue.
ProcessDefKey uint64
ElementId int32
}
SignalSubscriptionValue is an open subscription to a broadcast signal: an element instance (a signal intermediate catch event, later a signal boundary or event subprocess) waiting for a named signal. It is the MessageSubscriptionValue shape (ADR-0020) minus the correlation key — a signal matches by name alone and fans out 1:n, so there is nothing to correlate on (ADR-0088). The SignalName is the sole match key a broadcast scans for.
func (*SignalSubscriptionValue) ValueType ¶
func (*SignalSubscriptionValue) ValueType() ValueType
type TimerValue ¶
type TimerValue struct {
ProcessInstanceKey uint64
ElementInstanceKey uint64
TargetElementId int32
DueDate int64
Repetitions int32 // remaining fires after this one; -1 = infinite (timer cycle), 0 = fire once
// ProcessDefKey names the definition a *start* timer instantiates when it
// fires (ADR-0051). It is 0 for an instance-owned timer (catch/boundary),
// which is identified instead by ProcessInstanceKey/ElementInstanceKey. A
// start timer is precisely one with ProcessInstanceKey == 0 and
// ProcessDefKey != 0; TargetElementId then names its timer-start element.
ProcessDefKey uint64
// JobKey marks a job timer: non-zero means this timer, when due, acts on the job with
// that key rather than firing an element. 0 for every ordinary (catch/boundary/start/
// TTL) timer. Append-compatible: an old record decodes to 0.
JobKey uint64
// JobKind says *which* hold on the job this timer releases, and is only meaningful
// with JobKey set. Two holds can sit on one job at once — a worker leases it and then
// fails it with a backoff (ADR-0007 and ADR-0111) — and each timer must release only
// its own, or the lease expiry would hand the job out early and defeat the backoff.
// Append-compatible: a record written before this field decodes to JobTimerRetry,
// which is what every job timer was.
JobKind JobTimerKind
}
TimerValue is a timer-event subscription. The due-date index makes "which timers are due now" a range scan; see data-model.md.
func (*TimerValue) ValueType ¶
func (*TimerValue) ValueType() ValueType
type ToolCall ¶ added in v0.5.0
type ToolCall struct {
Tool string // the tool activity's BPMN id, as the model was told it
CallId string // the model's own id for this call, carried into the activation
Arguments []VariableValue // written into the activated activity's own scope
}
ToolCall is one tool an agent chose for the next round of an agent-driven ad-hoc subprocess (ADR-0253): the BPMN id of the contained activity to activate, and the arguments it supplies for that activity's declared parameters.
Unlike everything else in this file it is **not a persisted record** — it has no ValueType and no codec. It rides on a job-completion command and on the Completion a worker hands back, and what becomes durable is the activation the engine derives from it: a replay re-activates exactly the same activities without asking the model again (invariant I6). It lives here rather than in engine because the job protocol carries it too, and job must not depend on the engine's implementation.
type Value ¶
type Value interface {
// ValueType reports the discriminator a header should carry for this payload.
ValueType() ValueType
// contains filtered or unexported methods
}
Value is the typed payload of a record. Each implementation owns a fixed binary layout. encode appends to a caller-owned buffer (no allocation when the buffer has spare capacity, satisfying invariant I1); decode reads a payload back, returning ErrShortBuffer if src is truncated.
The methods are unexported on purpose: the set of value types is closed to this package, which keeps encode/decode and the newValue dispatch in lockstep.
type ValueType ¶
type ValueType uint8
ValueType identifies which entity a record concerns and therefore which payload struct its bytes carry. The set mirrors docs/architecture/data-model.md.
const ( VTProcessInstance ValueType = iota // the running instance as a whole VTElementInstance // a single active BPMN element instance (token carrier) VTJob // service-task work for external workers VTTimer // timer-event subscription VTMessageSubscription // waiting for a message (receive task, message event) VTMessage // an incoming message (buffered) VTVariable // a process variable VTIncident // a fault state (job failed, expression error) VTSignal VTError // error-event propagation VTProcessDefinition // a deployed definition // VTMessageFlow is a delivered message flow retained for history: one record // per message that correlated to a catch event or instantiated a message-start // process. Unlike VTMessageSubscription (an open wait, deleted on correlation) // it is never deleted, so the Operations collaboration view can replay the // exchange between pools after the fact (ADR-0038). VTMessageFlow // VTDataObject is a BPMN data object: a typed, named, scope-owned datum with a // declared lifecycle state. Unlike a plain variable it carries a data state // (order [received] → [approved]) whose every transition is a durable event, // so its state history and provenance rebuild from the log (ADR-0053). VTDataObject // VTDecisionEvaluation is one business rule task's DMN decision evaluation // retained for debugging: the input context it was given, the outputs it // produced, and the temis trace explaining which rules fired. Like VTMessageFlow // it is append-only history (one record per evaluation, never deleted), so an // operator can inspect after the fact exactly how a decision was made (ADR-0066). VTDecisionEvaluation // VTInboundDelivery is a per-source high-water mark for an at-least-once inbound // event bridge (ADR-0075): it records that an external source's deliveries up to // a sequence have been applied, so a replayed publish is skipped rather than // re-correlated (which would double-start a message-start process). It is generic // — the engine never interprets the opaque source id — and appended last so every // prior value type keeps its numeric value on the log. VTInboundDelivery // VTVariableAudit is one external variable override retained for audit (ADR-0098): // who set which variable, to what value, on which scope, keyed under its process // instance. Like VTMessageFlow and VTDecisionEvaluation it is append-only history // (one record per variable an operator sets, never deleted), so the "who changed // it" trail survives the instance and rebuilds from the log. Appended last so every // prior value type keeps its numeric value on the log. VTVariableAudit // VTCompensable is one completed compensable activity retained so a later // compensation throw can run its handler (ADR-0103). Written on the activity's // successful completion, keyed under its scope in completion order, and deleted when // compensated or when its scope tears down. Appended last so every prior value type // keeps its numeric value on the log. VTCompensable // VTOperatorAction is one operator intervention on a running instance retained for // audit (ADR-0159): who completed a parked job by hand, on which element, and why. // Like VTVariableAudit it is append-only history keyed under its process instance — // never deleted — so a step a person forced is always distinguishable from one the // engine drove, and the trail rebuilds from the log. Appended last so every prior // value type keeps its numeric value on the log. VTOperatorAction // VTProcessMigration is a running instance being rebound from one deployed version // of its process to another (ADR-0162). It is the only value that rewrites live // state in bulk rather than describing one entity's transition: the instance's // definition key and every element index its live records carry are translated // through the mapping the value holds. That mapping is *in the event* — never // derived during the fold — because a fold that recomputed it would depend on the // matching algorithm's code, so improving that algorithm would silently replay an // old log into a different state (invariants I4/I6). Appended last so every prior // value type keeps its numeric value on the log. VTProcessMigration )
type VarKind ¶
type VarKind uint8
VarKind tags the FEEL values Atlas persists for a variable: the scalars, plus VarJSON for the structured values (objects and arrays) an author or a script produces.
const ( VarNull VarKind = iota // no value VarBool // Bool is meaningful VarNumber // Text is the canonical decimal string VarString // Text is the string contents // VarJSON is a structured value (object or array). Text is its canonical // JSON encoding; it is re-parsed into a FEEL context/list when bound into an // evaluation. Kept as text so the durable record format is unchanged — a new // kind byte over the same length-prefixed Text (ADR-0009, ADR-0037). VarJSON )
type VariableAuditValue ¶
type VariableAuditValue struct {
ProcessInstanceKey uint64 // owning instance (the scope this record is keyed under)
ScopeKey uint64 // the scope the variable was written to (root or a sub-scope)
Actor string // who performed the override; "" when auth is off / unidentified
Name string // the variable that was set
Kind VarKind
Bool bool
Text string // number canonical string, string contents, or canonical JSON; empty otherwise
}
VariableAuditValue records one external variable override for audit (ADR-0098): who set which variable, to what value, on which scope. It is keyed under its owning ProcessInstanceKey as append-only history — one record per variable an operator sets — so the "who changed it" trail folds into the same instance timeline as the variable snapshot at the same log position, and survives the instance finishing. Actor is the acting principal's username, or "" when auth is off (single-user) or the caller is unidentified. Name/Kind/Bool/Text mirror the VariableValue that was written, so the audit row is self-contained. Like a variable it carries genuine runtime data, so its encoding is length-prefixed.
func (*VariableAuditValue) ValueType ¶
func (*VariableAuditValue) ValueType() ValueType
type VariableValue ¶
type VariableValue struct {
ScopeKey uint64 // owning scope (process instance key today)
Name string
Kind VarKind
Bool bool
Text string // number canonical string or string contents; empty otherwise
// ProducerKey names the element instance whose processing wrote this value: the
// task whose job returned it, the activity whose io-mapping or script produced it,
// the catch event the message payload arrived on. It is the fact that says *who
// wrote this*, which no diff of two variable snapshots can recover — on two
// parallel branches both branches see both writes, so both appear to have made
// them (ADR-0219).
//
// 0 means no element wrote it: the instance's start variables, an operator's
// override (ADR-0098 records that one), or a record written before attribution
// existed. Stamped at command time and frozen into the event, never recomputed on
// replay (I6). Append-compatible: an old record ends after Text and decodes to 0.
ProducerKey uint64
// Indexed says this write belongs in the variable value index: its process
// declared the name searchable (atlas:searchable) and the write is at the
// instance's root scope.
//
// It is decided at command time and frozen into the event, never recomputed on
// replay (I6) — and it has to be, because applyToState holds the record and
// nothing else: it cannot ask a compiled process whether a name is searchable.
// Append-compatible: a record written before the index existed ends after the
// producer key and decodes to false, which is honest — such a write is not in the
// index.
Indexed bool
}
VariableValue is a process variable: a named value owned by a scope (the process instance root for now). Unlike the graph-derived payloads, a variable carries genuine runtime data (its name and contents), so its encoding is length-prefixed rather than fixed-size.
func (*VariableValue) IndexText ¶ added in v0.5.0
func (v *VariableValue) IndexText() (string, bool)
IndexText is the value's canonical text for the variable value index, and whether it can be indexed at all.
The index is an ordered key-value index, so it answers equality and prefix over a byte string and nothing else. That admits the scalars and excludes the rest: a structured value is not something anyone searches for by its exact encoding, a NUL byte would break the terminator the exact match relies on, and a value past MaxIndexedValueBytes is refused rather than cut short.
The search side calls this on the query's value, so a query and a write agree on the bytes by construction rather than by two implementations happening to match.
func (*VariableValue) ValueType ¶
func (*VariableValue) ValueType() ValueType