@happyvertical/smrt-ledgers
Double-entry accounting, journal lifecycle, balance enforcement, and traceable entries.
What it gives you
Double-entry accounting, journal lifecycle, balance enforcement, and traceable entries.
This package is one focused part of the s-m-r-t stack. Its decorated models participate in the same manifest, tenancy, persistence, and generated-interface conventions as the rest of the framework.
Journal entries must balance
Accounts have debit- or credit-normal types. A Journal moves from draft to posted or voided, and each JournalEntry is debit-XOR-credit. In the default tolerant mode BALANCE_EPSILON is 0.01. This catches arithmetic imbalance, but a mixed-unit journal can still balance numerically without being semantically valid; keep integer-minor-unit commerce values consistent.
Named ledgers with exact balancing
A named Ledger fixes its owner, currency, and balance mode when it is created. In exact-integer mode, amounts must be safe integer minor units and debits must equal credits exactly. Accounts assigned to a ledger keep their scope, owner, and type, and cannot be deleted. Posted journals and lines in a named ledger are immutable, so they cannot be voided or deleted; correct them with compensating postings. postReceivable, postPayable, and postSettlement need an exact ledger. Each claims one key per tenant and ledger in a transaction and checks a payload digest when the same key is replayed.
Commerce posts to the accounts each invoice recorded
smrt-commerce posts each invoice line net to the revenue account stored on that line, and each tax component to its own liability account. Integer minor units balance exactly. A canceled invoice gets a reversing journal; the original stays posted.
Posted movements for reporting
JournalEntryCollection.listPostedMovements returns the posted debits and credits for a tenant, a set of accounts, a date range, and a currency, leaving out drafts and voids. The commerce tax summary uses it to set the movement on each tax liability account beside the tax it expects.
Summaries keep sensitive prompt variables out
Journal.summarize() uses the smrtLedgers.journal.summarize prompt-registry entry and excludes PII-conscious variables before invoking a provider.
Package UI
No UI layer to carry
This package stays focused on models, runtime behavior, or tooling. Build its interface with smrt-ui, or use a related domain package that exposes a /svelte entry point.
Intentionally headless
Package previews
No playground module yet
This package does not currently export a ./playground module. Its component names and import path remain available in the Components tab.
Intentionally not registered
Generated interface
REST
The exact route set comes from each model's @smrt({ api }) policy
and the application manifest—not from a second controller definition.
GET /api/journal-entries
GET /api/journal-entries/:id
POST /api/journal-entries
PATCH /api/journal-entries/:idWhat stays consistent
- Tenant scope and operation permissions run before model access.
- OpenAPI and generated clients use the same manifest fields.
- Packages can expose read-only or custom action sets.
Generated interface
MCP
AI tools are generated from the methods you explicitly include. Their inputs are validated against the same model metadata used by REST and CLI.
journal-entries_list({ where, limit, orderBy })
journal-entries_get({ id })
journal-entries_create({ data })
journal-entries_update({ id, data })Agent-safe by construction
- Tool availability can be narrower than the HTTP API.
- Principal execution intersects RBAC, agent ceilings, and persona tools.
- Descriptions and schemas are emitted from source metadata.
Browser-native agent interface
WebMCP
The generated web definitions carry the same tool names, descriptions, and JSON Schema
inputs as MCP. smrt-web registers them with document.modelContext.
import { webMcpToolDefinitions } from '@happyvertical/smrt-virt-web';
import { registerWebMcpTools } from '@happyvertical/smrt-web';
// Omitting an exposure policy registers read-only tools only.
const dispose = registerWebMcpTools(webMcpToolDefinitions);The page keeps its security boundary
- Tools execute through generated REST fetchers as the signed-in page user.
- Authentication, tenant gates, permissions, and field policy stay server-side.
- Feature detection makes registration a safe no-op outside supported browsers.
Generated interface
CLI
The app CLI turns manifest resources into predictable operator commands while keeping package-specific actions close to their models.
smrt journal-entries list --limit 25 --order-by created_at:desc
smrt journal-entries get <id>
smrt journal-entries create --from-file ./record.jsonMade for automation
- Machine-readable output and file-based input.
- The same include/exclude policy as other surfaces.
- Migration, introspection, dispatch, and knowledge commands stay in smrt-cli.