// The public API of the ledger module: the zod schemas, procedure contracts, permissions and events // another module or a browser may import. Nothing here reaches core/, adapters/ or a driver. import { procedure } from "@conusai-kit/core" import { z } from "zod" /** * The module's own error: its verification found projection drift, so it fences posting. A refusal of the * Ledger service is the kit's (`business_`, `@conusai-kit/business`), not a code of this module. */ export const LEDGER_ERRORS = { drift: "ledger_projection_drift", } as const const uuid = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" export const accountId = z.string().regex(new RegExp(`^acc_${uuid}$`)) export const transactionId = z.string().regex(new RegExp(`^txn_${uuid}$`)) const entryId = z.string().regex(new RegExp(`^ent_${uuid}$`)) const maximum = 9223372036854775807n /** Integer decimal strings cross JSON; conversion to BigInt happens only in the adapter. */ export const positiveMinor = z .string() .regex(/^[1-9][0-9]{0,18}$/) .refine((value) => { try { return BigInt(value) <= maximum } catch { return false } }) export const revision = positiveMinor const signedMinor = z .string() .regex(/^(?:0|-?[1-9][0-9]{0,18})$/) .refine((value) => { try { const amount = BigInt(value) return amount >= -9223372036854775808n && amount <= maximum } catch { return false } }) const currency = z.string().regex(/^[A-Z]{3}$/) const text = z .string() .trim() .min(1) .max(200) .refine((value) => ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) export const direction = z.enum(["debit", "credit"]) export const accountType = z.enum(["asset", "liability", "equity", "income", "expense"]) export const accountView = z.object({ accountId, code: z.string(), name: z.string(), currency, type: accountType, normalBalance: direction, allowNegative: z.boolean(), status: z.enum(["active", "archived"]), revision, }) export type AccountView = z.output export const balanceView = z.object({ accountId, currency, posted: signedMinor, pending: signedMinor, available: signedMinor, }) export type BalanceView = z.output export const entryView = z.object({ entryId, transactionId, accountId, currency, direction, amountMinor: positiveMinor, }) export type EntryView = z.output export const transactionView = z.object({ transactionId, revision, effectiveAt: z.iso.datetime({ offset: true }), description: z.string(), reference: z.string().nullable(), reverses: transactionId.nullable(), status: z.enum(["pending", "posted", "voided"]), entries: z.array(entryView).max(200), }) export type TransactionView = z.output export const verificationView = z.object({ checkedAccounts: z.number().int().nonnegative(), drift: z.array(accountId) }) export type VerificationView = z.output export const recordTerms = z .object({ effectiveAt: z.iso.datetime({ offset: true }), description: text, reference: text.optional(), status: z.enum(["pending", "posted"]), entries: z .array(z.object({ accountId, direction, amountMinor: positiveMinor }).strict()) .min(2) .max(200), }) .strict() export type RecordTerms = z.output const org = { org: z.string() } const cursor = z.string().min(1).max(4096).optional() const requestId = z.string().uuid() export const accounts = procedure("ledger.accounts.list") .kind("read") .scope("org") .permission("ledger.accounts.read") .input(z.object({ ...org, after: cursor })) .output(z.object({ rows: z.array(accountView), after: cursor })) .contract() export const account = procedure("ledger.accounts.get") .kind("read") .scope("org") .permission("ledger.accounts.read") .input(z.object({ ...org, accountId })) .output(accountView) .contract() export const balances = procedure("ledger.balances.list") .kind("read") .scope("org") .permission("ledger.balances.read") .input(z.object({ ...org, accountId: accountId.optional(), after: cursor })) .output(z.object({ rows: z.array(balanceView), after: cursor })) .contract() export const entries = procedure("ledger.entries.list") .kind("read") .scope("org") .permission("ledger.transactions.read") .input(z.object({ ...org, accountId, after: cursor })) .output(z.object({ rows: z.array(entryView), after: cursor })) .contract() export const transactions = procedure("ledger.transactions.list") .kind("read") .scope("org") .permission("ledger.transactions.read") .input(z.object({ ...org, after: cursor })) .output(z.object({ rows: z.array(transactionView), after: cursor })) .contract() export const transaction = procedure("ledger.transactions.get") .kind("read") .scope("org") .permission("ledger.transactions.read") .input(z.object({ ...org, transactionId })) .output(transactionView) .contract() export const verify = procedure("ledger.verify") .kind("read") .scope("org") .permission("ledger.balances.read") .input(z.object(org)) .output(verificationView) .contract() export const createAccount = procedure("ledger.accounts.create") .kind("effect") .scope("org") .permission("ledger.accounts.manage") .input( z .object({ ...org, requestId, code: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/), name: text, currency, type: accountType, normalBalance: direction, allowNegative: z.boolean(), }) .strict(), ) .output(accountView) .contract() export const updateAccount = procedure("ledger.accounts.update") .kind("effect") .scope("org") .permission("ledger.accounts.manage") .input( z .object({ ...org, accountId, requestId, expectedVersion: revision, name: text, status: z.enum(["active", "archived"]), allowNegative: z.boolean(), }) .strict(), ) .output(accountView) .contract() export const record = procedure("ledger.transactions.record") .kind("effect") .scope("org") .permission("ledger.transactions.write") .input(z.object({ ...org, requestId, terms: recordTerms, confirmed: z.literal(true) }).strict()) .output(transactionView) .contract() const transitionInput = z .object({ ...org, transactionId, expectedVersion: revision, requestId, confirmed: z.literal(true) }) .strict() export const post = procedure("ledger.transactions.post") .kind("effect") .scope("org") .permission("ledger.transactions.write") .input(transitionInput) .output(transactionView) .contract() export const voidTransaction = procedure("ledger.transactions.void") .kind("effect") .scope("org") .permission("ledger.transactions.write") .input(transitionInput) .output(transactionView) .contract() export const reverse = procedure("ledger.transactions.reverse") .kind("effect") .scope("org") .permission("ledger.transactions.reverse") .input(transitionInput) .output(transactionView) .contract()