Documentation
¶
Overview ¶
Package amount reads money out of tool arguments for the amount caps of the untrusted-content and amount-caps note (docs/design/untrusted-content-and-amount-caps.md, sections 6.2 and 6.3): the text of an amount to an int64 count of minor units, the ISO 4217 exponent of a currency, the strict reading of a tool's arguments that precedes it, and the one-line grammar of a cap rule. It is pure: no engine, no journal, no clock, no I/O.
Nothing here uses a floating-point type. An amount is the decimal text of a JSON number or string, scaled by the currency's exponent with checked int64 arithmetic; an overflow is an error, never a wrap. Anything the package cannot read with certainty is "not assessable" (ErrNotAssessable), and the engine gives a not-assessable amount the rule's own action, so a value the package refuses never silently passes a cap.
Package location: internal/amount, in the core module, beside internal/strictjson, which it builds on. The agent package imports it without a cycle because amount imports only the standard library and internal/strictjson (and strictjson imports nothing of agent's). It is internal/ rather than agent/internal/ so that the cmd modules (a later `bide audit caps`, `bide doctor`) can parse a rule too: Go's internal rule is by import path, and every module of this repository lives under github.com/bide-ai/bide.
Index ¶
- Constants
- Variables
- func Add(a, b int64) (int64, error)
- func Assess(raw []byte, unit Unit, exp int) (int64, error)
- func Exponent(code string) (int, error)
- func Exponents() map[string]int
- func Lookup(args []byte, path []string) (json.RawMessage, error)
- func Parse(text string, unit Unit, exp int) (int64, error)
- func Resolve(code string, override *int) (int, error)
- type Action
- type ActionKind
- type Key
- type Kind
- type Rule
- type Unit
- type Window
Constants ¶
const ( // MaxExponent is the largest exponent accepted, from the table or from an exponent= // override: 10^18 is the largest power of ten an int64 holds. MaxExponent = 18 )
Variables ¶
var ( // ErrSyntax: the text is not a plain decimal: optional minus, digits with no leading zero, and // at most one point with digits after it. An exponent, a plus sign, hex, NaN, a thousands // separator, whitespace and any non-ASCII digit are all refused. ErrSyntax = notAssessable("amount is not a plain decimal (optional minus, digits, one point; no exponent, plus sign, hex or leading zero)") // ErrFraction: more fraction digits than the currency's exponent (any, for minor units). ErrFraction = notAssessable("amount has more fraction digits than the currency's exponent") // ErrOverflow: the amount, scaled to minor units, or a sum, does not fit an int64. ErrOverflow = notAssessable("amount overflows int64") // ErrNegative: the amount is negative (or written with a minus sign). ErrNegative = notAssessable("amount is negative") // ErrType: the value is not a JSON number or string. ErrType = notAssessable("amount is not a JSON number or string") // ErrExponent: an exponent outside 0 to 18, or one that contradicts the ISO 4217 table. ErrExponent = notAssessable("currency exponent must be 0 to 18 and agree with the ISO 4217 table") )
The specific reasons. Each wraps ErrNotAssessable and none echoes the input, which a model wrote.
var ( // ErrArgs: the arguments are not strict. A duplicate member name, or two names that differ // only in case, at any depth: a tool that decodes its own arguments (a custom Tool, an MCP // server) may take the last duplicate, or match a name case-insensitively, and so read a value // other than the one the cap read. Also invalid UTF-8, an escaped lone surrogate, trailing // data, arguments that are not an object, and nesting deeper than a tool's arguments go. ErrArgs = notAssessable("arguments are not strict JSON") // ErrAbsent: the member is not there. ErrAbsent = notAssessable("amount argument is absent") )
var ErrNotAssessable = errors.New("amount not assessable")
ErrNotAssessable is wrapped by every error that means "this amount cannot be read with certainty": a text that is not a plain decimal, too many fraction digits, an overflow, a negative value, a value that is not a number or string, arguments that are not strict, or an argument that is absent.
var ErrRule = errors.New("cap rule")
ErrRule is wrapped by every error ParseRule and ParseRules return. The engine wraps it in agent.ErrConfig at registration.
var ErrUnknownCurrency = errors.New("unknown currency code")
ErrUnknownCurrency: the code is not in the exponent table (and no exponent= was stated for it), or is not three upper-case ASCII letters. It is a configuration error, found when a rule is registered; it is not ErrNotAssessable.
Functions ¶
func Add ¶
Add is a + b in int64 with an overflow check. A sum of reservations that does not fit is ErrOverflow, a refusal, never a wrap.
func Assess ¶
Assess reads one JSON value, a number or a string as it stands in the arguments (Lookup returns it verbatim), to a non-negative count of minor units. The number is read from its text, never converted through a float, so 12.50 is "12.50". A negative amount (including -0) is ErrNegative, an exponent form, a value that is neither a number nor a string, and anything else Parse refuses, is an error: the caller gives the rule's action to each.
func Exponent ¶
Exponent returns the ISO 4217 exponent (the number of minor-unit digits) of a currency code. A code is exactly three upper-case ASCII letters; "usd" is not "USD". An unknown code, and a code the published list gives no minor unit (XAU, XXX and the like), is ErrUnknownCurrency.
func Exponents ¶ added in v0.24.0
Exponents is a copy of the ISO 4217 exponent table: every code with its number of minor-unit digits. The approve page carries the same table to write an amount as a decimal, and a test holds the two equal.
func Lookup ¶
func Lookup(args []byte, path []string) (json.RawMessage, error)
Lookup returns the JSON value at path (object members, outermost first) of a tool call's arguments, exactly as written, after checking that the arguments are strict. The check is of the whole document, not only the path: a duplicate or case-variant name anywhere makes the call not assessable, because a tool decoding the arguments its own way may differ about any of them. Empty arguments are the empty object, as for a Func tool. A null at the path is returned as null (Assess then refuses it); an empty path, an empty member name, a missing member, or a step through a value that is not an object is ErrAbsent. A name that differs from the path's only in case is not the member (the lookup is exact); it is absent.
The strict options are those internal/strictjson applies to Func arguments in agent/tool.go (duplicate names, one spelling, UTF-8, trailing data), plus the case-variant check, which strictjson makes only against a struct's fields and which needs no struct here.
func Parse ¶
Parse reads the decimal text of an amount to an int64 count of minor units. For Major, a fraction of at most exp digits is allowed and the result is scaled by 10^exp; for Minor the text is an integer and exp is not used. The result is signed: a minus sign is parsed, not refused, so the caller (Assess does) decides what a negative amount means.
The accepted text is exactly: an optional '-', then "0" or a digit 1 to 9 followed by digits, then optionally '.' and one or more digits. Parse never uses a float and never accepts a literal that strconv.ParseInt over the scaled digits would not (the note's C1).
func Resolve ¶
Resolve is the exponent a rule uses for code: the table's, or the one stated with exponent= (override non-nil) for a code the table lacks. An override that contradicts the table is ErrExponent, as is one outside 0 to MaxExponent, so a rule cannot quietly change how many minor units a dollar has. An override for a code that is not three upper-case letters is ErrUnknownCurrency.
Types ¶
type Action ¶
type Action struct {
Kind ActionKind
GateName string
}
Action is the rule's action; GateName names the gate a host registered, empty for the escalation gate.
type ActionKind ¶
type ActionKind uint8
ActionKind is what a rule does to a call over its limit.
const ( // Gate holds the call for approval. Gate ActionKind = iota + 1 // Refuse does not run the call and does not ask. Refuse )
type Key ¶
type Key struct {
Principal bool
Path []string
Lower bool // strings.ToLower the member's text
Trim bool // strings.TrimSpace the member's text
}
Key is what a cumulative rule is keyed by: the run's principal, or a member of the arguments, normalised as stated. The engine canonicalises further (a digest of the normalised text).
type Rule ¶
type Rule struct {
Name string
Tools []string // the set of tools sharing this rule's ledger
Arg []string // the member path of the amount in each tool's arguments
Unit Unit
CurrencyArg []string // the member holding the call's currency; nil for a fixed currency
Currency string // the currency the limit is in
Exponent int // the currency's exponent, from the table or exponent=
Limit int64 // in minor units
Kind Kind
Window Window // Sum only
Key *Key // Sum only; nil for an unkeyed total
Action Action
}
Rule is one parsed cap line. Registration-time checks against a tool's input schema are the engine's; the parser checks only the line.
func ParseRule ¶
ParseRule parses one rule line. The error wraps ErrRule and names the column and what was expected; it does not echo the line.
func ParseRules ¶
ParseRules parses lines, one rule each, and refuses a name used twice. A name is the ledger's key (6.5), so a name differing from another only in case is refused too: it is almost surely a mistake, and the two would be different ledgers.