Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3166
Sibling: #412 (step 7 shipped the observation this reports on)
Found by: the dig-app#289 lane, on the installed node, 2026-08-30
Task
dign mirror bond-states --json returns {"state":"unknown","reason":"chain_unreadable"} on a node
that is synced, with 8 chia peers, and reading the chain fine for balances. Establish why the bond
observation cannot read a chain that the balance path reads successfully, and make it able to.
The measurement
On the installed dign 0.172.0, at the same moment, on the same machine:
| call |
result |
control.wallet.balance |
{"balance":1010000, "synced":true} — 1 010 $DIG, independently corroborated against api.coinset.org |
dign mirror bond-states --json |
{"state":"unknown","reason":"chain_unreadable"} |
chia_peer_peak_height equals the replica peak, so this is not the silent-staleness shape NC-12
records. The node can read the chain. The bond observation says it cannot.
Why this matters rather than being cosmetic
#412 step 7 shipped the observation (merged 5df3e34) specifically so control.mirror.bondStates
would stop answering chain_unreadable on every call. It now constructs a real observation — and on a
real machine still answers chain_unreadable. So the capability exists and the surface does not
receive it, which is the gap between a code path existing and a person seeing it work (§2.6).
Downstream, this is a money surface going blank: dig-app's collateral heading derives its locked
figure from this method, so on this configuration a real user sees "How much is locked up is not
known — your node cannot read the chain right now." That sentence is honest, which is why this is a
capability gap and not a lie — but it is the wrong answer, and the honest-unknown is masking it.
Scope
Find which chain read the observation performs that the balance path does not, and why it fails.
Candidates worth checking before anything else, in order:
dig_mirror_coin::list needs a ChainSource plus the operator wallet's own puzzle hash. The
balance path answers per-address through a different route; if the observation's ChainSource is
absent or differently constructed, chain_unreadable is the honest report of a missing dependency
rather than a failing read.
- Whether the reason is accurate at all.
chain_unreadable may be the catch-all arm for several
distinct failures. If so, the reason itself is the first defect — an operator told the chain is
unreadable will go looking at peers, which are fine.
- Whether it differs by configuration (
enable_live_broadcast defaults false; the operator wallet may
hold nothing).
The trap to avoid
Do not "fix" this by making the observation return an empty page. A page of no rows is a definite
claim that this node holds no bonds, and control.rs's own comment says so: "A caller MUST NOT
substitute an empty observation for an Err — a page of no rows says 'this node holds no bonds',
which is a definite claim about money." If the read genuinely cannot be performed, the honest unknown
stays; the deliverable is then a more accurate reason, not a fabricated zero.
Acceptance
On a real machine with a synced node, dign mirror bond-states --json returns either a real
observation, or an unknown whose reason names the actual obstacle. Verified by running it — not by a
passing test, and not from the node's own logs alone.
Parent epic: https://github.com/DIG-Network/dig_ecosystem/issues/3166
Sibling: #412 (step 7 shipped the observation this reports on)
Found by: the dig-app#289 lane, on the installed node, 2026-08-30
Task
dign mirror bond-states --jsonreturns{"state":"unknown","reason":"chain_unreadable"}on a nodethat is synced, with 8 chia peers, and reading the chain fine for balances. Establish why the bond
observation cannot read a chain that the balance path reads successfully, and make it able to.
The measurement
On the installed
dign 0.172.0, at the same moment, on the same machine:control.wallet.balance{"balance":1010000, "synced":true}— 1 010 $DIG, independently corroborated againstapi.coinset.orgdign mirror bond-states --json{"state":"unknown","reason":"chain_unreadable"}chia_peer_peak_heightequals the replica peak, so this is not the silent-staleness shape NC-12records. The node can read the chain. The bond observation says it cannot.
Why this matters rather than being cosmetic
#412 step 7 shipped the observation (merged
5df3e34) specifically socontrol.mirror.bondStateswould stop answering
chain_unreadableon every call. It now constructs a real observation — and on areal machine still answers
chain_unreadable. So the capability exists and the surface does notreceive it, which is the gap between a code path existing and a person seeing it work (§2.6).
Downstream, this is a money surface going blank: dig-app's collateral heading derives its locked
figure from this method, so on this configuration a real user sees "How much is locked up is not
known — your node cannot read the chain right now." That sentence is honest, which is why this is a
capability gap and not a lie — but it is the wrong answer, and the honest-unknown is masking it.
Scope
Find which chain read the observation performs that the balance path does not, and why it fails.
Candidates worth checking before anything else, in order:
dig_mirror_coin::listneeds aChainSourceplus the operator wallet's own puzzle hash. Thebalance path answers per-address through a different route; if the observation's
ChainSourceisabsent or differently constructed,
chain_unreadableis the honest report of a missing dependencyrather than a failing read.
chain_unreadablemay be the catch-all arm for severaldistinct failures. If so, the reason itself is the first defect — an operator told the chain is
unreadable will go looking at peers, which are fine.
enable_live_broadcastdefaults false; the operator wallet mayhold nothing).
The trap to avoid
Do not "fix" this by making the observation return an empty page. A page of no rows is a definite
claim that this node holds no bonds, and
control.rs's own comment says so: "A caller MUST NOTsubstitute an empty observation for an
Err— a page of no rows says 'this node holds no bonds',which is a definite claim about money." If the read genuinely cannot be performed, the honest unknown
stays; the deliverable is then a more accurate reason, not a fabricated zero.
Acceptance
On a real machine with a synced node,
dign mirror bond-states --jsonreturns either a realobservation, or an unknown whose reason names the actual obstacle. Verified by running it — not by a
passing test, and not from the node's own logs alone.