Skip to content

feat(mirror): declare control.mirror.bondState, the per-(store, root) bond state - #38

Merged
MichaelTaylor3d merged 3 commits into
mainfrom
feat/412-mirror-bond-state
Aug 30, 2026
Merged

feat(mirror): declare control.mirror.bondState, the per-(store, root) bond state#38
MichaelTaylor3d merged 3 commits into
mainfrom
feat/412-mirror-bond-state

Conversation

@MichaelTaylor3d

@MichaelTaylor3d MichaelTaylor3d commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

DO NOT MERGE — gate round in progress.

Declares control.mirror.bondStates, the per-(store, root) mirror bond state surface dig-node
SPEC.md §25.8 requires. Release-first: the interface publishes before the node serves it.

Step 6 of DIG-Network/dig-node#412. Epic DIG-Network/dig_ecosystem#3166.
Unblocks (does NOT close) DIG-Network/dig-app#300 and the locked-total half of
DIG-Network/dig-app#289. This PR closes nothing — verified with
gh pr view 38 --json closingIssuesReferences[].

The shape

control.mirror.bondStates · TOKEN-GATED · Category::Collateral · Routing::Owned

params  { after?: { store_id, root }, limit?: u32 }
result  { state: "known",
          entries: [ { store_id, root, bond_state, ...payload } ],
          complete: bool,
          cursor: { store_id, root } | null,     // key REQUIRED, null is meaningful
          locked_dig_base_units: u64,
          epoch: u64 }
      |  { state: "unknown", reason: "served_set_unknown" | "chain_unreadable" | "in_flight_unknown" }

Seven bond states, six of which mean "no coin yet" for different reasons and different remedies:

bond_state payload remedy
bonded coin_id, epoch, amount_dig_base_units none
pending wait
unfunded short_dig_base_units add $DIG — the only funding-alarm state
deferred reason (CollateralUnknownReason, reused) none; the wallet may be full
withheld none — Relayed provenance
disabled the operator's own switch
reclaiming coin_id, epoch, amount_dig_base_units wait — money still locked

The vocabulary reconciliation, and which side must move

§25.8's withheld (a Relayed capsule) and dig-node's BondState::Withheld (the node-wide
collateralisation switch) were the same word for different states, and §25.8's reclaiming had
no BondState variant at all. They differ in SCOPE and in REMEDY: an operator told "withheld" about
a disabled node goes looking at content, one told "withheld" about a relayed capsule goes looking
for a switch. This contract keeps three states apart, and neither existing use survives
unchanged
:

  • dig-node MUST rename BondState::WithheldDisabled, and add Withheld (Relayed
    provenance) + Reclaiming.
  • dig-node SPEC.md §25.8 MUST gain disabled, which it could not express at all.

Recorded normatively in this crate's SPEC.md §4.2g and on MirrorBondState's doc.

Stated vacuity: withheld is unreachable until dig-node's surface enumerates its SERVED set
rather than its Held set — a Relayed capsule is by construction absent from the desired-bond
set, so a Held-keyed derivation answers "no such row" where the contract promises "withheld on
purpose". Declared here deliberately; the serving half must reach it or say it cannot.

"No bond" vs "cannot tell", on the wire

They live at different levels, and that is the design. Every per-row state is a DEFINITE
statement — including the six that mean no coin. A fact the node could not read makes the whole
call
{state:"unknown", reason}; there is no per-row unknown and no empty-list fallback, because
a truncated list and a complete one read identically and the rows a broken read drops are exactly
the bonds nobody is then watching. entries: [] + complete: true is an ANSWER (this node holds no
bonds), never a read failure. Requirement-unknown is NOT a "cannot tell" reason — it is the definite
per-row deferred.

Design constraints, discharged

  1. No absent field collapses. cursor carries deserialize_with = "required_option" and
    complete carries no default; an absent key is a decode FAILURE. No #[serde(default)] sits
    on any field whose absence carries meaning.
  2. Money unit named in the field. short_dig_base_units, amount_dig_base_units,
    locked_dig_base_units — $DIG has 3 decimals; never mojos. bonded/reclaiming amounts are
    read FROM THE COIN, never from today's requirement.
  3. Reuse before inventing. CollateralUnknownReason is reused verbatim for deferred rather
    than copied. MIRROR_BOND_STATES_MAX_LIMIT is defined as COINS_BY_PARENT_MAX_LIMIT, not as
    an equal literal. Category::Collateral, required_option, control_call!, the
    refuse-don't-clamp limit idiom and the Known/Unknown tagged-union shape all follow the house
    pattern.
  4. locked_dig_base_units is the WHOLE-SET total including reclaiming coins, node-computed. A
    client summing the page under-reports locked money by a page boundary and shows unspendable funds
    as available. This is what dig-app#289 reads.

