Skip to content

id(currency): RateAuthorisation::validate accepts internally inconsistent bodies — min>max, empty/inverted window, fee_bps>10000, empty/same ledgers #4071

Open

Description

@dezren39

Summary

RateAuthorisation::validate in the new pkgs/id/src/currency.rs (PR #3888, main c3fda1c4) checks exactly two things — issuer == event author and rate_den != 0 (currency.rs:170-180). Every other internally inconsistent body folds as Live: the fold's only validity gate is this function (currency.rs:313-320), so a signed authorisation that can never legitimately convert — or that misprices every conversion — is indistinguishable from a good one for every consumer of the fold.

Evidence (verified this run, laptop probe)

Probe: ~/git/fleet/probe-2026-11-01-laneB-currency/ on the laptop — verbatim validate from currency.rs (reference copy sha256 8f68982526219dff920586495a076d2c1a33f87fe3b6716a74e561ea821b9493 @ c3fda1c4). All of these return Ok(()):

validate(min>max) -> Ok(())                                   min_amount 10000, max_amount 1
validate(expires_at==not_before (empty window)) -> Ok(())     2000 / 2000
validate(expires_at<not_before) -> Ok(())                     2000 / 3000
validate(fee_bps 99999 (>100%)) -> Ok(())                     fee is 999.99%
validate(empty ledgers) -> Ok(())                             source_ledger "" and dest_ledger ""
validate(source==dest ledger) -> Ok(())                       ABC -> ABC
validate(rate_num 0 (zero rate)) -> Ok(())                    every conversion yields 0
validate(total_cap 0) -> Ok(())                               cap permits nothing
validate(empty allowlist (allows nobody)) -> Ok(())           AuthorisedCallers::Allowlist([])
test validate_accepts_internally_inconsistent_bodies ... ok

(Sanity: the two documented checks do fire — rate_den = 0 returns ZeroRateDenominator.)

Why it matters

validate is the library-level contract the fold, and later the Phase 2 gateway checks (#3801: window, min/max, remaining volume at submission), rely on. A body with min_amount > max_amount, an empty/inverted validity window, or fee_bps > 10000 can never be caught downstream as "invalid" — the gateway's live-state checks would evaluate a nonsense window and nonsense bounds as if they were policy, and different consumers can disagree about shapes like a zero rate or a same-ledger "conversion". The field docs in this same file state the invariants in prose ("Rate denominator; non-zero in a valid authorisation", "Mandatory expiry", "Optional fee in basis points") — rate_den is the only one enforced.

Acceptance criteria

  • validate rejects, with named ValidateError variants: min_amount > max_amount; expires_at <= not_before; fee_bps > Some(10000); empty source_ledger or dest_ledger; source_ledger == dest_ledger.
  • Shapes that are arguably legitimate-but-dead (zero rate_num, zero total_cap, empty allowlist) are either rejected with named variants or explicitly documented as valid in the field docs — one stated rule, tested either way.
  • Tests cover each rejected shape; existing currency tests still pass; the fold ignores the newly invalid bodies (covered by the existing fold-filter test pattern).

Distinct from: #3801 (gateway submission-time checks against accounts/tiers — it assumes a valid folded object), #4069 (duplicate-version fold tie-break, same file, different defect), #4063 (app_manifest parser validation, different module).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 1, 2026
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  4. 82 remaining items

  5. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    agent: repo-worker-code
    state: parked
    queue: repo-health-code-pilot
    task: 8834
    lane: aea12c82-c5af-4e5a-9b66-f8e360f87177
    at: 2026-11-04T21:47:22Z

    Parked task 8834 for 30 minutes after fresh revalidation. Issue #4071 is OPEN and assigned to dezren39; PR #5181 is OPEN, fixes #4071, and is authored by dezren39 on the task branch queue/8834-4071-rateauthorisation-validation. This is Drew-owned work: no PR, branch, worktree, files, or tests were touched. Resume only after PR #5181 merges/closes or Drew explicitly releases ownership; then revalidate fresh. Labels now reflect parked state: queue/parked + status/parked + work/assigned.

  6. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
  7. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 4, 2026
  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  10. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    work/assignedQueue worker lifecycle state
    on Nov 5, 2026
  11. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · handed off

    • Agent: fleet monitor
    • Session / ID: task 8834 (repo-fleet-monitor run 2026-11-05T15:45:48Z)
    • Found via: monitor reconciliation (Duty 1 claim/label divergence repair)
    • Task: Claim evaporated: task 8834 is queued and unclaimed in repo-health-code-pilot, but this issue still carried claimed labels. Labels reset to the truth (queue/queued + status/needs-triage). Checkpoint stage at evaporation: handed_off.
    • Expectation: The next lane claim picks task 8834 up from the queued state and posts a fresh pickup note.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)queue/parkedQueue task is parked (waiting on CI, time, or an event)queue/queuedHas a queued task in the Supabase agent queuesource/creatorFiled by the new-issue-creator jobstatus/needs-triageNot yet triaged; needs a first lookstatus/parkedDeliberately paused with a recorded resume conditiontask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions