ChoiceArgumentsVariations is #[serde(untagged)], so variant order
decides which variant a payload deserializes into. The comment above
the enum explains the order by which fields each variant requires. That
reasoning is incomplete, and one gap lets a payload parse as the wrong
variant with no error.
TransferFactoryV2 and AcceptV2 carry different ExtraArgs
types, with different strictness:
transfer_factory::ExtraArgs has context.values: HashMap<String, ContextValue>. ContextValue is a tagged enum listing
eleven AV_* tags.
accept::ExtraArgs has context.values: serde_json::Value, which
accepts anything.
So a V2 factory payload whose context carries a tag ContextValue does
not list fails TransferFactoryV2 and then satisfies AcceptV2, which
needs only actors and extraArgs. The transfer field is dropped
without an error.
Measured. This payload parses as AcceptV2:
{
"transfer": { "sender": {"owner":"alice::1220ab","provider":null,"id":""}, "...": "..." },
"actors": ["alice::1220ab"],
"extraArgs": {
"context": { "values": { "k": { "tag": "AV_BrandNew", "value": "x" } } },
"meta": { "values": {} }
}
}
Nothing in this repository deserializes the enum at runtime. Every
use constructs it for serialization, so this is latent rather than
live. It becomes live if a consumer parses a stored command, or if the
registry ever adds an AV_* tag and something round-trips a payload.
The existing unit tests do not catch it, because they all use an empty
context, which both variants accept.
Two ways to close it, and both change a shared public type, so this
needs a decision rather than a quick patch.
- Put
#[serde(deny_unknown_fields)] on accept::v2::ChoiceArguments
and accept::ChoiceArguments. A payload carrying transfer then
falls through to Generic, which is visible rather than wrong.
- Give the enum a real tag and stop relying on order.
Either way, add a test with a populated context and an unrepresentable
tag, so the ordering rule has cover.
ChoiceArgumentsVariationsis#[serde(untagged)], so variant orderdecides which variant a payload deserializes into. The comment above
the enum explains the order by which fields each variant requires. That
reasoning is incomplete, and one gap lets a payload parse as the wrong
variant with no error.
TransferFactoryV2andAcceptV2carry differentExtraArgstypes, with different strictness:
transfer_factory::ExtraArgshascontext.values: HashMap<String, ContextValue>.ContextValueis a tagged enum listingeleven
AV_*tags.accept::ExtraArgshascontext.values: serde_json::Value, whichaccepts anything.
So a V2 factory payload whose context carries a tag
ContextValuedoesnot list fails
TransferFactoryV2and then satisfiesAcceptV2, whichneeds only
actorsandextraArgs. Thetransferfield is droppedwithout an error.
Measured. This payload parses as
AcceptV2:{ "transfer": { "sender": {"owner":"alice::1220ab","provider":null,"id":""}, "...": "..." }, "actors": ["alice::1220ab"], "extraArgs": { "context": { "values": { "k": { "tag": "AV_BrandNew", "value": "x" } } }, "meta": { "values": {} } } }Nothing in this repository deserializes the enum at runtime. Every
use constructs it for serialization, so this is latent rather than
live. It becomes live if a consumer parses a stored command, or if the
registry ever adds an
AV_*tag and something round-trips a payload.The existing unit tests do not catch it, because they all use an empty
context, which both variants accept.
Two ways to close it, and both change a shared public type, so this
needs a decision rather than a quick patch.
#[serde(deny_unknown_fields)]onaccept::v2::ChoiceArgumentsand
accept::ChoiceArguments. A payload carryingtransferthenfalls through to
Generic, which is visible rather than wrong.Either way, add a test with a populated context and an unrepresentable
tag, so the ordering rule has cover.