Blast radius checked

gitnexus has no index in this worktree; blast radius taken by git grep across origin/main of
dig-node and dig-app plus a direct read — an allowed §2.0 fallback, stated here rather than implied.

  • Additive only. No existing type, field, token, constant or match arm changed shape. Every
    0.25.0 payload still decodes; verified by the existing 182 KATs, all still green.
  • ControlHandler gains one required method. Zero implementors exist outside this crate's own
    MockNode
    — dig-node dispatches manually in crates/dig-node-service/src/control.rs and has no
    impl ControlHandler (the win_service.rs hit is service_control_handler, unrelated).
  • The intended forcing function: dig-node's
    crates/dig-node-service/tests/control_contract_conformance.rs iterates ControlMethod::ALL, so
    adopting 0.26.0 goes RED until the node serves the method and adds the dign verb — which is step
    6's node half, by design.
  • Risk: LOW. No custody path, no spend, no existing behaviour altered.

Verification

  • cargo test --lib183 passed, 0 failed (182 pre-existing + the new KATs).
  • cargo fmt --check clean; cargo clippy --all-targets -- -D warnings clean.
  • Both new KATs proved load-bearing by mutation (committed first, then mutated, then restored):
    • deserialize_with = "required_option"#[serde(default)] on cursor
      an_absent_paging_key_never_becomes_a_definite_answer FAILS. The mutation targets the FIELD
      ATTRIBUTE, not required_option's body: an absent key never reaches a deserialize_with
      function, so those cover disjoint inputs and only the attribute is under test.
    • #[serde(rename = "withheld")] on Disabledthe_bond_surface_wire_tokens_are_unique FAILS.
  • The bound is pinned from BOTH sides: MIRROR_BOND_STATES_MAX_LIMIT must PASS, MAX + 1 and
    0 must be refused as INVALID_PARAMS, and the refusal is enforced on the way IN.
  • Fixtures designed against the nearest wrong implementation: the locked-total vector is a
    TRUNCATED page whose visible amounts sum to less than locked_dig_base_units (a page that
    happened to sum to the total would pass against a client re-deriving it); the state vectors carry
    two roots of the SAME store in different states (a store-keyed surface would merge them); the
    wire-token uniqueness test reads tokens back through serde rather than off a second list that
    could agree with itself.

Version

0.25.0 → 0.26.0. On a 0.x line the minor slot is the breaking slot, and additive-only is still
a minor here. Nothing existing changed shape, so a 0.25 consumer keeps decoding.

§2.4b

This crate declares no dig-* or chia-* dependencies — serde, serde_json, async-trait,
semver and dev-only futures, all at 1/0.x majors already current. Nothing to bump.

Docs

SPEC.md §4.2g (new, normative) + its method table row; README.md catalog row. The crate's own
exhaustiveness KAT (the_spec_and_readme_name_every_catalogued_method) enforces both and caught the
gap before I did.

Gate round 1 — both GATING findings fixed (bcbf9da)

  1. provenance_unknown — a fourth whole-call unknown reason, so a producer that enumerates its
    pairs but cannot see PROVENANCE has a conforming answer instead of a known page with its
    withheld rows silently absent. §4.2g's MUST is unchanged (not softened to SHOULD) and no per-row
    unknown was added; the new text only gives "say so" its one spelling.
  2. after is normalized and validated through the existing normalize_coin_id0x tolerated
    and stripped, uppercase / wrong-length / non-hex refused as -32602 INVALID_PARAMS in either
    half, on the way IN, and never coerced to start-of-set. Canonical lowercase unprefixed 64-hex is
    now pinned in §4.2g, on both MirrorBondKey fields and on the trait's ordering bullet, so the
    ascending order is defined over one spelling.

Two new KATs, each proved load-bearing by mutation: aliasing as_wire's new arm to
served_set_unknown fails the provenance KAT and the token-uniqueness KAT; replacing the cursor
check with .and_then(|k| normalize_bond_key(k).ok()) — the silent-restart behaviour — fails the
cursor KAT. Restored after each; cargo test --lib 185 passed, 0 failed, fmt + clippy clean.

