bal

package
v0.16.16 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package bal is the conservation primitive (@B): an append-only double-entry journal over bounded accounts, where conservation is an arithmetic identity — every transfer writes two signed entries (−a, +a) in one transaction, so the system total always equals the sum of its boundary accounts.

Identity (@B09a): the substrate-preferred two-identity split. The external account_id is a namespaced STRING at every boundary — no numeric width exists to truncate, so the /ts id-boundary bug class is structurally impossible here (@C04d the strong way). The internal account key is a dense uint32, engine-internal, fixed-width codec, never on any wire struct.

Numerics (@B04): amounts are int64 minor units through the account's scale. No float64 touches an amount anywhere, ever — ParseAmount is the only sanctioned string→amount crossing.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EncodeAccountKey

func EncodeAccountKey(k AccountKey) [4]byte

EncodeAccountKey writes the key with an explicit fixed-width codec (4 bytes, big-endian) — the sanctioned byte crossing.

func FormatAmount

func FormatAmount(v int64, scale uint8) string

FormatAmount renders minor units back to the canonical decimal string at the account's scale.

func ParseAmount

func ParseAmount(s string, scale uint8) (int64, error)

ParseAmount converts a decimal string to int64 minor units under the account's scale — the ONLY string→amount crossing. It refuses anything that is not an exact integer of minor units at that scale (XOLU-BAL004 territory), and never passes through float64.

func ValidateAccountID

func ValidateAccountID(id string) error

ValidateAccountID enforces the external-id shape: non-empty, ≤256, no whitespace; '/' and ':' and '.' are the namespace vocabulary.

Types

type AccountDef

type AccountDef struct {
	ID       string // namespaced external id, e.g. "warehouse:A/widget" or "1.1.9.10"
	Unit     string // "EUR", "widget", "gram"
	Scale    uint8  // decimal places of the minor unit
	Floor    int64  // minimum balance (default 0)
	Ceiling  *int64 // optional maximum balance
	Postable bool   // only leaf (imputable) accounts accept entries (@B03a)
}

AccountDef defines an account (@B03, @B03a).

func (AccountDef) Validate

func (d AccountDef) Validate() error

Validate checks a definition.

type AccountKey

type AccountKey uint32

AccountKey is the engine-internal dense identity: uint32, the wave-1 per-primitive width. It never appears in JSON.

const MaxAccountKey AccountKey = 0xFFFFFFFF

MaxAccountKey is the codec ceiling; it fits uint32 exactly.

func DecodeAccountKey

func DecodeAccountKey(b [4]byte) AccountKey

DecodeAccountKey reads the fixed-width codec back, losslessly across the full uint32 span.

type AmountScaleError

type AmountScaleError struct{ Detail string }

func (*AmountScaleError) Error

func (e *AmountScaleError) Error() string

type BoundsError

type BoundsError struct {
	AccountID string
	Side      string // "floor" or "ceiling"
}

func (*BoundsError) Error

func (e *BoundsError) Error() string

type ChainBreak

type ChainBreak struct {
	AccountKey int64
	EntryID    int64
	Detail     string
}

ChainBreak localises a violation of the per-account arithmetic chain.

type Entry added in v0.16.15

type Entry struct {
	EntryID         int64     `json:"entry_id"`
	TransferID      string    `json:"transfer_id"`
	AccountID       string    `json:"account_id"`
	Amount          int64     `json:"-"` // rendered as string by the handler (@B04)
	PreviousBalance int64     `json:"-"`
	CurrentBalance  int64     `json:"-"`
	Version         int64     `json:"version"`
	Memo            string    `json:"memo,omitempty"`
	At              time.Time `json:"at"`
}

Entry is one journal row on the API surface: external account id, signed amount, the chain triple, memo, instant. Internal keys never appear (@B09a).

type NotPostableError

type NotPostableError struct{ AccountID string }

func (*NotPostableError) Error

func (e *NotPostableError) Error() string

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is bal's SQL plane (@B05): the append-only journal and the balances table, maintained in the same transaction as each entry. The bounds guard's input commits-or-aborts with the entry it guards (@C04a); no rollup is ever consulted by a guard.

The db handle must carry WAL + busy_timeout (the house defaults). Transfer is deliberately WRITE-FIRST: its opening statement is the guarded UPDATE itself (accounts resolved by subquery), so the transaction is a writer from its first statement and contending transfers queue under busy_timeout even on plain deferred transactions. A read-first shape would hit WAL's snapshot invalidation (SQLITE_BUSY past the busy handler) on read→write upgrade — the G-13 harness caught exactly that in an earlier form.

func NewStore

func NewStore(db *sql.DB, tablePrefix string) *Store

NewStore binds bal to a database with a tenant table prefix.

func (*Store) AccountScale added in v0.16.15

func (s *Store) AccountScale(ctx context.Context, accountID string) (uint8, error)

AccountScale returns an account's scale (render support).

func (*Store) Balance

func (s *Store) Balance(ctx context.Context, accountID string) (value int64, version int64, err error)

Balance returns the current balance and version for an account.

func (*Store) ChainOracle

func (s *Store) ChainOracle() chronicle.RebuildOracle

ChainOracle wraps VerifyChains as a rebuild oracle for iolu db check.

func (*Store) DefineAccount

func (s *Store) DefineAccount(ctx context.Context, def AccountDef) (AccountKey, error)

DefineAccount creates an account and its zero balance row. The internal key is allocated densely (MAX+1) inside the transaction.

func (*Store) Entries added in v0.16.15

func (s *Store) Entries(ctx context.Context, accountID string, afterEntryID int64, limit int) ([]Entry, error)

Entries returns up to limit journal rows for an account, oldest first, starting after afterEntryID (0 = from the beginning).

func (*Store) GlobalFoldOracle

func (s *Store) GlobalFoldOracle() chronicle.RebuildOracle

GlobalFoldOracle: SELECT SUM per account from the journal, compared row-for-row against balances — derive(journal) == current, exactly.

func (*Store) Init

func (s *Store) Init(ctx context.Context) error

Init creates the bal tables. Idempotent. The journal's `state` column (default 'committed') leaves room for holds (@B10) without migration.

func (*Store) Transfer

func (s *Store) Transfer(ctx context.Context, transferID, from, to string, amount int64, memo string, at time.Time) error

Transfer moves amount minor units from `from` to `to` as two signed journal entries (−a, +a) in ONE transaction (@B03). Admission is the house CAS discipline (@B06, T-34): the decision lives inside each UPDATE's predicate, rows-affected is the verdict — never read-decide-write. The guarded UPDATE is the transaction's FIRST statement (write-first; see Store docs), resolving the account and its floor by subquery and returning the chain triple in the same statement. Error discrimination (unknown vs not-postable vs bounds) happens on the failure path only, where the transaction is read-only and about to roll back.

func (*Store) VerifyChains

func (s *Store) VerifyChains(ctx context.Context) ([]ChainBreak, error)

VerifyChains is the local verifier (@B08): per account, every entry satisfies previous+amount=current, entryₙ.previous = entryₙ₋₁.current, and versions are contiguous. A lost, duplicated, or altered entry is not merely detected but LOCALISED to the exact break.

type UnknownAccountError

type UnknownAccountError struct{ AccountID string }

func (*UnknownAccountError) Error

func (e *UnknownAccountError) Error() string

Jump to

Keyboard shortcuts

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