Repository navigation
$ money-float: rates carried as exact nano-units end to end, converted once at the config intake; a non-whole-nano rate is refused, never rounded (#81, OWNER rule 21) - #870
Open
MattJackson wants to merge 5 commits into
Conversation
…once at the config intake Part 2 #81 (BUSBAR-1.6.0.md l.2282): no f32/f64 on any money path. The four reserved tier rates were carried as binary floats from the config struct through RawTierRates (contract), the rate-apply seam (RawRates), the root relay (card_from_config) and TierRates (ledger) into the card build, with a second float conversion in RateNanos::from_raw (cost.rs:117) outside the intake table. Now: - RateEntryCfg::raw_tier_rates is THE intake: it converts each configured rate ONCE, through the ledger's representable_nano_rate, into Option<u64> nano-units. - RawTierRates and TierRates hold Option<u64> nano-units (None = a configured rate the card cannot hold, item 22: placed unpriced, a hit refuses, #42). Default is every tier at Some(0), the free tier 1.5.5's #[serde(default)] gave. - RateCard::from_config / place_rate take integers; from_micro_rates converts through representable_nano_rate and nothing else. - RateNanos::from_raw copies integers; it no longer converts. - RawTierRates::blended_per_mtok is gone; rate_entry_per_mtok (the routing order, not money) reads the config figures as 1.5.5 did. - nano_rate does the x1000 and #44's half-away-from-zero rounding on the configured decimal's own digits (shortest round-trip text), never on a binary product, and its ceiling is a checked integer multiply. 1.5.5 parity: every rate with up to three decimals converts to 1.5.5's integer; the busbar-oracle money fixtures configure only whole rates (0, 1, 1e5, 2e5, 1e6, 2e6, 1e7, 2e7). The one signed divergence (#81, #44) is an exact decimal half nano-unit whose binary product fell below the half (0.5005 -> 500 in 1.5.5, 501 now), about 1% of x.xxx5 rates. REDs: nano_rate(0.5005) == 501, nano_rate(0.1 + 0.2) == 300; a 0.1+0.2 rate prices exactly through the live intake; a 2^53+1 token count prices with no drift; every money-fixture rate prices at its 1.5.5 figure; the 1.5.5 projection equivalence over 10k generated rates allows only the signed half-nano class; the ceiling test pins the exact integer boundary.
…ts is refused, never rounded (OWNER rule 21) OWNER rule 21 (2026-10-14): "money is bank auditor grade. its either right or its wrong. make it right." / "money - ledger * ratecard". The card holds integer nano-units, so a configured *_utok rate either IS a whole number of them (at most three decimal places of micro-units) or the card cannot hold it. nano_rate no longer rounds: a rate with any digit past the third decimal converts to 0, and representable_nano_rate answers None. Boot validation already refuses a None naming the model, class and value. Every rate the card can hold converts to exactly 1.5.5's (utok * 1000.0).round(). A rate 1.5.5 rounded (0.0015, 0.5005) is refused at config time. REDs flipped to refusals: nano_rate(0.5005), (0.5015), (0.0015), (0.0014), (0.0005) and the double 0.1 + 0.2 are refused (0 / None); RateNanos::from_cfg refuses 0.0015 and 0.0014; the 1.5.5 projection equivalence has two arms only (whole-nano rates equal 1.5.5's integer, every other rate is None at the intake). The earlier commit's "signed half-nano divergence" is withdrawn: nothing is rounded. docs/configuration.md states the rule for operators.
…ses boot (0.0005, 0.0015, 0.5005); 0.001 is the boundary that boots; the refusal message says rates are never rounded (OWNER rule 21)
This was referenced Oct 14, 2026
Open
…tes through the intake conversion (representable_nano_rate); no float crosses the seam
MattJackson
marked this pull request as ready for review
October 14, 2026 20:02
MattJackson
marked this pull request as draft
October 14, 2026 20:20
MattJackson
marked this pull request as ready for review
October 14, 2026 20:22
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Rates on the money path are carried as exact integer nano-units end to end. Each configured rate is converted once, at the config intake. A rate that is not a whole number of nano-units is REFUSED at config time and never rounded.
RateEntryCfg::raw_tier_rates(crates/busbar-kernel/src/config/sections.rs) converts each configured_utokrate once, through the ledger'srepresentable_nano_rate. Nothing after it carries a float.RawTierRates(contract) andTierRates(ledger) holdOption<u64>nano-units per token.Nonemeans a rate the card cannot hold, and it refuses (Per-user provider credentials: using a seat subscription from your own apps #42).Defaultis every tier atSome(0). That is the free tier 1.5.5's#[serde(default)]gave.RateCard::from_configandplace_ratetake integers.from_micro_ratesconverts only throughrepresentable_nano_rate.RateNanos::from_rawcopies integers; it is no longer a second conversion outside the intake table.nano_rate. It multiplies by 1000 exactly, on the configured decimal's own digits (the shortest round-trip text of the double, never a binary product). A rate with any digit past the third decimal of micro-units converts to 0 /None. Its ceiling is a checked integer multiply.Nonerate and names the model, class and value. The message now says that a non-zero rate is a whole number of nano-units (at most three decimals) and is never rounded.RawTierRates::blended_per_mtokis removed.rate_entry_per_mtokis a routing order (cheapestand the hookcost_per_mtok), not money. It reads the config figures exactly as 1.5.5 did (crates/busbar/src/config/mod.rs:2137@v1.5.5).docs/configuration.mdstates the rule for operators.Why
BUSBAR-1.6.0.mdl.2282): no f32/f64 on any money path.RawTierRates,TierRatesandRawRates, acrosscost.rs,rate_apply.rs,billing.rs,kernel-ledger cost/mod.rsandroot/kernel.rs;Against 1.5.5
(utok*1000.0).round(),crates/busbar/src/cost.rs:99@v1.5.5). The busbar-oracle money fixtures configure only whole rates (0, 1, 1e5, 2e5, 1e6, 2e6, 1e7, 2e7), so every golden cell is unchanged.0.0015,0.5005) is now refused at boot /--validate. It is never rounded.REDs ($)
nano_rate_refuses_a_rate_that_is_not_a_whole_number_of_nano_units:0.0015,0.0014,0.0005,0.5005,0.5015,1.0001and the double0.1 + 0.2are refused (0 /None). Whole-nano rates convert exactly.rate_nanos_from_cfg_refuses_a_rate_finer_than_one_nano_unit: the intake refuses0.0015and0.0014.test_validate_rate_card_refuses_a_rate_the_card_cannot_hold:0.0005,0.0015and0.5005refuse boot and name the model, class and value.0.001boots.a_fractional_cent_rate_prices_exactly_through_the_live_intake: rates of 0.1 and 0.2 price one token of each at exactly 300 nano-units. The drifted double0.30000000000000004is refused.a_large_count_times_a_rate_has_no_float_drift: 2^53+1 tokens × 300 nano-units, exact.the_intake_prices_every_money_fixture_rate_at_its_1_5_5_figure: the oracle fixture rates plus the in-tree card fixtures.from_raw_is_byte_identical_to_the_unclamped_projection_for_every_in_range_rate: over 10k generated rates there are two arms only. A whole-nano rate equals 1.5.5's integer; every other rate isNoneat the intake.18_446_744_073_709_548). It also checks that a refused rate lands UNPRICED on the card, never as a priced zero.Ledger rows touched
None. The
no-float-moneygate's MONEY_INTAKE rows (from_micro_rates,from_config,place_rate,TierRates,card_from_config) still name existing items. Those items no longer carry a decimal, so the gate is not weakened. The gate file is held by #853, #787 and #503.Shared files (queue rule 20, disjoint hunks, clean merge with predev f70f567)
config_validate/{mod.rs,tests/tests.rs}, shared with row 74 F25: a base URL's or token endpoint's userinfo rides as Authorization: Basic where 1.5.5 sent it; no plane receives it #913, row 74 F25: a lane base URL's userinfo is stripped, never dialled, sent or printed, as 1.5.5 did #876 and row 166 PR1: secret resolve takes a config-time proof (ConfigScope) and the config-scope gate #863.root/tests/kernel.rs, shared with row 113: admin DR verbs proven to land on the book's configured store (test-only; wiring landed in #556) #817.