Skip to content

mint_status can report a CONFIRMED mint as 'can never confirm' — guard the conclusion, not the read #208

Description

@MichaelTaylor3d

Task

mint_status can report a mint that CONFIRMED as one that "can never confirm", and the fix must go
on that CONCLUSION rather than on the read it is derived from. dig-app must refuse to render a
terminal mint failure that rests on an absence no tier warranted.

Context

dig-account 0.13.0, src/mint/did.rs:236-247, concludes:

if did_record.is_none() && source.as_ref().is_some_and(CoinRecord::is_spent) {
    return Ok(MintStatus::Failed { reason: "the funding coin was spent by a different spend; this mint can never confirm" });
}

Both reads are coin_record. Per dig-app#169, control.wallet.coinById is NOT scoped to the
wallet, so dig-node's crates/dig-wallet/src/sage/routing.rs:31-40 routes it to the fallback tier
whatever the node's sync state and it reports synced: false on every reply — even for a coin its
own database holds (measured on dig-node 0.118.1 at peak 9148856). So a DID coin can read as absent
without warrant while the funding coin truthfully reads as spent, and the conclusion fires for a
mint that landed: real XCH spent, a permanent on-chain identity created, and the app saying it can
never confirm.

The read-level fix was tried and rejected in dig-app#169, with the trace. Guarding
coin_record makes the read error on every poll, which ChainMint::look
(crates/dig-app-core/src/account/chain_mint.rs:341) maps to Sighting::Unreachable, which
await_confirmation (crates/dig-app-core/src/account/mint.rs:258,281) turns into
MintOutcome::ConnectionLost after twelve looks — so every mint on a healthy machine ends in a
false "the chain could not be reached", well inside a mainnet confirmation window. It also closes
profile creation entirely, because the same read is the lineage probe's first.

Skill/reference of "good"

professional-ui on never asserting a fact the app does not have; #2690's Unknown-is-not-Blocked
split. dig-app/SPEC.md §3.1c now records the open lie and the required rendering.

Scope

The conclusion, not the read. A MintStatus::Failed whose premise is an unwarranted absence must
surface as UNKNOWN / still checking — not as Rejected, not as Blocked, not as ConnectionLost.
ControlChainSource::last_freshness already records the tier that answered; the shape question is
how a ceremony generic over ChainSource gets at it without every test double growing a field.

Upstream alternative worth pricing first: fix dig-node's routing so a wallet-known coin read by id is
answered from the database with an honest synced, which removes the premise instead of filtering
the conclusion. If that lands, the read-level guard becomes correct and dig-app#169's narrowing can
be reverted — its pinning test says so in its own doc comment.

Delegate

Same pass as the dig-app mint wiring (#2398 S1 / dig-app#170) — that PR is what makes this reachable.
Do not ship the wiring without it.

Evidence

A test in which the DID coin reads absent from an unsynced tier beside a spent funding coin, and the
surface still says "still checking". It must fail against today's mapping.


Moved from DIG-Network/dig_ecosystem#2937 so this repo's own PR can close it (CLAUDE.md §1.3). GitHub refuses
a private-to-public issue transfer, so the body was copied and comment history stayed on the
original.

Orchestrator ticket: https://github.com/DIG-Network/dig_ecosystem/issues/2398

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions