#[non_exhaustive]pub enum InboundDelegateMsg<'a> {
ApplicationMessage(ApplicationMessage),
UserResponse(UserInputResponse<'a>),
GetContractResponse(GetContractResponse),
PutContractResponse(PutContractResponse),
UpdateContractResponse(UpdateContractResponse),
SubscribeContractResponse(SubscribeContractResponse),
ContractNotification(ContractNotification),
DelegateMessage(DelegateMessage),
UnsubscribeContractResponse(UnsubscribeContractResponse),
WakeupFired {
tag: Vec<u8>,
},
Lifecycle(LifecycleEvent),
}Expand description
Messages delivered into a delegate’s process() function.
This is the inbound counterpart of OutboundDelegateMsg and sits on the
host↔delegate wire boundary.
Marked #[non_exhaustive] so future variants can be added without a
source-level break; downstream match sites must include a wildcard arm.
OutboundDelegateMsg is deliberately not marked, and the asymmetry is
the point — see the rationale on that enum. (An earlier version of this
comment asserted that OutboundDelegateMsg already carried the attribute.
It never has.)
§Wire format and compatibility
bincode, variant index 0..=N in declaration order. Two rules follow, and the compiler enforces neither:
- Never insert or reorder a variant. That silently reassigns every later
tag, so delegate WASM compiled against an older stdlib decodes the same
bytes into a different variant — no error, just a message quietly
reinterpreted as another one.
delegate_msg_variant_tags_are_pinnedpins the tag of every variant of both enums so a reorder fails CI instead. - Appending is compatible in exactly one direction. An old sender’s old
variant always decodes on a new receiver. A new sender’s new
variant does not decode on an old receiver: bincode rejects the
unknown tag — as
ErrorKind::Custom("invalid value: integerN, expected variant index 0 <= i < M"), since bincode hands the index to serde’s derived visitor rather than validating it itself. (NotInvalidTagEncoding, which bincode only ever produces for a badOptiondiscriminant.)#[non_exhaustive]does not change this — it is a source-level attribute with no effect on the encoding, and serde has no unknown-variant fallback to fall back to.
For this enum the incompatible direction is a new host → old delegate,
and it is mostly unreachable in practice: the host emits a response variant
only in reply to the matching request variant, so a delegate that never
emits a request added in stdlib version X never receives the response added
in X. Deployed delegate WASM therefore keeps working against an upgraded
node. The genuinely constrained direction is delegate → host; see
OutboundDelegateMsg.
The compatibility claims above are asserted, not merely asserted-in-prose,
by the delegate_wire_compat test module at the bottom of this file.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
ApplicationMessage(ApplicationMessage)
UserResponse(UserInputResponse<'a>)
GetContractResponse(GetContractResponse)
PutContractResponse(PutContractResponse)
UpdateContractResponse(UpdateContractResponse)
SubscribeContractResponse(SubscribeContractResponse)
ContractNotification(ContractNotification)
DelegateMessage(DelegateMessage)
UnsubscribeContractResponse(UnsubscribeContractResponse)
WakeupFired
Delivered by the host when a periodic wake-up the delegate declared in
its manifest falls due (see
WakeupSchedule and
#[delegate(manifest(wakeups = [tag = seconds]))]). tag is the
declared tag’s bytes, so a delegate with several schedules can tell
them apart. Owned ('static).
§History: why the request half is a manifest entry
This variant (tag 9) shipped in 0.10.0 with a run-time request half,
DelegateCtx::schedule_wakeup, backed by the host import
__frnt__delegate__schedule_wakeup. No released freenet-core ever
provided that import, and a delegate that imports a function the node
does not provide fails to INSTANTIATE, so 0.11.0 removed it (pinned by
host_imports’s the_imports_removed_in_0_11_0_have_not_come_back).
0.12.1 restores the feature with the request in the manifest instead.
That is not a stylistic choice: a manifest field is ignored by a node
that does not know it, so ONE delegate build loads everywhere and
simply receives no WakeupFired on a node without the feature. An
import, or a new OutboundDelegateMsg variant (older nodes fail to
decode the whole outbound batch it sits in), would make that same build
dead or lossy on every node that predates the host side.
A delegate only receives this if its manifest declares a wake-up, which requires a stdlib that defines this variant, so no deployed delegate can be sent a tag it cannot decode.
§What the context cache holds during a wakeup
Nothing the delegate should read. freenet-core’s delegate context cache
is keyed per delegate, not per conversation, and entries are pruned
after DELEGATE_CONTEXT_TTL (10 minutes). Two consequences, both
arguing the same way:
- A wake-up is periodic and not tied to any one exchange, so whatever context exists when it fires belongs to something else or has expired; with intervals of 10 minutes or more it is simply gone.
- If the delegate happens to have a live context from some other in-flight exchange inside that window, it belongs to that exchange. Reading it during a wakeup would be reading another conversation’s working state.
This is why the variant carries no DelegateContext: there is no
coherent value to put in it. A delegate needing state across a wakeup
reads it from its secrets, which is what core’s own cache doc
recommends for exactly this case.
Appended at tag 9, after UnsubscribeContractResponse at tag 8.
Lifecycle(LifecycleEvent)
A lifecycle event: the delegate was installed on this node, or the
node started. See LifecycleEvent.
§What keeps this safe for deployed delegates
A delegate built against an older stdlib cannot decode this tag (see
the wire-format note on this enum). The host sends it only to a
delegate whose embedded DelegateManifest
lists the event’s LifecycleKind, and
the manifest macro only lists kinds the delegate’s own stdlib defines
(it names each one through the stdlib, so anything else fails to
compile). The first half is a property of the host implementation, not
of the format; freenet-core pins it.
Carries no DelegateContext, for the same reason as WakeupFired: it
opens a conversation rather than continuing one.
A client cannot send this variant: the host delivers it itself and
refuses it in a client’s ApplicationMessages.
Appended at tag 10, after WakeupFired at tag 9.