Documentation
¶
Overview ¶
manager.go is the PER-TENANT selector over the treasury Store: it resolves each request to its OWN Hanzo Base (SQLite) file instead of a process-wide singleton, so one tenant's finance/ledger writes can NEVER appear in another tenant's reads. This is the storage side of the standing Hanzo rule — Postgres stays a supported option (Formance, one layer up), but every tenant's books run live on its own Base file.
TWO file classes, one opener:
- the HOUSE ledger — the platform's OWN reserve fund (fund:reserve, revenue:*, payout:*): a SINGLE single-writer, overdraw-guarded file. It CANNOT be split per tenant (the reserve overdraw guard is one atomic balance), so it is the deployment's own book, in the system namespace.
- a CUSTOMER ledger — one isolated file per tenant, in that org's namespace, opened on first use and cached.
The house fund is UNREACHABLE by naming a tenant, and no longer because a slug is reserved: the system namespace is a different KIND from every org namespace, so a tenant string cannot render to it however it is spelled. That is why the reserved-slug guard, the hash escape hatch and the third physical layout this file used to carry are gone — hanzoai/namespace already answers "which file does this entity's ledger live in", injectively, and answering it a second time here is how two answers start.
Package sqlstore is the Hanzo Base (HIP-0105 per-tenant SQLite) adapter for the ledger core: it implements ledger.Store + ledger.Tx over one SQLite file, and nothing more. It carries the storage concern the core deliberately does not — the SAME single-connection + WAL pattern every clients/* store uses (referrals, crm, prompts), so it is Base-compatible and drops into the unified binary unchanged.
It imports the core (ledger) and the one Hanzo SQLite driver — never cloud, zip, or IAM. When the core is lifted to hanzoai/finance this adapter travels with it as the default backend; the driver import is the only thing a different Base backend would swap.
MONEY IS EXACT AND BIG. Amounts are 18-decimal USD (1e-18, the EVM/uint256 unit) held as big.Int money.Amount — a value exceeds SQLite's 64-bit INTEGER past ~$9.20, so amount columns are TEXT (the signed 18-decimal integer string) and an account's balance is a maintained running total (treasury_accounts.balance), NOT a SQL SUM (you cannot SUM a decimal-string column, and a busy wallet's million usage postings must not be re-summed on every gate read). The running balance is updated inside the same transaction as each posting, so it can never drift from the journal.
Index ¶
- type Manager
- type Store
- func (s *Store) Balance(ctx context.Context, account string) (money.Amount, error)
- func (s *Store) BalancesWithPrefix(ctx context.Context, prefix string) (map[string]money.Amount, error)
- func (s *Store) Close() error
- func (s *Store) Entries(ctx context.Context, limit int) ([]ledger.JournalEntry, error)
- func (s *Store) Policy(ctx context.Context) (ledger.SharePolicy, error)
- func (s *Store) SetPolicy(ctx context.Context, p ledger.SharePolicy) error
- func (s *Store) SumByKindSince(ctx context.Context, kind string, since int64) (money.Amount, error)
- func (s *Store) Tx(ctx context.Context, fn func(ledger.Tx) error) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager opens and caches one *Store per namespace. It is safe for concurrent use. Each distinct tenant maps to a distinct file; the mapping is namespace's, so it is injective (it never folds "acme" and "ACME" into one bucket — that would itself be a cross-tenant break) and can never traverse the path or reach the house fund.
func NewManager ¶
NewManager roots every ledger under dataDir.
func (*Manager) Close ¶
Close closes every open store (house + tenants). Idempotent; returns the first close error, if any.
func (*Manager) Get ¶
Get resolves a tenant's OWN ledger. It takes the NAME and not the tenant string it was folded from: this package sits below cloud, so it cannot reach cloud's one door for turning a principal into a name, and a second fold here would be a second answer to which file a tenant's money is in. Handed the name, it cannot open another tenant's file, cannot reach the house fund (a different KIND), and cannot leave the data directory.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the SQLite-backed ledger persistence. ONE file holds the whole chart of accounts, journal and policy for a deployment (the platform's own books — not per-org, unlike a tenant product store). Serialized on a single connection so the engine's balance-guard read-then-write is atomic under load.
func Open ¶
Open opens (creating + migrating) the ledger database subsystem names for ns, under dir.
The namespace is a PARAMETER because these stores do not share one: a customer's ledger belongs to that org, the house book belongs to the deployment. It is also what keys the file, so an opener cannot name one entity's ledger and unlock it with another's key — that pairing is made once, inside cek, from this one value.
func (*Store) BalancesWithPrefix ¶
func (*Store) SumByKindSince ¶
SumByKindSince sums the entry amounts of ONE kind created at/after `since` (unix seconds) — a read-only aggregate over the indexed created_at, no schema change. Amounts are 18-decimal TEXT (SQLite INTEGER overflows past ~$9.20), so the fold is in Go. This is the usage-cap's period-spend source: sum kind "finance.usage" for the org store since the start of the UTC month, so a cap enforces on real ledger spend rather than a bounded, truncatable journal scan.