Service Wire Protocol¶
Channel·Transport topic table of contents · Spec table of contents · Previous: 05. Transport Liveness
What this chapter answers — the byte format and command list exchanged between nodes.
Contract owner —
framework/runtime/protocol/service-wire-v1.schema.jsonis the sole normative authority that fixes command IDs, frame layout, field relationships, and validation order. The byte layout other implementations depend on for interoperability (frame order, field order, size limits, enum values) is contract description in this chapter — when it diverges from the schema, this chapter is corrected to match the schema. The procedure that produces those bytes (which function reuses a buffer, when a codec is generated) is implementation description — it follows the schema when the schema changes. This chapter does not attach a separate decision/discretion label to every sentence, because every command and field rule is a contract the schema owns.Related contracts — Layer Boundaries and Identifiers · Location runtime · Redis Relocation Store · Transport Liveness · Relocation Handoff State Transitions
| Section | Covers |
|---|---|
| 1. Schema And Generation Boundary | Normative generated-codec authority, format ownership by layer, schema conventions, validator, and Location Store authority keys |
| 2. Record Framing And Decode | Multipart frame layout, decode validation, payload size limits |
| 3. Command Space | The list of 53 commands and their roles, Message Follow and session-replacement notifications |
| 4. Admission And Connection Fence | The hello/admit/reject procedure, DescriptorRevision ordering, ClientServer direction |
| 5. Service Liveness | The livenessProbe/Ack cycle, the Classic fanout beacon, subscriber-ready determination |
| 6. Typed Application Message JSON | The framework-json-v1 profile rules |
| 7. Durable Authority And Explicit Creation | Generation separation, the creation record, factory failure handling |
| 8. Instance Spot Cold Activation Recovery | The Missing+Instance intent envelope, first-activation recovery on the same target, User Spot terminal service operations |
| 9. Maintenance Capture And Relocation Envelope | The Retiring seal, the byte reservation gate, relocation envelope encoding |
| 10. Relocation, Actor Membership, And Ready | The authority phase state machine, aggregate relocation commit, the moment of Ready |
| 11. Request Terminal Identity | OperationId/ReplyRouteId, terminal completion tracking, root replacement |
| 12. Verification Requirements | The observable results confirmed by the schema self-test, generated codecs, and golden fixtures |
§7-§11 cover durable authority, cold activation recovery, the relocation manifest/CAS, membership and Ready, and terminal identity. Because this content directly overlaps the 05-location-relocation and 03-spot-actor topics, whether to relocate it is decided again when those topics are rewritten. This chapter performs only sentence-level cleanup and does not move any section.
1. Schema and Generation Boundary¶
Normative Generation Authority¶
framework/runtime/protocol/service-wire-v1.schema.json is the sole normative
wire authority for the Framework service wire. It fixes command IDs, frame and
logical-stream layout, enum values, field bounds, durable formats, and semantic
constraints. Each C++/.NET/JVM/Node.js codec and constant surface MUST be
generated from that schema. Until W-2 completes the generated swap for a
surface, its existing handwritten codec remains the transitional implementation;
new or changed wire surfaces MUST go through generation and MUST NOT add a new
handwritten encode/decode path.
Consequently, a wire divergence is possible only through a reviewed schema change. A runtime must not fork a layout, add a local compatibility encoding, or reinterpret a schema field in source. The schema self-test, generated-asset check, decoder-fixture check, and the schema's golden fixtures are the cross-language conformance mechanism: every generated codec, and every transitional handwritten codec until its swap, must accept and produce the same declared bytes and failures.
Normative Format by Layer¶
| Layer | Normative format | Owner and interpretation |
|---|---|---|
| Location Store records | Canonical JSON envelope | Location runtime §2.4 defines the JSON record's byte layout precisely; the provider treats it as opaque bytes. |
| ClientServer application records | JSON 0xF2 channel envelope |
The one-directional service boundary where a Client starts a send/request and a Server runs the handler and reply, the ClientServer Channel (details), owns this envelope's application-record contract and its JSON semantics. |
| Internal mesh commands and relocation direct-transfer stream | service-wire-v1.schema.json binary formats |
Generated codecs own command frames; relocation-envelope-v1 is its big-endian logical stream. |
| Application payload bytes | Opaque, application-owned bytes | The Framework validates only the declared envelope boundary and does not assign business meaning to the bytes. |
Machine-Readable Schema Conventions¶
The generator input is the existing schema, not a model inferred separately
for each language. Its
types array declares named layouts: primitives and enums use encoding and
values; ordered fixed fields use kind: "struct" with declaration-order
fields; counted sequences use kind: "vector" with countType and item;
and length-delimited, conditional, and tagged layouts declare their own
lengthType, layout, cases, fields, or encodingOrder. $ref names a
declared type, $bound names a declared limit, and constraints,
trailingBytes, when, and otherwise state validation required of both
encoders and decoders. Command bodies are declaration-order body arrays under
commands; durable envelopes are under durableFormats; the relocation direct
stream is declared by relocationLogicalStreamFormat.
The schema does not yet give every existing layout a uniform generator-ready lowering rule or per-language output mapping. W-2 must fill those missing generator-input details, including complete lowering coverage for every layout kind, conditional/semantic constraint handling, and the generated asset and fixture mapping. It must extend the schema rather than introduce private syntax or handwritten codec exceptions.
Validator¶
Generators and fixture builders run the validator before producing a file.
node framework/runtime/protocol/validate-service-wire-schema.mjs \
--self-test framework/runtime/protocol/service-wire-v1.schema.json
The wire major is 1 and the required capability is framework-service-v13.
The build stops if the schema and golden fixtures diverge, or if the
validator finds an undefined type, a duplicate ID, or an invalid
enum/bound/conditional field.
Location Store Authority Key Format¶
The store that records which node an object currently lives on is called the Location Store. The rule for building the authority key for an Actor and for a Spot — a logical instance with an address and state that stays reachable by the same global ID even if its running node changes — is fixed by the same schema and golden fixtures.
| Object | Key format |
|---|---|
| Actor | zla1:a:<byte-length>:<encoded-ActorId> |
| Spot | zla1:s:<byte-length>:<encoded-SpotRid> |
- MeshName is not part of the key — it is stored only as the authority payload's current placement attribute.
- Percent encoding leaves RFC 3986 unreserved bytes as-is and represents everything else as uppercase hex.
2. Record Framing and Decode¶
Frame Layout¶
A ROUTER routing identity is the transport envelope the raw binding consumes; the service codec does not copy it into the application frame. A service record is a multipart message in the following order — this order and each frame's byte layout is contract description that other implementations depend on verbatim for interoperability, and the procedure that produces those bytes is implementation description owned by the codec generated from the schema.
frame 0 frame 1 next frame
+------------------------+ +----------------+ +------------------------+
| head prefix (5 bytes) | | metadata | | typed payload envelope |
| + command body | | when flag 0x01 | | when command allows it |
+------------------------+ +----------------+ +------------------------+
always present set by flag set by command
The first 5 bytes of frame 0 are the same for every record.
byte 0 1 2 3 4
+------+------+---------+----------+---------+
| 0x5A | 0x4D | 1 | command | flags |
| 'Z' | 'M' | major | ID | bit set |
+------+------+---------+----------+---------+
\___ magic ___/ one byte each
The layout of the command body that follows is the declaration order of
commands[].body in the schema. Every integer longer than one byte is
big-endian.
Flags determine which additional data follows.
| Bit | Name | When set | When clear |
|---|---|---|---|
0x01 |
metadata |
Frame 1 must be present as a metadata frame. | A metadata frame is rejected. |
0x02 |
boundSession |
A bound-session tail follows the command body. | It does not follow. |
0x04 |
sourceSpotId |
A source Spot RID follows in the same tail. | It does not follow. |
0x08 |
extension |
An extension follows the command body. | It does not follow. |
These flags can be used only on commands that the schema permits or requires. An undefined flag, an unexpected frame count, a conditional tail, or a trailing byte is rejected as a protocol error before application dispatch.
Decode Validation and Size Limits¶
- The decoder checks the complete record length, item count, UTF-8 validity, and every bound before allocating.
- A metadata frame cannot exceed 1,024 bytes.
- The application payload's absolute schema limit is
applicationPayloadAbsoluteBytes, 4,294,966,774 bytes. - On a RouteMesh — the connection group where several runtime nodes find each other by name and exchange messages — ServerServer applies only the absolute schema and wire representation bounds; it has no Framework-level message-size cap.
- On ClientServer, the actual allowed payload size is whichever is smaller: that absolute limit, or
normalizedEffectiveMaxMessageBytesminus the real envelope overhead. - No separate, hidden 16 MiB cap applies to the application payload.
The ClientServer complete-message limit is fixed at startup admission.
- The sender uses the smaller of the local and remote
normalizedEffectiveMaxMessageBytes, and the receiver uses its own admitted limit. - This value cannot change during the admitted connection's lifetime, and is applied before allocation.
- If both sides' limits are 32 MiB, a 17 MiB payload is allowed, since the complete message stays within 32 MiB.
- RouteMesh admission doesn't carry this field, and an SS sender or receiver doesn't reject a message because of this value. HWM, mailbox byte budgets, and protocol representation bounds remain separate resource and wire guards.
Typed Payload Envelope¶
A typed payload preserves the packet name, contract information, and serializer payload together in one envelope. Application code is never exposed to raw frame assembly, codec tables, or maintenance fields.
Framework Multipart Application Profile¶
Service messaging commands that carry several Framework message parts as a
single application payload use a common profile for the outer application
envelope. This envelope's packet name is fixed as ZLinkFrameworkMultipart
and its content type as application/x-zlink-multipart. The actual
application message's bytes are preserved verbatim by part order inside the
envelope's payload. Parts are opaque bytes; this profile does not carry a
per-part packet name or content type on the wire.
Operations where the command itself defines a separate application-payload envelope, such as Actor creation, are not subject to this profile — that operation's own contract governs its packet name and content type.
The payload's byte layout is as follows.
+-------------+ +---------------+------------------+ +---------------+-----+
| part count | | part 0 length | part 0 bytes | | part 1 length | ... |
| u32 (>= 1) | | u32 | opaque for length| | u32 | |
+-------------+ +---------------+------------------+ +---------------+-----+
\____________ repeated part count times ______________/
The part count must be at least 1. Before building the result list, the decoder confirms the count is representable by the remaining bytes, and that each length does not exceed the remaining range. It rejects the record if any bytes remain after all parts are read. The Framework does not interpret a part's contents for business meaning — it restores the original bytes into each Message as-is.
The Framework does not recompute this profile's count, lengths, outer envelope, content-type frame, or Framework metadata as a separate application byte HWM. Core's byte charge ends when the complete message is dequeued into the binding/Framework, and the decoded payload follows ordinary Framework ownership rules. The Framework acquires an application job queue permit before receive/claim, limiting handler admission by job count.
3. Command Space¶
Wire v1 uses the following IDs. 7..15, 32, 35, 41, 45, and 54..255 are
reserved and never reused for another meaning. A previous command name in parentheses
is diagnostic compatibility text, not a command that is decoded or sent.
| ID | Command | Layer | Application payload | Permitted flags | Role |
|---|---|---|---|---|---|
| 1 | hello |
infrastructure | none | — | Proposes this connection be accepted by offering its own descriptor |
| 2 | admit |
infrastructure | none | — | Approves the selected connection |
| 3 | reject |
infrastructure | none | — | Rejects admission |
| 4 | update |
infrastructure | none | — | Updates the admitted descriptor revision |
| 5 | livenessProbe |
infrastructure | none | — | Confirms the current connection's round trip |
| 6 | livenessAck |
infrastructure | none | — | Responds to the same probe ID |
| 16 | nodeSend |
application | required | metadata |
Node one-way |
| 17 | nodeRequest |
application | required | metadata |
Node request |
| 18 | channelSend |
application | required | metadata |
Channel one-way (RouteMesh connections only) |
| 19 | channelRequest |
application | required | metadata |
Channel request (RouteMesh connections only) |
| 20 | reply |
infrastructure | optional | — | Request terminal result (RouteMesh connections only) |
| 21 | spotSend |
application | required | metadata |
Spot one-way |
| 22 | spotRequest |
application | required | metadata |
Spot request |
| 23 | logicalMulticast |
application | required | metadata |
Logical multicast |
| 24 | actorSend |
application | required | metadata · boundSession · sourceSpotId |
Actor one-way |
| 25 | actorRequest |
application | required | metadata · boundSession · sourceSpotId |
Actor request |
| 26 | actorLookup |
infrastructure | none | — | Actor route lookup |
| 27 | actorDestroy |
infrastructure | none | — | Actor destroy coordination |
| 28 | actorJoin |
infrastructure | optional | — | Actor membership proposal — which Spot the Actor belongs to |
| 29 | actorLeft |
infrastructure | none | — | Actor leave commit |
| 30 | relocationReady |
infrastructure | none | — | Temporary queue, Restore, and relay-reception-ready reply |
| 31 | relocationData |
infrastructure | none | — | Post-capture ingress-hold relay record transfer |
| 32 | reserved (relocationAck) |
— | — | — | Removed per-message ACK/numeric-high-water command |
| 33 | replyRelay |
infrastructure | optional | — | Terminal completion relay |
| 34 | relocationCutover |
infrastructure | none | — | One-way control reporting all pre-boundary relay sent |
| 35 | reserved (relocationComplete) |
— | — | — | Removed target-completion-reply command |
| 36 | boundSessionSend |
application | required | — | Bound egress on a STREAM session — the server execution unit kept from accepting one STREAM client connection until it closes |
| 37 | actorJoined |
infrastructure | none | — | Actor join commit |
| 38 | boundSessionBind |
infrastructure | none | — | Session binding commit |
| 39 | instanceSpot |
application | required | metadata |
Logical Instance Spot operation |
| 40 | relocationPrepare |
infrastructure | none | — | Request to install temporary queue, declare the final-stage (and optional base-stage) payload manifest, and prepare relay |
| 41 | reserved (relocationReserved) |
— | — | — | Removed relocation-specific capacity-reservation ACK |
| 42 | sessionRelocationSeal |
infrastructure | none | — | Session ingress seal request |
| 43 | sessionRelocationSealed |
infrastructure | none | — | Session seal response |
| 44 | sessionRelocationRoute |
infrastructure | none | — | One-way Session-route update control for target route switch or source abort before relay-ready is accepted |
| 45 | reserved (sessionRelocationRouted) |
— | — | — | Removed Session-route application response command |
| 46 | replyRelayAck |
infrastructure | none | — | Relayed terminal result ACK |
| 47 | userSpotCreate |
infrastructure | none | — | Creates a remote User Spot in an already-reserved slot |
| 48 | userSpotClose |
infrastructure | none | — | Closes only the specified generation's remote User Spot |
| 49 | actorCreate |
infrastructure | none | — | Creates a remote Actor in an already-reserved slot |
| 50 | messageFollow |
infrastructure | none | — | The location-cache invalidation notice sent to the source runtime after a successful relay |
| 51 | boundSessionReplaced |
infrastructure | none | — | Notifies the previous session after the new binding becomes current |
| 52 | relocationState |
infrastructure | none | — | Direct source-memory-to-target payload chunk transfer (base/final stage) |
| 53 | relocationFailed |
infrastructure | none | — | Explicit reply to a matching relocationPrepare reporting assembly or preparation failure |
The table's Layer, Application payload, and Permitted flags columns reproduce the schema declarations. Only application-layer commands enter an application handler. Infrastructure commands are exchanged by runtime components and never enter the application queue. An unknown command, an infrastructure command going the wrong direction, or a command the current topology does not allow is likewise never placed on that queue.
Each command's body and direction follow the schema's closed definition.
3.1 Message Follow Notification¶
Body Layout¶
messageFollow is an infrastructure record that does not wait for a
response. It allows neither flags nor an application payload — a single
service record carries a body closed by version 1 and its length. The
body carries the source route, the target route, the hop count, the queue
count/bytes at relay time, the original operation ID, and the original
reply route ID. The two queue values are saturating u32 diagnostic
snapshots. UINT32_MAX means the actual count or queued byte size is at
least that value; the snapshot never controls payload admission.
Route Validation¶
- The source and target routes must share the same object kind and object identity.
- Each route carries the object generation, the target node RID and generation, the authority owner generation, and the owner lease generation; the receiver first confirms the source route's target node is a currently admitted peer.
- Only a hop count of 1..8 is allowed. One control envelope is at most 16 MiB. This envelope bound and the saturating diagnostic fields do not impose a message-count or stored-byte bound on the retained payload queue.
- A record that points at a different object, or whose route fence doesn't match, ends as a protocol error before application dispatch.
Suppressing Duplicate Notifications¶
A runtime that completed a relay may send messageFollow to the source runtime. The source
runtime invalidates its current cache entry only when it points at the same route fence
as the source route. It does not erase a newer route. Even if the notification is lost, the
cache lifetime must eventually expire the stale route.
The sender's dedicated suppression registry uses the complete source and target route fences
as its key. Its state moves through idle → inFlight → sentUntilExpiry, and only a send
failure returns it from inFlight to idle. Route-cache expiry or replacement also removes
the marker. The registry does not own the original operation's payload, reply route, or
terminal completion. 45. Target Selection and Route Cache
shows the state flow.
3.2 Bound Session Replacement Notification¶
boundSessionReplaced is a one-way infrastructure record sent to the previous session owner after
the new Actor binding becomes current. It carries an Actor-authority source fence and the previous
session owner's lifecycle and binding identity, and uses no flags, application payload, or
acknowledgment. The receiver verifies that the sending node matches the Actor-authority target and
uses the previous session-owner identity as the local target fence. Delivery and the previous owner's
callback or connection close neither delay nor roll back the new bind terminal. The previous owner
applies only a record matching the retired identity. When the previous session is closed
(100 ms after the callback terminal) is owned by
Session and Actor binding §14.
4. Admission and Connection Fence¶
Admission Procedure¶
- RouteMesh and ClientServer approve the current physical connection as a service route via
hello → admit|reject. - A manually configured lifecycle token is a non-zero opaque equality token generated by a CSPRNG, a generator of cryptographically unpredictable random values.
- New values are not judged by numeric magnitude — a previous token is blocked instead by the current physical connection's handover and liveness.
- For a peer whose owner is recorded in the store, the runtime also checks whether that host is still the owner and whether its lease is still valid.
DescriptorRevision Ordering¶
- Only
DescriptorRevisionhas strictly increasing ordering within the same lifecycle. - Identical bytes at the same revision are idempotent; different bytes at the same revision, or a lower revision, are a protocol error.
updatecan only change the existing channel weight, runtime state, placement capacity, and maintenance wave.- RID, topology, security identity, capability, application version, and the normalized message limit only change by re-admitting the connection.
Physical Connection Replacement¶
Admission of a Descriptor — the registration
information a remote runtime publishes so its endpoint, identity, membership, weight, and
status can be discovered — and physical transport replacement use the same fence.
Once descriptor expectations are complete, an endpoint-only manual intent with
generation 0 cannot overwrite them. The runtime requests
termination of the current physical connection at the endpoint level, and does
not create a new connection for the same endpoint before observing that
endpoint's close snapshot or disconnect event. A successful call does not
replace observation of the physical close. The connection_id of a monitor
event is for diagnostics and correlation only and is never used as a fence. A
late event from the previous connection is fenced by the descriptor's RID,
security identity, and lifecycle generation together with the observation
order, and cannot change admission or ready state
of the new connection.
ClientServer Direction¶
- A ClientServer connection fixes a single application-attached channel name, the ChannelName, and a client-to-server direction.
- The only service-wire records on a ClientServer connection are the infrastructure commands: the client starts
helloas a Core request and sends/answers the liveness pair; the server answersadmit/rejectonly on that hello request's reply leg and pushesupdateand liveness. - Application records on a ClientServer connection do not use service-wire commands. They ride the channel envelope — the two-frame record
[JSON header (formatMarker 0xF2; kind request/response/command/error), payload]all four runtimes share for channel messaging. A request rides the Core request envelope and its response/error rides the matching reply leg; a one-way command is a plain send.channelSend(18)/channelRequest(19) and the command 20 reply travel only on RouteMesh connections. - Reusing a RouteMesh record — where multiple nodes find each other by name — for a ClientServer connection, or the reverse, is a protocol error.
5. Service Liveness¶
The Probe/Ack Cycle¶
%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
participant A as Node A
participant B as Node B
Note over A,B: Admission succeeds — peer timeout deadline starts
A->>B: livenessProbe(id) — every 5s, a new non-zero id if none is outstanding
B->>A: livenessAck(id)
Note over A: Only the first Ack matching the current outstanding id<br/>restarts the 15s deadline and clears outstanding
- The timing and judgment rules — the 5-second probe period, the 15-second deadline, one outstanding ID resent as is, only the first ACK for the current ID refreshing the deadline, the immediate not-ready conditions — are owned by Transport liveness §3 and §10. This section defines only the command schema and the connection epoch those records ride.
- The probe, ACK, and timer are handled by the infrastructure reserve and are not delivered to the application queue or a handler.
- Both admitted peers probe. The 5-second probe obligation is bidirectional and begins the moment a connection is admitted, independent of which side dialed. A node that only answers a peer's probe with an ACK but never originates its own probe is non-conforming: the other side would judge it live while it never confirms the reverse direction. The diagram shows one direction for brevity; each admitted peer runs the full probe/ACK cycle toward the other.
- Probe and ACK ride the admitted physical connection's current epoch, and that epoch is stable for the connection's lifetime. A
livenessProbeand itslivenessAckare addressed to the peer identity and connection generation that admission established (scope: admitted-physical-connection-lifetime). A redundant re-dial or a repeatedhello/admitfor a peer that is already admitted on a live physical connection is idempotent: it neither supersedes the admitted connection nor rotates its connection generation. Emitting a probe or ACK stamped with a superseded or not-yet-delivered generation — one the peer's live pipe does not recognize — is a defect; the peer silently drops it as "an ACK from a different connection," and neither side's deadline is refreshed. A new connection generation is minted only when a genuinely new physical connection replaces the admitted one (per the duplicate-connection selection in 13. Mesh Node), not on every inbound admission record for an already-admitted, unchanged descriptor.
Classic Fanout Beacon¶
A one-way delivery style where the receiving side never responds is called Classic fanout. Since this kind of publisher can't receive an ACK, it sends a separate beacon every 5 seconds. The beacon is sent periodically, independent of application publish traffic.
Determining Subscriber Readiness¶
- A subscriber uses a dedicated SUB socket per publisher.
- It becomes Ready — the state where transport connection, handshake, and identity checks have all finished so the target can accept a message — on the first valid application record, or a beacon from that publisher, and switches only that publisher to not-ready once 15 seconds pass since the last valid receive.
- An incorrect frame count or payload on the reserved topic is an immediate protocol error.
- If a derived public topic happens to exactly match the reserved topic, that is rejected as an application argument or configuration error, before transport.
6. Typed Application Message JSON¶
framework-json-v1 Profile Rules¶
The public encoding and validation rules for the framework-json-v1 profile used by default
typed application messages are owned exclusively by
Message Model §2.3. The runtime validates
the payload and converts it to a typed value under that profile. This document defines no second rule
set; parser choice, buffer reuse, and transport delivery of the original UTF-8 bytes remain internal
implementation details.
Relocation Adapter State Is Outside the Profile¶
Application state returned by an Actor/Spot relocation adapter is not subject to this profile. The Framework stores relocation state as opaque bytes, and does not perform JSON parsing or compare a state contract ID or an application-specific version.
7. Durable Authority and Explicit Creation¶
Generation and Authority¶
- Store-backed authority keeps the provider-issued
StoreVersion,ObjectGeneration— the number that distinguishes different logical incarnations of the same logical ID — andAuthorityOwnerGeneration— the number that marks the order in which the authority owner changed within the same incarnation — separate from the current host'sOwnerIdandOwnerLeaseGeneration— the provider-issued value that distinguishes the current owner's host process lifecycle. - The object generation changes only when a new object is created under the same key after a delete.
- The current owner recorded by the store, and its standing, is called Authority; the authority owner generation increases every time that object's owner changes, blocking changes from a stale owner.
- The host owner lease token is shared across the whole process.
Creation Record¶
Actor and User Spot manager create, and target-owned Instance activation,
use a generic reservation to create the final object/owner generation and
a Creating row.
- A creation record preserves the object kind, global key, stable type, target descriptor, capacity delta, provider-issued fence, and the content reference/hash of a complete request envelope up to 1 MiB.
- This value, preserved in the pending current row, is called the
stored creation intent. During recovery, this record can be scanned to confirm the fence value and receipt still match exactly, and used to restore state. - The application code that actually creates the object is called the Factory. Once Factory, initialize, and initial membership finish, the reservation commit and the
ReadyCAS run under the same fence. - Only target-owned Instance cold activation additionally fixes the durable activation inbox's first record before commit.
- Manager
Findand ID-only messaging use onlyReady. - Entry Spot is published after startup initialization, before the host becomes
Serving, and is never created by a caller.
Factory Failure Handling¶
- A Factory failure seals the local barrier as failed and terminal-processes the waiting request exactly once.
- A one-way operation records a drop event.
- The runtime deletes the row and reconciles an ambiguous result by reading, only if the StoreVersion, object/owner generation, and owner lease it read earlier are all still unchanged.
- The local registry stays failed until
Missingis confirmed, and only then can a caller start a newNewObject.
Object Role¶
The Client and Server object roles require a Location Store. The None
object role creates neither authority nor a hidden local runtime.
8. Instance Spot Cold Activation Recovery¶
The recovery scope and caller-visible result are defined by Failure Handling and Failover Scope §4.4. This section describes only the wire-record, durable-root, and scan structure that implements that scope.
Recovery in this section is not general owner-loss reactivation. It resumes an operation on the
same target node and lifecycle generation only when the first cold activation published Ready but
didn't finish recording that operation's terminal completion and removing the recovery pointer. A
steady Ready owner process termination or owner-lease expiry doesn't use this root, select another
node, or run a factory.
Missing+Instance Intent Envelope¶
A normal Instance send/request carries only the global SpotRid and is not a
create command. The target of a Missing+
Instance intent — the caller's
explicit choice allowing a new Instance Spot to be prepared when no target Spot exists —
stores a complete
instance-activation-recovery-v1 envelope — one that preserves even
command 39's optional metadata presence/frame — in the
Relocation Store, the store that
holds the activation envelope an Instance Spot cold activation needs and the reply
payload completed after relocation,
and links the receipt to the Reserve. This format, and the durable
activation inbox, are used only for target-owned Instance cold activation,
never for Actor/User Spot generic create.
Command 39 Route Kind¶
A command 39 route is a closed union of a first byte and a u16 body
length.
| Kind | Purpose | Contents |
|---|---|---|
1 |
Delivers to an existing Ready authority | The object/owner/lease generation and StoreVersion of a state that can accept new work |
2 |
Missing cold activation only | The target Mesh/node RID/lifecycle, Spot RID, stable type, descriptor version, and deadline — an authority fence is forbidden |
If a kind 2 route's operation identity or metadata presence/bytes differ
from the ZLIA's target Mesh/stable type/descriptor version/deadline, it is
rejected as a protocol error before reservation.
Target Host Scan and Recovery¶
- During the startup first scan and a rate-limited background scan, the target host resumes any Pending record it owns, or a Ready Instance activation recovery root that points to an incomplete first operation. The authority's target node RID and lifecycle generation must exactly match the current host.
- The scan and late control records converge on a single local barrier, keyed by object key, object/owner generation, and owner lease.
The recovery pointer is removed in the following order:
- Finalize the durable inbox's first record before publishing
Ready. The barrier blocks the handler. - Startup does not publish Serving before restoring the queue head.
- Durably record the first handler's terminal completion and advance the replay cursor to the inbox sequence.
- Only then remove the recovery pointer with Preserve CAS. Queue admission alone does not remove it.
Cold Activation Recovery Failure Handling¶
- A Cold activation — the process of creating and initializing a new Instance Spot when authority is Missing and the caller specified Instance intent — recovery failure seals the local barrier, terminal-processes the request exactly once, and records a one-way drop event.
- It is then deleted only when the fence value matches, and the deletion result is read back for reconciliation.
- If the process ends before the delete, the target scan can safely re-run the retry-safe factory.
- No new activation starts before
Missingis confirmed.
8.1 User Spot Terminal Service Operation¶
Command 47 — Remote Create¶
- A User Spot remote create uses command 47.
- After a generic Reserve, the source sends a correlation/operation ID, the source node lifecycle, the global Spot RID/stable type, and the provider-issued reservation fence and deadline to a single specified target.
- The reservation fence preserves the expected StoreVersion, object/owner generation, target node lifecycle/owner lease, and pending capacity together.
- Since the target reads the immutable content of the Pending creation projection from the Location Store, command 47 carries no application payload or metadata.
Command 48 — Remote Close¶
- A User Spot remote close uses command 48.
- Besides the source node lifecycle and operation identity, it sends the
SpotRefthat exactly identifies the target to close, the target node lifecycle, the AuthorityOwnerGeneration, and the StoreVersion. - Before deciding whether to accept, the target checks the current authority and Actor membership and the relocation state.
- Both commands are RouteMesh infrastructure commands and allow neither flags nor a payload.
Reply Envelope¶
Both operations return their results in the command 20 reply envelope.
- The success tail for Create is the
Existing/Created/Rejecteddiscriminator plus theSpotRefthat identifies the target; the success tail for Close is a singleclosedbool. - Create's application reply is forbidden on
Existing, and only optionally allowed onCreatedorRejected. - The source operation table guarantees terminal-once by source RID/lifecycle and operation ID.
- Neither Location row polling nor a control message built from an application packet substitutes for a reply.
9. Maintenance Capture and Relocation Envelope¶
Actor Join Request Envelope¶
actorJoin(28) is sent as [request], and its result returns only on the command 20
reply — this request's [reply] leg. actorJoin(28)'s request body — correlation, the actor route fence, the entry flag,
and the target spot route fence — is the complete cross-language contract for this
operation. No other field travels on the wire for it. In particular, per-transfer
bookkeeping identifiers a runtime uses internally to track an in-flight move (for
example, a transfer id) are language-internal only and never appear in this body; a
runtime that needs such an id generates and keeps it locally, not on the wire.
Sending or receiving command 28 as a one-way record instead of a request/reply pair is
outside this operation's contract; the receiver rejects it as a protocol error before
application dispatch, per the rules in
2. Record Framing and Decode.
Receiver-side admission semantics — parking behind an existing preparation and the
later-attempt-wins rule described in
15. Spot and Actor Model §4.2 —
key on the actor identity carried in this body, not on any language-internal id.
When the join-accepted reply carries an application reply, the target wraps it in the Framework multipart application profile on the reply leg. There is exactly one part, and that part carries the bytes of the application reply message the handler produced, verbatim. This profile does not carry a per-part packet name or content type on the wire — the envelope's packet name and content type are the profile's fixed values, and interpreting the part bytes belongs to the application layer under the per-layer normative-format principle. When there is no application reply, no multipart envelope is carried at all. The source unwraps this profile and delivers the sole part as the application reply, without interpreting the part bytes as another envelope. The only reply metadata the source exposes to the caller is the profile's fixed outer content type, or none — it must not reconstruct another value, such as the request's content type. Framing other than this profile — nesting the part in an additional envelope, or exposing the still-wrapped inner bytes directly — is outside this operation's contract.
Receiver Stable-Type Resolution¶
Because the actorJoin(28) body deliberately carries no Actor stable type, the receiver
resolves the factory type from the canonical Location Store, not from any wire field or a
prior local record. The Actor's Authority row is the single per-Actor source of truth (its
canonical key is authority\0actor\0{ActorId}; see
21. Location Runtime §2.4) and already carries
allocation.stableType. On admission the receiver MUST read that row for the ActorId in
the body and accept the join only when the row exactly matches the actor route fence:
| Value read from the Authority row | Fence value compared | Condition |
|---|---|---|
allocation.state |
— | Must be active. |
allocation.objectKind |
— | Must be actor. |
objectGeneration |
ObjectGeneration |
Equal. |
| owner node RID | target node RID | Equal. |
| descriptor lifecycle generation | node generation | Equal. |
authorityOwnerGeneration |
expectedAuthorityOwnerGeneration |
Equal. |
ownerLeaseGeneration |
expectedOwnerLeaseGeneration |
Equal. |
The join is not accepted if any of the seven rows differs.
The factory is then resolved from the row's allocation.stableType against the local
factory registry. This is the same Authority-derived stable-type verification the
relocation path already performs on relocationState(52) (which likewise omits the type
from its wire object); the 28 admission path extends it to the first target preparation
step rather than trusting a sender-supplied type. Every failure is a typed terminal on the
command 20 reply, never a silent drop: a missing or unreadable row is Unavailable
(Store unavailable) or NotFound (never created / already retired); any fence field that
does not match is a stale/mismatch protocol terminal; an unknown stableType (no local
factory) is a typed rejection. A generation compared here is a bounded generation and is
compared only for equality, never by numeric ordering (§12).
The lease value the source places in the fence's expectedOwnerLeaseGeneration is the
Actor's current Location owner lease, not a bound-Session token; an unbound Actor still
carries its owner lease, and a bound Session adds only the seal/route-update legs, so a
receiver MUST NOT require a bound Session to admit a canonical actorJoin(28).
Session Seal and Source Relay¶
- Before stopping source application dispatch, the relocation coordinator seals a bound Session binding with command 42. Command 43 reports the seal result.
- The Session owner validates only current Session identity, binding generation, ActorId/ObjectGeneration, and relocation identity. It doesn't create a numeric high-water or re-read Actor authority.
- Requests and pushes arriving after the seal are held by the Session owner until route change or abort. No relocation-specific record-count or byte bound is added.
- Ordinary server messages arriving on the source object route keep being relayed to the target temporary queue. This uses ordering and retransmission of the same TCP connection, without a per-message ACK or durable journal.
Relocation Manifest and Direct Chunk Transfer¶
- Source sends command 40,
relocationPrepare, as[request]to request temporary-queue installation and declare the payload manifest for the transfer that follows. Its body carries the object identity, source node RID/generation, andpayloadTotalLength,payloadChunkCount, andpayloadChecksumCrc32cdescribing the payload. No relocation-root pointer or Relocation Store lookup key is carried; Prepare fully describes the direct transfer that follows, sourced from source memory. Target sends command 30,relocationReady, as[reply]only after temporary-queue installation is ready to receive. This pair negotiates no message/byte allowance or participant reservation beyond the declared manifest. - Source sends the payload as one or more command 52,
relocationState,[send]records on the same ordered connection. Each record carries the relocation/target-attempt/ coordinator fence, the object identity,senderRole, and a zero-basedchunkOrdinal. The chunk bytes reuse the existingrelocation-data-chunk-v1format. Immediate assembly copy and storage lifetime follow Relocation flow §4.3. - The target compares the assembled payload's length and CRC-32C against the value Prepare
declared. A mismatch is an explicit failure; the target never attempts
partial-assembly restore and never retries transparently on checksum mismatch. On
failure the target sends command 53,
relocationFailed, as a reply to the matching Prepare, after cleaning its own partial chunks and prepared resources. Only receipt of this explicit failure — never a dropped or indeterminate connection — causes the source to restore the captured payload from source memory and finish the operation as a failure; an indeterminate outcome is not reversible from the source's perspective. - Command 31,
relocationData, carries only post-capture ingress-hold application records on the same ordered connection. It never carries saved queue work or timers, which travel only in command 52 chunks. It contains no saved queue prefix or timers and creates no per-record ACK or numeric high-water. - Source inserts command 34,
relocationCutover, as[send]after the current ingress-hold relay prefix. Its body addsboundaryRecordCountandboundaryChecksumCrc32c, describing the precise relayed-record batch the boundary closes over. Target sends no response. Reserved IDs 32, 35, and 41 are neither sent nor accepted. - If the source observes that a previously sent cutover did not reach the target
(connection loss) and the source instance is still live, it opens a new connection and
retransmits the full pending batch plus a fresh cutover — never only the tail — and the
target replaces any partial staged batch wholesale rather than appending. The
retransmission window equals
RelocationCutoverWaitTimeout(default 1,000 ms, configurable). After the window elapses, the existing CAS fallback in the next subsection applies with only a Warning and a counter increment, and no further blind retry. - The source stores application state, queue work not yet executed before relocation, and timer information for direct transfer. Native timer handles and callback continuations aren't encoded.
- The target registers a temporary queue, then runs factory and chunk assembly. It doesn't run application handlers until this work finishes.
- After relaying every message received before cutover, the source sends cutover as a
[send]on the same ordered connection. It is the boundary proving that every earlier relay on that connection reached the target. It has no reply. - Ordinary server-to-server
sendadds no relocation-specific application ACK. Arequestkeeps its existing operation identity, correlation, deadline, and caller retry.
CRC-32C Convention and Capability¶
- Every checksum listed in
relocationTransferChecksumProfile(payloadChecksumCrc32c,boundaryChecksumCrc32c, and the chunk/manifest checksums used by the retained store paths below) uses the same CRC-32C (Castagnoli) convention: polynomial0x1EDC6F41, initial value0xFFFFFFFF, reflected input and output, XOR output0xFFFFFFFF, andcheck("123456789") == 0xE3069283. - The wire major is
1and the required capability isframework-service-v13— bumped from v12 for commands 52/53 and the Prepare/Cutover manifest fields, with all four runtimes bumped together. - A target's effective inbound chunk-byte limit is negotiated in the admission-accept
reply's
receiveChunkLimitBytesfield, or via host preflight where that reply is unavailable. Where no negotiation path exists (JoinEntrySpot), the effective limit is 32 KiB — the general Compact data lower bound. This shape and its bounds are pinned by theactor-join-reply-tailgolden fixture (framework/runtime/protocol/golden/actor-join-reply-v1.json), which all four runtimes (cpp, dotnet, java, node) decode identically. All four runtimes originate a canonicalactorJoin(28) as[request]— wiringreceiveChunkLimitBytesinto the command 20 reply, this request's[reply]leg — once the target's canonical capability is observed (an authority fence plus a peer admitted at that generation); when it is not, each runtime keeps its language-internal admission path (a transitional fallback). The receiver resolves the stable type from the Store Actor Authority row per §9, never from the wire. (An earlier revision stated C++ and .NET did not originate; now that the Store-backed canonical receiver exists in all four runtimes, origination is unified across all four.)
Target CAS and Retained Store Roles¶
- After chunk assembly and temporary queue registration finish, receipt of cutover starts
the target CAS of Location Store owner and membership from source to target. If cutover
doesn't arrive within
RelocationCutoverWaitTimeout(default 1,000 ms) after the Restore-ready reply, the target records acutover_timeoutWarning and starts the same CAS. Only the target performs this CAS. - Neither the source nor the Session owner changes the Location Store based on a timeout, local mirror, or Session route result.
- The Relocation Store no longer holds the Actor/Spot relocation payload — the direct
chunk transfer above is the single handoff path, and the two Stores never used a
distributed transaction or 2PC for it. The Relocation Store's remaining
responsibilities are the Instance Spot cold-activation envelope (§8) and the
relocation-scoped pending-request terminal record; those two paths keep using the
store's own
relocation-manifest-v1/relocation-root-pointerformats and CAS discipline, unchanged by this section. - If CAS fails, the target doesn't open its queue and retries the same CAS until the Restore operation's validity deadline (an absolute deadline on the target, unrelated to Relocation Store retention). After an indeterminate response, it first reads the Store to determine whether the target itself is already owner. A different valid owner or generation makes the relocation stale immediately.
- If the target owner isn't confirmed before that deadline expires, the target records
a
location_update_failedError and removes the prepared Actor or Spot, temporary queue, and relocation state. It doesn't update the Session route. A late Store response cannot reactivate the terminalRelocationId. - After successful CAS, the move isn't rolled back to the source.
10. Relocation, Actor Membership, and Ready¶
RelocationId¶
RelocationId is a non-zero 128-bit value made by the runtime. It distinguishes
repeated control messages for the same relocation and isn't exposed to the application.
Authority and Target-Only CAS¶
Before CAS, the source is owner. The target has only a prepared instance after Restore
while waiting for cutover or the 1,000 ms fallback, and doesn't run application messages. The target
becomes owner when the target-only CAS succeeds. The Actor or Spot's
ObjectGeneration stays the same while owner generation increases.
One Actor, a PerActor Spot authority, a SpotWide aggregate, and an Instance Spot
follow the same rule. When several owners and memberships must change together, the
target changes all or none in one conditional batch. Relocation adds no separate runtime
capacity gate for participant count, relay record count, or bytes. Existing Store
provider and transport frame/page size limits still apply.
Post-Commit Queue and Ready¶
After CAS succeeds, the target opens queue and lifecycle in this order.
- Put saved existing work and timers into the target execution queue.
- Put work relayed before cutover behind it.
- Add further work from the temporary queue and switch the dispatch route.
- Finish required lifecycle callbacks and open application dispatch.
- Send command 44 route update from the target runtime to the Session owner.
There's no global ordering promise between messages arriving over different TCP connections. Only order accepted into the target queue is preserved. After the Owner — the MeshNode that actually runs the Actor or Spot and manages its application queue — changes, Message Follow sends messages arriving at the old address to the target.
After the cutover submission reaches a success or failure terminal, the source
doesn't wait for a target completion response. Only an explicit target failure before relay-ready is
accepted aborts and restores source queue and Session seal. A later submit failure doesn't
restore source. A late or duplicate cutover only records a late_cutover Warning and doesn't mutate state
again. When the 1,000 ms fallback opens the queue, the contract doesn't guarantee that
late relay runs before new direct target messages.
Session Route¶
- Session route is validated only against the Session owner's current Session and binding.
- Command 42 seals the current binding; command 43 returns only the seal-install result. Command 43 carries no Session-message sequence or high-water.
- Command 44 is sent by the target runtime for the session-route update and by the source coordinator for an abort before relay-ready is accepted. The route update carries relocation identity, current binding generation, ActorId/ObjectGeneration, and target route. The Session owner doesn't re-read the Location Store or Actor authority mirror.
- The Session owner changes the route and current
ActorRefsnapshot to the target, submits messages held during the seal to the target route, and releases the seal. - Command 44 has no reply, and reserved command 45 is neither sent nor accepted.
SessionRelocationSealTimeoutdefaults to 3,000 ms. If the matching command 44 doesn't arrive in time, the Session owner closes the physical Session and cleans binding, held-message, and seal state.- A late command 44 or an identical duplicate after timeout only records a Warning and doesn't change route, seal, or authority again.
- If target explicitly fails before relay-ready is accepted, only the matching seal is released and held Session messages are submitted to the source route. A later failure, including cutover-submit failure, doesn't reopen source route.
Session-Actor Binding §8.1 governs validation by the transport, target, and Session owner and defines their boundaries.
11. Request Terminal Identity¶
OperationId and ReplyRouteId¶
OperationIdis a non-zero identity made of twou64words (high,low).ReplyRouteIdis a separate non-zerou64. Both are unique within the source owner's lifecycle; wrapping or reusing either is a terminal runtime error.- The operation ID is a deduplication identity and does not substitute for the reply route.
Registries and durable records do not reduce
OperationIdto one word. - Durable terminal identity is the combination of the unchanging
RelocationId, the fence of the side that started the request, and theOperationId.
Terminal Completion Tracking¶
- The target writes terminal completion and delivery state to a new immutable relocation root, then updates
TerminalCompletionCountandPendingRelayCounttogether via an authority CAS. replyRelayuses the original reply route and the source lease fence that identifies that request.- The source sends an authenticated
replyRelayAckafter accepting the terminal result, or after confirming it is already terminal. - A closed physical connection is not evidence of terminal delivery.
The Completed Condition¶
Completed is allowed only when the accepted request count equals the
terminal completion count and pending relay is 0. If the ACK cannot be
confirmed while the source lease is still valid, Retire ends as
ForceStopped, preserving the relocation root and reply bytes for the
retention period.
Root Replacement¶
- Root replacement verifies a new immutable root's reference/checksum/inventory digest, then links it via an authority CAS.
- A conflict loser root is cleaned up as an orphan.
- Cleanup releases the reference from Location authority, then performs the Relocation Store delete.
- A published reference's permanent missing state, a checksum mismatch, or an inventory digest mismatch is a non-retriable
RelocationDataLost, and does not roll back a committed owner/membership back to the source.
SendReady Record and Binding Completion¶
The schema's Framework service-wire SendReady record kind 12 is service control. Core 0.13's
per-operation send_completion and the binding awaitable are a separate contract that reports
HWM-retry completion. Retiring the binding readiness callback does not remove the service-wire
record or its schema value.
Wire Records and Shared Capacity¶
A wire command does not grant bypass; ordinary control and malformed records also use shared permits. Receive and Dispatch Loop owns pre-classification permits; Payload Ownership owns ordinary record-storage lifetime.
12. Verification Requirements¶
The schema self-test, the golden-fixture decode results of the generated codecs, and the checked-in codec tables alone confirm the following.
Schema and Codec
- The generated output and the checked-in codec tables match the schema.
- Every runtime produces the same value and failure from the framework-json-v1 golden fixture for typed application messages.
Decode and Admission
- A record with an incomplete length, item count, UTF-8 validity, enum/flag value, or topology direction is rejected as a protocol error before application dispatch and never reaches the application.
- An update with a lower DescriptorRevision within the same lifecycle, or with different bytes at the same revision, is rejected as a protocol error; resending the same revision with the same bytes leaves state unchanged (idempotent).
- Inbound traffic other than probe/ACK does not extend the probe round-trip deadline.
Relocation Transfer and CAS
- Connection-bound accepted work never ends up in a relocation envelope.
- A checksum mismatch on an assembled relocationState stage is an explicit failure — never a blind retry and never a partial-assembly restore.
- If the digest of the participant list the store knows about differs from the list recorded for the relocation, it ends as RelocationDataLost.
- Actor relocation commit changes the owner and the target Entry Spot membership atomically.
- Ready is never published before the owner commit, the restore/replay and timer restoration, the queue merge, and the dispatch switch have finished.
- Relocation adapter bytes are never interpreted as JSON or as a typed state contract.
Terminal Completion
- For the retained pending-request terminal record path (§11 Root Replacement), a crash before the Captured CAS is not treated as durable replay; writing and verifying the top-level record happens before the authority CAS, and the authority releasing that reference happens before the record is deleted.
- Pending relay is never completed by a physical disconnect alone, without a replyRelayAck.
Channel·Transport topic table of contents · Spec table of contents · Previous: 05. Transport Liveness