@@ -4,6 +4,59 @@ All notable changes to this project are documented here.
44This project adheres to [ Semantic Versioning] ( https://semver.org ) and
55[ Conventional Commits] ( https://www.conventionalcommits.org ) .
66
7+ ## [ 0.2.0] - 2026-07-27
8+
9+ ### BREAKING CHANGES
10+
11+ The custody gate now returns a RULING and mints an approval that OWNS the coin spends it
12+ authorized, and that approval is the only thing the signer accepts. A ` 0.x ` MINOR is
13+ semver-breaking, so every consumer must move: ` ^0.1 ` cannot resolve ` 0.2.0 ` .
14+
15+ - ` PolicyAuthorizer::authorize_op ` now takes ` &[CoinSpend] ` (not ` &SpendSummary ` ) and returns
16+ ` Result<SpendRuling> ` (not ` Result<()> ` ). It derives the summary itself, so there is no
17+ caller-supplied description for it to disagree with.
18+ - ** ` MoneySigner::sign_coin_spends ` is REMOVED.** ` sign_approved(SpendApproval) -> SpendBundle `
19+ is the only signing entry point; no function anywhere turns ` &[CoinSpend] ` into a signature.
20+ - ** The ` SpendAuthorizer ` trait is REMOVED.** A custody gate must not be an interface whose
21+ simplest implementation approves everything — the crate's only consumer had shipped exactly
22+ that (a default authorizer whose entire body was ` Ok(()) ` ), so every bound this crate
23+ advertised was absent from the running application.
24+ - ** ` AccountError::RequireAuth ` is REMOVED.** "Not yet — ask the human" is now
25+ ` SpendRuling::RequiresConfirmation ` , an ` Ok ` value. As an error it forced consumers to collapse
26+ it into a refusal, which made the confirm ceremony unreachable for exactly the ` Confirm ` and
27+ ` Vault ` tiers that exist to require it.
28+ - ` LocalMoneySigner::sign_unsigned ` is now ` pub(crate) ` : it accepted a caller-supplied summary,
29+ which was the same defect one layer over.
30+
31+ Migration: replace ` authorize(&summary) ` / ` authorize_op(&summary, class) ` with
32+ ` authorize_op(&coin_spends, class) ` ; match the ruling — ` Approved(approval) ` → ` sign_approved ` ,
33+ ` RequiresConfirmation(pending) ` → render ` pending.summary() ` , then
34+ ` pending.confirmed(decision) ` . Delete any fake authorizer and drive the real gate with a test
35+ policy plus the public ` FixedClock ` .
36+
37+ ### Features
38+ - ** wallet:** The gate mints an owned ` SpendApproval ` ; the signer accepts nothing else
39+ - ** wallet:** ` SpendRuling ` carries the escalatable third outcome, so a ` Confirm ` /` Vault ` spend
40+ reaches the confirm ceremony
41+ - ** wallet:** ` SpendOpClass::Undeclared ` escalates to the human rather than dead-ending, so a
42+ request from outside the process is confirmable rather than unspendable
43+ - ** wallet:** ` AutoSendPolicy::configured_limits ` — the ` Option ` form of the op-class lookup
44+
45+ ### Fixes
46+ - ** wallet:** Refuse a spend whose input coin amounts do not sum in a ` u64 ` . ` dig-wallet-backend `
47+ 0.16 accumulates them with an unchecked ` += ` (` client/verify.rs:153 ` ), and those amounts arrive
48+ in a caller-supplied skeleton, so an unsummable total panicked in a debug build and wrapped in a
49+ release build — after which the wrapped figure was what value conservation was checked against.
50+ - ** wallet:** Charge the rolling period cap the spend's real value, and merge the cap's two
51+ overflow checks into the one that is reachable.
52+
53+ ### Tests
54+ - The #1698 exploit is now UNWRITABLE rather than merely rejected: compile-fail proofs for the
55+ exploit itself, for minting an approval outside the crate, and for using either token twice,
56+ plus trait-absence assertions (` Clone ` /` Serialize ` /` DeserializeOwned ` /` Debug ` ) and structural
57+ invariants (one minter, one field-assembly site, one ` derive_summary ` call site, no signing
58+ function over ` &[CoinSpend] ` ).
59+
760## [ 0.1.2] - 2026-07-27
861
962### Features
0 commit comments