…) bond state

Declares the surface dig-node SPEC.md 25.8 requires, release-first: the
interface publishes before the node serves it.

Seven states, six of which mean "no coin yet" for different reasons and
different remedies. Conflating "out of funds" with "withheld on purpose"
is what produces hourly funding alarms about a healthy node.

Reconciles a vocabulary mismatch rather than inheriting it: 25.8's
`withheld` (Relayed provenance) and dig-node's BondState::Withheld (the
node-wide switch) were the same word for different states, and 25.8's
`reclaiming` had no variant at all. The contract keeps all three apart
as withheld / disabled / reclaiming, and names which side must move.

Unblocks DIG-Network/dig-app#300 and the locked-total half of
DIG-Network/dig-app#289. Step 6 of DIG-Network/dig-node#412.

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d

Copy link
Copy Markdown
Contributor Author

IN PROGRESS — not the verdict. Gate on head 8101744b98defed94eb3dc47e3212c87fe878f40, fresh context.

Established so far, so it survives an interruption:

  1. The three-state split is CORRECT and I am not asking for it to be re-merged. withheld / disabled / reclaiming differ in scope (one capsule's provenance vs one node-wide switch) and, decisively, in remedy. The lane's argument holds and SPEC.md §4.2g states the consequence for dig-node explicitly (rename BondState::WithheldDisabled, add Withheld + Reclaiming, §25.8 gains disabled).
  2. The vacuity IS recorded normatively, not only in the PR body — SPEC.md §4.2g ("A node that enumerates only its desired-bond set can never emit withheld… MUST enumerate the SERVED set"), src/results.rs on MirrorBondState, and a MUST bullet on ControlHandler::mirror_bond_states in src/traits.rs. Three places, normative voice. But see the finding below: the contract says such a node "MUST say so" and gives it no way to say so.
  3. Call-level-only unknown is enforced by the types. MirrorBondStatesResult is an externally-tagged two-variant enum; no per-row unknown variant exists, and MirrorBondStatesUnknownReason (served_set_unknown / chain_unreadable / in_flight_unknown) deliberately excludes requirement-unknown, which stays the per-row deferred { reason: CollateralUnknownReason }. entries: [] + complete: true is a genuine answer, documented as such.
  4. ControlMethod is #[non_exhaustive] (src/method.rs:80) and SPEC.md §7.1 makes adding a method an additive MINOR, so 0.26.0 is right for the method-catalog half.
  5. closingIssuesReferences is empty — this PR closes nothing, as intended for one step of #412.
  6. Unit naming is cleanshort_dig_base_units, amount_dig_base_units, locked_dig_base_units; no field on a $DIG quantity says mojos, and both SPEC.md §4.2g and the rustdoc state the 3-decimal / not-a-mojo distinction.

Two findings forming, posted inline shortly: the unexpressible partial served set, and after's unvalidated hex key. Mutation spot-check of the absent-cursor KAT still running.

@MichaelTaylor3d MichaelTaylor3d left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGES-REQUIRED

Head reviewed: 8101744b98defed94eb3dc47e3212c87fe878f40 (resolved from the remote myself). Fresh context, no prior involvement.

(GitHub returns 422 for a REQUEST_CHANGES event on a same-identity PR, so this verdict is recorded as a comment review. The two open inline threads are what bar the merge, via required_conversation_resolution.)

The three-states judgement: the split is RIGHT. Do not re-merge them.

withheld / disabled / reclaiming are three states, and the lane's argument holds on both axes it names. They differ in scope — one capsule's Relayed provenance versus one node-wide switch — and decisively in remedy: an operator told "withheld" about a disabled node goes looking at content when the answer is a switch. reclaiming is not a bookkeeping nicety either; its money is still locked, so folding it into "unbonded" would report unspendable funds as available, which is the same money lie from the other end. Conflating any two reproduces dig-app#300's actual defect. The PR is also right that neither existing use survives unchanged — dig-node must rename BondState::Withheld to Disabled and add the other two, and §25.8 must gain disabled — and that consequence is stated normatively in SPEC.md §4.2g rather than only in the PR body, which is the right place for it.

The vacuity IS recorded normatively, and that is where finding 1 lives

It is written in three places in normative voice (SPEC.md §4.2g, MirrorBondState's rustdoc, a MUST bullet on ControlHandler::mirror_bond_states), and it names what the serving half must do: enumerate the served set, not the Held set. That clears the bar.

But the contract's own escape clause has no wire representation. §4.2g says a producer that cannot enumerate the served set "MUST say so", and all three MirrorBondStatesUnknownReason variants describe infrastructure failures — none describes a producer that enumerates bonds fine but is blind to provenance. Its only conforming-looking move is a known page with complete: true and every withheld row silently missing: the exact "no such row" failure §4.2g forbids, with complete: true asserting a falsehood. That leaves withheld tickable on a conformance list while unreachable. Gating, because release-first means this file decides the producer's options. Two acceptable fixes in the inline thread.

Verified

  • Call-level-only unknown is enforced by the types. MirrorBondStatesResult is a two-variant tagged union with no per-row unknown anywhere in MirrorBondEntry; requirement-unknown correctly stays the per-row deferred { reason: CollateralUnknownReason } and is explicitly excluded from the call-level reason set. entries: [] with complete: true is a genuine answer, documented as such in the type, the SPEC and the trait — not a degenerate unknown.
  • The mutation spot-check is real, and it hit the reachable target. In my own worktree at 8101744, changing deserialize_with = "required_option" to #[serde(default)] on cursor made an_absent_paging_key_never_becomes_a_definite_answer FAIL. Restored, worktree removed, shared checkout untouched (git status --porcelain empty). The lane's stated reason for mutating the field attribute rather than required_option's body is correct: an absent key never reaches a deserialize_with fn, so the two cover disjoint inputs and only the attribute is under test here.
  • The other KATs are load-bearing by construction, not decoration. golden_bond_state_vectors_pin_every_state carries two roots of the same store in different states, so a store-keyed surface fails. the_bond_surface_wire_tokens_are_unique reads tokens back through serde rather than off a second list. the_locked_total_spans_pages_and_is_never_the_page_sum uses a truncated page whose visible sum is strictly less than the total, and asserts that inequality, so a fixture drift making them equal fails loudly rather than going vacuous. the_bond_page_bound_is_enforced_from_both_sides proves MAX passes as well as MAX+1 and 0 refusing — both directions, which is what a one-sided bound proof hides.
  • locked_dig_base_units is unambiguously the whole-set total including reclaiming coins, in the rustdoc, SPEC.md §4.2g, the README row and the trait MUST bullet, each saying a client MUST NOT sum the page. The KAT distinguishes the two implementations rather than merely exercising the field.
  • Additive-only, and 0.26.0 is right. No existing type, field, token or constant changed shape; ControlMethod is #[non_exhaustive] (src/method.rs:80) and SPEC.md §7.1 makes a new method an additive MINOR. Full suite green at head, including doctests.
  • ControlHandler's new required method is safe here. The only implementor is this crate's MockNode; dig-node dispatches manually in crates/dig-node-service/src/control.rs and has no impl ControlHandler. It matches the precedent set by collateral_buffer, likewise required with no default.
  • The forcing function is stated as intentional in the PR body — adopting 0.26.0 turns dig-node's control_contract_conformance.rs red until the node serves the method — so it will not read as a regression.
  • closingIssuesReferences is []. Asked the parser, not the prose. Closes nothing, as intended for step 6 of #412.
  • Unit naming is clean. short_dig_base_units, amount_dig_base_units, locked_dig_base_units; no field on a $DIG quantity says mojos, and the 3-decimal, not-a-mojo distinction is restated wherever an amount appears.

Findings, ranked

  1. src/results.rs:2365 — GATING. The provenance-blind served set is unreportable; withheld is vacuous with no way for a producer to say so.
  2. src/params.rs:1724 — GATING. after is an unvalidated, unnormalized hex key, breaking the crate's own convention (WalletCoinsParams::validated normalizes its cursor); a 0x-prefixed key silently restarts the walk, and the ascending order is never specified as lowercase.

Non-gating, no thread opened

  • control.mirror.bondStates is the first method whose wire-name prefix (control.mirror.*) does not match its Category (Collateral). Defensible — the category is about what the method is about, and the collateral category already owns the requirement/margin/buffer trio this completes — but it is a new precedent worth being deliberate about, since a future control.mirror.* method that is not collateral-shaped will have to follow it or break it.
  • src/method.rs:25 runs well past the file's wrapping width; reflow when the file is next touched.

Re-review on push: I will re-verify both findings against the new head and resolve each thread that is genuinely addressed. Not editing, not merging, not undrafting.

Comment thread src/results.rs
Comment thread src/params.rs
…e a malformed bond cursor

Two gating findings on the control.mirror.bondStates contract.

SPEC.md 4.2g told a Held-only producer it MUST say it cannot enumerate the served
set, while every MirrorBondStatesUnknownReason was an infrastructure failure that
did not fit it. A node that enumerates its pairs perfectly well but cannot see
provenance therefore had no conforming answer, and its only conforming-LOOKING
move was a `known` page with `complete: true` and every `withheld` row silently
absent -- the exact failure 4.2g forbids, asserting a completeness the node knows
it lacks, and leaving `withheld` tickable on a conformance list while unreachable
on the wire.

Adds `provenance_unknown` as a fourth whole-call reason and binds it normatively:
a producer that cannot determine provenance MUST answer it and MUST NOT return a
`known` page. 4.2g's MUST is unchanged and is not weakened to a SHOULD, and no
per-row unknown is introduced -- the call-level-only design stands, because a
truncated page and a complete one read identically.

MirrorBondStatesParams::validated checked only `limit`, leaving `after.store_id`
and `after.root` unvalidated while the sibling WalletCoinsParams::validated
normalizes its cursor and refuses a malformed one. The order is ascending over the
key's STRING form, so a `0x`-prefixed key sorts before every canonical one: a node
that ignored it would RESTART the walk while appearing to resume, and a repeated
page inflates the running locked-$DIG total dig-app#289 accumulates -- wrong in the
reassuring direction and indistinguishable from a correct answer.

Both halves now go through the existing `normalize_coin_id` rather than a second
copy of the rule: `0x` tolerated and stripped, anything else refused as
-32602 INVALID_PARAMS, on the way IN as well as through `validated`. An
unparseable cursor is never coerced to start-of-set. The canonical form is pinned
as LOWERCASE unprefixed 64-hex in 4.2g, on MirrorBondKey's fields and on the trait's
ordering bullet, so the ascending order is defined over one spelling.

Two KATs, each proved load-bearing by mutation:
- a_provenance_blind_producer_can_say_so_instead_of_shipping_a_short_page
- a_malformed_bond_cursor_is_refused_rather_than_restarting_the_walk

Co-Authored-By: Claude <noreply@anthropic.com>
@MichaelTaylor3d

Copy link
Copy Markdown
Contributor Author

IN PROGRESS — not the verdict. Scoped re-gate of 8101744..bcbf9da, head bcbf9da.

Confirmed by reading so far:

  • §4.2g still MUST (SPEC.md:1116) — "An implementation MUST enumerate the SERVED set, and one that cannot MUST say so rather than report the state as satisfied." Not weakened to SHOULD; the delta only appends the spelling of "say so".
  • No per-row unknown added. MirrorBondState gained no variant; provenance_unknown is bound at the call level only (results.rs:2377, MirrorBondStatesUnknownReason).
  • Bound normatively in all four claimed places: SPEC.md:1117-1124 (§4.2g) and SPEC.md:1147-1157 (levels paragraph); traits.rs:574-585; method.rs:594; README.md:158.
  • Additive, not breaking. git show origin/main:src/results.rs | grep -c MirrorBondStatesUnknownReason0, and the same for MirrorBondKey. Both types are new in this PR, so the fourth variant breaks no released consumer.
  • Cursor validation reuses the existing helper, not a copy: normalize_bond_key (params.rs:1760) calls normalize_coin_id (params.rs:767) on each half independently. That helper strips an optional 0x, requires len 64 and accepts only 0-9a-f — so uppercase is refused, matching WalletCoinsParams exactly. No second convention.

Still running: baseline suite + the two mutations.

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor Author

PASS — scoped re-gate of 8101744..bcbf9da

Head read: bcbf9da46b92353aacf38b3144eef8b63f655f85 (re-resolved from gh pr view 38 --json headRefOid; it did not move during the review). Scope: the delta only. The rest of the PR was cleared last round and this delta does not touch it — the 7 changed files are README.md, SPEC.md, src/{method,params,results,traits}.rs and src/kats.rs.

Finding 1 — provenance_unknown: correct, and additive

  1. §4.2g still says MUST. SPEC.md:1116 is unchanged: "An implementation MUST enumerate the SERVED set, and one that cannot MUST say so rather than report the state as satisfied." The delta only appends the spelling of "say so" (SPEC.md:1117-1124). Not softened to SHOULD.
  2. No per-row unknown. MirrorBondState gained no variant; the reason lives only on MirrorBondStatesResult::Unknown. The call-level-only design that keeps a truncated page distinguishable from a complete one is intact.
  3. Bound in all four claimed placesSPEC.md:1117 + SPEC.md:1126-1130, traits.rs:574-585, method.rs:594, README.md:158. Swept for the superseded three-reason wording: grep -rn "in_flight_unknown\|InFlightUnknown" returns four hits, all already carrying the fourth reason or being the variant's own definition. No stale list survives.
  4. Additive, not breaking. git show origin/main:src/results.rs | grep -c MirrorBondStatesUnknownReason0, same for MirrorBondKey. Both types are new in this PR, so a fourth variant cannot break a released consumer. This is genuinely additive rather than a breaking change wearing additive clothes.

The choice not to rebind served_set_unknown is right for the reason the lane gave, and the KAT pins it: assert_ne! on the two wire tokens (kats.rs:5537) is what stops a later "simplification" from collapsing them and sending an operator at the enumerate subsystem for a provenance defect.

Finding 2 — cursor validation: reuses the sibling rule, no second convention

normalize_bond_key (params.rs:1760) calls the existing normalize_coin_id (params.rs:767) on each half independently — not a copy. That helper strips an optional 0x, requires exactly 64 chars and accepts only 0-9a-f, so uppercase is refused and 0x is tolerated-and-normalized, byte-identical to WalletCoinsParams. That resolves the discrepancy in the dispatch brief in favour of the review thread and the sibling convention, which is the correct call — a second convention here would be the rival-implementation defect.

Enforced on the way in via Deserialize (params.rs:1770), refused as -32602, never coerced to None. Canonical form pinned on both MirrorBondKey fields (results.rs:2323-2333), in §4.2g's paging paragraph (SPEC.md:1147-1154) and on the trait ordering bullet (traits.rs:588-590) — which is what makes the Ord derive over the raw Strings agree with the wire contract.

Mutations — both KATs verified non-vacuous, run in my own worktree

Baseline at bcbf9da: test result: ok. 185 passed; 0 failed, both new KATs present and green.

mutation result
as_wire: ProvenanceUnknown => "served_set_unknown" FAILEDa_provenance_blind_producer_can_say_so_instead_of_shipping_a_short_page (kats.rs:5551) and the_bond_surface_wire_tokens_are_unique (kats.rs:5303). 183 passed / 2 failed, exactly as claimed.
validated: self.after.and_then(|k| normalize_bond_key(k).ok()) — the silent restart FAILED at kats.rs:5643 with a malformed cursor must be refused: MirrorBondStatesParams { after: None, ... }. The panic names the defect itself — the cursor became None, i.e. start-of-set.

The provenance fixture is built against the nearest wrong implementation, not a spelling variant: the truthful answer at kats.rs:5519-5530 contains a withheld row, and the forbidden short page decodes as Known { complete: true, entries.len() == 1 } and is asserted != the sanctioned refusal. Two observably different answers about the same set. That is the property, not an outcome.

Both mutations were applied in C:\tmp\worktrees\gate-dnci-38 (my own worktree, cut at bcbf9da), restored from a file copy, and git status --porcelain was empty after each restore. No shared checkout was mutated.

Non-gating (1) — posted inline and resolved by me

kats.rs:5654assert!(decoded.map(|p| p.after).unwrap_or(None).is_none(), ...) is unconditionally true given the assert!(decoded.is_err()) two lines above: Err(_).map(..).unwrap_or(None) is None for every input. The property it names (never coerced to None) is genuinely proven by the validated() arm above it, so nothing is untested — the assertion just cannot fail. Cosmetic; not worth a round-trip.

Verdict: PASS. Nothing gating. Merge and publish 0.26.0.

@MichaelTaylor3d MichaelTaylor3d left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-gating nit (I am resolving this myself; it does not block).

Comment thread src/kats.rs
@MichaelTaylor3d
MichaelTaylor3d marked this pull request as ready for review August 30, 2026 04:06
@MichaelTaylor3d
MichaelTaylor3d merged commit b3804b5 into main Aug 30, 2026
9 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the feat/412-mirror-bond-state branch August 30, 2026 04:06
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