Help us shape the future of Go! Take the 2026 Go Developer Survey

amount

package
v0.25.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

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

View Source
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

View Source
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.

View Source
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")
)
View Source
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.

View Source
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.

View Source
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

func Add(a, b int64) (int64, error)

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

func Assess(raw []byte, unit Unit, exp int) (int64, error)

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

func Exponent(code string) (int, error)

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

func Exponents() map[string]int

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

func Parse(text string, unit Unit, exp int) (int64, error)

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

func Resolve(code string, override *int) (int, error)

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 Kind

type Kind uint8

Kind is the form of a rule.

const (
	// PerCall: "> N CUR", a limit on one call's amount.
	PerCall Kind = iota + 1
	// Sum: "sum N CUR per window", a limit on the total over a window.
	Sum
)

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

func ParseRule(line string) (Rule, error)

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

func ParseRules(lines ...string) ([]Rule, error)

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.

type Unit

type Unit uint8

Unit says how a tool's argument is written.

const (
	// Minor: an integer count of the currency's minor units (cents).
	Minor Unit = iota + 1
	// Major: a decimal in major units (dollars), with at most the currency's exponent of
	// fractional digits.
	Major
)

func (Unit) String

func (u Unit) String() string

String is the unit as a rule writes it: minor or major.

type Window

type Window uint8

Window is the period a cumulative rule sums over. Hour and Day are tumbling windows in UTC (decision 11).

const (
	// Run sums over the run.
	Run Window = iota + 1
	// Hour sums over a UTC clock hour.
	Hour
	// Day sums over a UTC calendar day.
	Day
)

func (Window) String

func (w Window) String() string

String is the window's name in a rule: run, hour or day.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL