Skip to content

feat(mirror): add MirrorBondState::Unadvertised, the switch-on-but-nothing-to-advertise state - #41

Merged
MichaelTaylor3d merged 1 commit into
mainfrom
loop/mirror-unadvertised-state
Sep 1, 2026
Merged

feat(mirror): add MirrorBondState::Unadvertised, the switch-on-but-nothing-to-advertise state#41
MichaelTaylor3d merged 1 commit into
mainfrom
loop/mirror-unadvertised-state

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

Why

dig-node PR #484 fixes a real money lie: a node whose DIG_MIRROR_ADVERTISE_URLS list has no publishable entry creates no mirror coin, and the node reported every bond as unfunded, short_dig_base_units: 1010 — a demand for $DIG that would have bonded nothing. Its own log had been saying the true cause at every bring-up.

That fix needs a wire value, and this enum had none that is true. The gate on #484 checked the alternatives independently and reached the same conclusion:

  • unfunded names a figure and sends the operator to buy $DIG. False about money — the wallet may be full.
  • every CollateralUnknownReason under deferred blames a working subsystem (census, record, chain, wallet read).
  • disabled is the closest, and it is still wrong in a way that defeats the fix: results.rs says a client MUST NOT present Disabled as a fault, and SPEC.md defines it as the node-wide switch (§25.7) — which here is ON. Serving it obliges a conforming dig-app to stay silent about the single reason the node bonds nothing.

So the honest answer replaces a wrong answer with no answer. This adds the value that is neither.

What

MirrorBondState::Unadvertised — a bare tag, no payload, node-wide like disabled, meaning: the node holds the capsule, its collateralisation switch is ON, its wallet may be full and the price may be known, but it has no advertise URL a peer could fetch from, so it advertises nothing and creates no coin.

It IS a fault a client MUST surface. That is the whole distinction from disabled: both are node-wide and both mean "no coin", but one is a decision the operator already made, and the other is that decision silently not being honoured. The only remedy is a publishable advertise URL.

It covers the general state, not the one-off: the list being empty, and the list being set but with every entry rejected as non-absolute or reachable only from this machine. dig-node's refusal (lifecycle.rs:429) is over the post-classify accepted list, so the state is stated over that class.

Version — 0.28.0, MINOR

Additive: one new variant, one new wire token, no existing token, payload or field changed. Nothing that decoded before decodes differently. A client that has not adopted it fails to decode the new tag rather than silently mis-reading an old one, which is the correct direction for a contract whose whole purpose is that these states are never collapsed.

Blast radius

gitnexus was NOT used and I say so explicitly. Its registered index for this repo is stale by construction (the lane analyses its own worktree, the registration points at the primary checkout), and a stale index answers impactedCount: 0 for a symbol with real callers — a false-safe zero. This was done by git grep over the branch plus direct read.

MirrorBondState referenced outside results.rs: kats.rs (fixtures and the token-uniqueness KAT), traits.rs:571 (the mirror_bond_states contract prose). No total match on it exists in this crate outside tests, so nothing silently absorbs the variant; the two counted assertions (7) in the uniqueness KAT failed on the addition and were updated deliberately rather than by wildcard.

Verification

cargo test --lib188 passed, 0 failed, 0 filtered out. Count checked, not exit status: cargo surfaced through this harness as [exited with code 0] while failing, twice during this change.

Red was genuine and at the decision. Before the variant existed:

error[E0599]: no variant, associated function, or constant named `Unadvertised`
              found for enum `MirrorBondState` in the current scope

Then, with the variant added but the uniqueness KAT's count left at seven:

failures:
    kats::the_bond_surface_wire_tokens_are_unique
test result: FAILED. 187 passed; 1 failed

That second red is the load-bearing one — it proves the new token is genuinely distinct on the wire rather than colliding with an existing one.

The new KAT asserts the property, not the outcome. an_unadvertised_bond_is_its_own_state_and_not_disabled pins the literal {"bond_state":"unadvertised"} bytes and asserts the value differs from Disabled. The second half is the one that matters: the nearest wrong implementation is exactly the interim mapping this release replaces, and a test that only round-tripped the new value would pass against it.

cargo clippy --all-targets -- -D warnings clean; cargo fmt --check clean.

Coherence

SPEC.md gains the row, the "seven of the eight states" count, and a normative paragraph stating that disabled and unadvertised are both node-wide and only one of them is a fault. traits.rs gains the matching MUST on the implementation side. Both land in this PR (§4.2).

Next, and it is not a follow-up ticket

dig-node #484 adopts this the moment it is on crates.io: it drops its interim Unadvertised → Disabled mapping and restores its own SPEC.md §25.8 definition of disabled to mean only the §25.7 switch. Release-first (§4.1) — this ships first, gets verified on the index, and only then does the consumer bump.

…thing-to-advertise state

A node whose advertise-URL list has no publishable entry creates no mirror
coin, however much $DIG it holds and however definitely its own
collateralisation switch is ON. The enum had no value for that, so dig-node
had to choose among values that are all false about it: `unfunded` names a
figure and sends the operator to buy $DIG that would bond nothing, every
`deferred` reason blames a working subsystem, and `disabled` is the operator's
own switch -- which this contract says a client MUST NOT present as a fault,
so serving it there obliges a conforming client to stay silent about the one
thing actually wrong.

`Unadvertised` is that state, and it IS a fault a client must surface. That is
the entire distinction from `Disabled`: both are node-wide and both mean no
coin, but one is a decision the operator already made and the other is that
decision silently not being honoured.

Additive: a new variant, a new wire token, no existing token or payload
changed. SPEC.md and the trait guidance name the pair apart, and the KAT
asserts both the token and that it differs from `disabled` -- the nearest
wrong implementation being the interim dig-node mapping this replaces.

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d
MichaelTaylor3d merged commit 4e346a3 into main Sep 1, 2026
8 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the loop/mirror-unadvertised-state branch September 1, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant