Task
Every Sage-parity action method returns the same empty ActionResponse {} (crates/dig-wallet/src/sage/types.rs:1693-1695), so a caller cannot tell "I did the thing" from "there was nothing to do." Enumerate the affected methods, then give each one an outcome it can actually report.
Sibling of #254, parent
https://github.com/DIG-Network/dig_ecosystem/issues/2870.
This is the same defect the trusted-peer work just fixed
control.chiaPeers.remove answered removed: true unconditionally, so the only un-trust remedy
reported success whether or not it removed anything. That was a HIGH, gating finding — a surface lying
about whether a privileged action took effect — and it was closed by replacing the boolean with
ChiaPeerRemovalOutcome ("removed" | "no_such_peer", no boolean companion, so a consumer must
match).
ActionResponse {} is that shape generalised: its own doc-comment says "An empty response shared by
every action method above ({})".
Confirmed members, and step 1 is to complete the list
Measured directly above the definition:
update_nft
update_nft_collection
redownload_nft
increase_derivation_index
The list is almost certainly longer — "every action method above" is a comment, not a bound. Step 1 is
enumerating the real set from the dispatch table rather than from the comment, and stating the count.
Why increase_derivation_index is the one that makes this money-class
The HD derivation index decides which addresses this node scans. If the call silently no-ops — a
malformed request, a clamp, an unwritten row — the operator is told it succeeded and funds at higher
indices stay invisible. There is no error, no retry, and no way for the caller to know. That is a
surface lying about money, which is the narrow class §2.6 keeps as a stop even during end-to-end-first
work.
redownload_nft on a nonexistent NFT and update_nft on an unknown id have the same structure with a
lower blast radius.
Scope
- Enumerate every method returning
ActionResponse, from the dispatch table.
- For each, decide whether "nothing to do" is distinguishable and worth reporting. Where it is, give
it a typed outcome — model it on ChiaPeerRemovalOutcome rather than inventing a second shape, and in
particular do not add a bool companion: the reason that enum works is that a consumer cannot
ignore it.
- Where an empty response is genuinely right — a true idempotent no-op with no observable difference —
say so in the doc comment, so the next reader does not re-litigate it.
The constraint that makes this harder than it looks
This is a Sage-PARITY surface. A third-party client may parse these bodies strictly, so changing a
response shape is a compatibility question, not just a correctness one. Establish what Sage itself returns
for each method before changing any shape — matching Sage may be the right answer even where it is
less informative, and if so that is a finding worth writing down rather than a defect to fix.
Note the same PR already changed this wire once: PeerRecord.peak_height became null and banned was
added on get_peers, reasoned as safe because the old value was always 0 and nothing writes peer
telemetry. Use the same standard of evidence here.
Evidence
Per changed method, a test proving the "nothing to do" case is reported differently from the success
case — and, for any method left as {}, the recorded reason.
Effort
Medium. Step 1 is mechanical; step 2 is a per-method judgement with a compatibility constraint.
Task
Every Sage-parity action method returns the same empty
ActionResponse {}(crates/dig-wallet/src/sage/types.rs:1693-1695), so a caller cannot tell "I did the thing" from "there was nothing to do." Enumerate the affected methods, then give each one an outcome it can actually report.Sibling of #254, parent
https://github.com/DIG-Network/dig_ecosystem/issues/2870.
This is the same defect the trusted-peer work just fixed
control.chiaPeers.removeansweredremoved: trueunconditionally, so the only un-trust remedyreported success whether or not it removed anything. That was a HIGH, gating finding — a surface lying
about whether a privileged action took effect — and it was closed by replacing the boolean with
ChiaPeerRemovalOutcome("removed" | "no_such_peer", no boolean companion, so a consumer mustmatch).
ActionResponse {}is that shape generalised: its own doc-comment says "An empty response shared byevery action method above (
{})".Confirmed members, and step 1 is to complete the list
Measured directly above the definition:
update_nftupdate_nft_collectionredownload_nftincrease_derivation_indexThe list is almost certainly longer — "every action method above" is a comment, not a bound. Step 1 is
enumerating the real set from the dispatch table rather than from the comment, and stating the count.
Why
increase_derivation_indexis the one that makes this money-classThe HD derivation index decides which addresses this node scans. If the call silently no-ops — a
malformed request, a clamp, an unwritten row — the operator is told it succeeded and funds at higher
indices stay invisible. There is no error, no retry, and no way for the caller to know. That is a
surface lying about money, which is the narrow class §2.6 keeps as a stop even during end-to-end-first
work.
redownload_nfton a nonexistent NFT andupdate_nfton an unknown id have the same structure with alower blast radius.
Scope
ActionResponse, from the dispatch table.it a typed outcome — model it on
ChiaPeerRemovalOutcomerather than inventing a second shape, and inparticular do not add a
boolcompanion: the reason that enum works is that a consumer cannotignore it.
say so in the doc comment, so the next reader does not re-litigate it.
The constraint that makes this harder than it looks
This is a Sage-PARITY surface. A third-party client may parse these bodies strictly, so changing a
response shape is a compatibility question, not just a correctness one. Establish what Sage itself returns
for each method before changing any shape — matching Sage may be the right answer even where it is
less informative, and if so that is a finding worth writing down rather than a defect to fix.
Note the same PR already changed this wire once:
PeerRecord.peak_heightbecamenullandbannedwasadded on
get_peers, reasoned as safe because the old value was always0and nothing writes peertelemetry. Use the same standard of evidence here.
Evidence
Per changed method, a test proving the "nothing to do" case is reported differently from the success
case — and, for any method left as
{}, the recorded reason.Effort
Medium. Step 1 is mechanical; step 2 is a per-method judgement with a compatibility constraint.