Repository navigation
Description
id(currency): sign_authorisation never calls validate — a signed authorisation naming another issuer or with rate_den = 0 is dead on arrival, and signing succeeds silently
Summary
pkgs/id/src/currency.rs has exactly one validity contract for a RateAuthorisation body — RateAuthorisation::validate (issuer must equal the event author; rate_den must be non-zero) — and the fold enforces it (fold_authorisation filters on body.validate(author).is_ok(), currency.rs:299 at origin/main a2b4345e80). But the signing entry point, sign_authorisation (currency.rs:328), never calls it: it serializes the body and hands it straight to chain::sign. A signer can therefore produce a properly signed, chain-valid event whose body is invalid by the module's own two hard rules, and nothing anywhere will ever fold it as Live:
- Issuer mismatch: body names issuer B, signed by A.
fold_authorisation(.., expected_issuer = B)ignores the event (its author is A, not B);fold_authorisation(.., expected_issuer = A)drops the body at thevalidatefilter (body names B). The signed object is unreachable in every fold. rate_den = 0:validaterejects it asZeroRateDenominator, so the fold drops it — yet it sits on the issuer's chain as a signed version, and (per the fold's version-selection order: filter first, then max-by-version) it does not shadow a good lower version, but it does permanently occupy its sequence slot and reads as a real signed authorisation to any consumer that decodes without folding.
This is the sign-side half of the contract the artifact module already closed in #5623 ("validate known-kind bodies in SignedArtifact::sign / sign_after"): a signed object should be validated as strictly at creation as it is at consumption, so the mistake surfaces at sign time — where the caller can fix it — instead of as a silent no-op in every later fold.
Evidence (verified this run, origin/main a2b4345e80)
git grep -n "validate" pkgs/id/src/currency.rsreturns exactly three sites: the definition (currency.rs:153), the fold filter (currency.rs:299), and one test (currency.rs:513).sign_authorisationandsign_revokecontain no validation call.- Probe (laptop, worktree of origin/main at
a2b4345e80, integration testpkgs/id/tests/probe_currency_sign.rsin worktree~/git/fleet/probe-currency-sign-wt, run withcargo test --test probe_currency_sign -- --nocapture):sign(issuer-mismatch body) -> Ok,sign(rate_den=0 body) -> Ok,sign(valid body) -> Ok(control), and the mismatched body folds toNonefor both the signing issuer and the named issuer — dead on arrival, exactly as the code reading predicts. (The probe test asserts the desired refusal, so it fails on current main at the issuer-mismatch assertion; the printed lines above are the observed behavior.) - Existing test coverage signs only a valid body (
signed_events_round_trip_through_the_fold), so the gap produces no red signal today.
Why it matters
sign_authorisation is the only sanctioned way to create these objects (Phase 1 of #2290; the conversion engine #5536 builds on it). An LP tool that mis-sets issuer_id (e.g. copies a counterparty's body as a template) or a rate with a zero denominator gets a successful signature, a chain event, and a reference hash to hand out — and the authorisation simply never exists for any consumer. That is the worst failure shape for a signed-object system: creation reports success, consumption silently ignores. Failing at sign time with the existing ValidateError vocabulary costs one call; the artifact precedent (#5623) shows the fleet's chosen shape for exactly this fix.
Acceptance criteria
-
sign_authorisationrefuses a body thatvalidatewould reject for the signing key's issuer (issuer mismatch,rate_den = 0), surfacing the refusal throughSignError(a new variant wrappingValidateErroris fine); no event is produced for an invalid body. - Tests: signing an issuer-mismatched body and a
rate_den = 0body both error; the existing valid-body round-trip test still passes unchanged. - The fold's behavior is unchanged (invalid bodies from other/legacy signers are still ignored, not folded).
- Out of scope: extending
validate's rule set — that is id(currency): RateAuthorisation::validate accepts internally inconsistent bodies — min>max, empty/inverted window, fee_bps>10000, empty/same ledgers #4071, open separately. This issue only enforces the two rules that already exist, at sign time.
Notes
- Related: id(currency): RateAuthorisation::validate accepts internally inconsistent bodies — min>max, empty/inverted window, fee_bps>10000, empty/same ledgers #4071 (validate accepts internally inconsistent bodies — the rule-set extension; complementary, not duplicate: it never mentions the sign path, and its fix would not make
sign_authorisationcallvalidate), id(artifact): validate known-kind bodies in generic signing helpers #5623 (the artifact-module precedent for sign-side validation), id: design: RateAuthorisation as a signed ID-graph object — schema, signing, revocation #2290 / id: implement RateAuthorisation as a signed ID-graph object (design #2290) #5521 (the RateAuthorisation design/implementation this module serves). - Found by Lane B (new-issue-creator-code-b), weighting away from Lane A's recent fleet-config filings and Lane B's own recent dead-test filings.
Activity
developing-today commentedon Nov 6, 2026
CODE-1923
dezren39 commentedon Nov 6, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 6, 2026
Agent note — task 10861 handed off (lane code-n)
opencodeperfleet queue lane— handing off to the laptop opencode claimant; this lane does not work the task directly.held,user/owned,do-not-merge,do-not-auto-edit).57 remaining items
dezren39 commentedon Nov 6, 2026
Handed off to opencode claimant — task 10861, issue #6422.
Lane code-r claimed task 10861; §1b lane assignment returned
opencode. Re-verified fresh viagh(2026-11-06): issue #6422 is OPEN with no hard-stop labels (held,user/owned,do-not-merge,do-not-auto-editabsent) and no open PR found.Checkpointed
handed_offwith execution_lane=opencode; parking task 10861 for 1200s pending the claimant launching the bound opencode session. Attempts are now 5/8. If the claimant has not launched by lease expiry, task 10861 returns to the queue.developing-today commentedon Nov 6, 2026
🤖 Agent note · picked up
developing-today commentedon Nov 6, 2026
🤖 Agent note · handed off