code wiki / (root) / nx_coa_lib.nx

nx_coa_lib.nx source

↩ module page · 108 lines · 5118 B

1// nx_coa_lib.nx -- F982: CHART OF ACCOUNTS + PERIOD CLOSE, on the sovereign ledger. 2// 3// nx_ledger deliberately has ONE balance convention (credits - debits, custodial sense) because a 4// primitive should not guess. This is the layer that makes a balance INTERPRETABLE: it records what 5// KIND each account is, and therefore which side is its NORMAL balance. 6// asset, expense -> DEBIT-normal (natural balance = debits - credits) 7// liability, equity, revenue -> CREDIT-normal (natural balance = credits - debits) 8// So a funded bank account reads +$100 instead of -$100, without the ledger having to take sides. 9// 10// ★DATA-DRIVEN (rule 11): the type->side map is STORED, not hardcoded. Adding an account kind is a 11// DATA write (coa_type_put), not a code change and not a recompile. 12// 13// ★FAIL-CLOSED (rule 26 analog): an account with no registered type, or a type with no registered 14// side, does NOT silently default to a side -- coa_natural returns 0 (unknown) and writes nothing. 15// Guessing a sign on money is how a balance sheet inverts silently. 16// 17// PERIOD CLOSE: closing a period appends a lock record. coa_post_ok REFUSES posting into a closed 18// period. Locks are append-only like everything else -- a close is a fact, not a mutable flag. 19// Closing is IDEMPOTENT (rule 10): closing an already-closed period is a no-op that still reports closed. 20// license_tier: ORIGINAL No hw writes (Rule 26). LIB (no main). 21 22import "nx_ledger_lib.nx" 23 24// register an account and its kind ("asset" | "liability" | "equity" | "revenue" | "expense" | any 25// kind the operator defines, so long as its side is registered via coa_type_put) 26func coa_put(prefix: *u8, acct: *u8, atype: *u8) -> i64 { 27 let k: *i64 = sys_mmap(8 * 1) as *i64 28 let v: *i64 = sys_mmap(8 * 1) as *i64 29 k[0] = ("type" as *u8) as i64 30 v[0] = atype as i64 31 let rec: *u8 = sys_mmap(512) 32 let rl: i64 = canon_encode(k, v, 1, rec) 33 return reg_put(prefix, "coa:" as *u8, "coa:__idx__" as *u8, acct, rec, rl) 34} 35 36// register which SIDE a kind is normal on. side = "debit" or "credit". THIS is the data-driven hinge. 37func coa_type_put(prefix: *u8, atype: *u8, side: *u8) -> i64 { 38 let k: *i64 = sys_mmap(8 * 1) as *i64 39 let v: *i64 = sys_mmap(8 * 1) as *i64 40 k[0] = ("side" as *u8) as i64 41 v[0] = side as i64 42 let rec: *u8 = sys_mmap(512) 43 let rl: i64 = canon_encode(k, v, 1, rec) 44 return reg_put(prefix, "coatype:" as *u8, "coatype:__idx__" as *u8, atype, rec, rl) 45} 46 47// an account's registered kind -> out; 1 if found, 0 if unregistered 48func coa_type(prefix: *u8, acct: *u8, out: *u8) -> i64 { 49 let po: *i64 = sys_mmap(16) as *i64 50 let lo: *i64 = sys_mmap(16) as *i64 51 if reg_get(prefix, "coa:" as *u8, acct, po, lo) != 1 { return 0 } 52 mt_field(po[0] as *u8, lo[0], "type" as *u8, 4, out) 53 if out[0] == (0 as u8) { return 0 } 54 return 1 55} 56 57// a kind's normal side -> out; 1 if found, 0 if unregistered 58func coa_side(prefix: *u8, atype: *u8, out: *u8) -> i64 { 59 let po: *i64 = sys_mmap(16) as *i64 60 let lo: *i64 = sys_mmap(16) as *i64 61 if reg_get(prefix, "coatype:" as *u8, atype, po, lo) != 1 { return 0 } 62 mt_field(po[0] as *u8, lo[0], "side" as *u8, 4, out) 63 if out[0] == (0 as u8) { return 0 } 64 return 1 65} 66 67// ★NATURAL BALANCE in the account's own sign. Writes to out[0]. 68// Returns 1 on success; 0 = FAIL-CLOSED (unknown account or unknown side -- nothing written to out). 69// ledger_prefix and coa_prefix are separate so a chart can describe a ledger it does not own. 70func coa_natural(ledger_prefix: *u8, coa_prefix: *u8, acct: *u8, out: *i64) -> i64 { 71 let ty: *u8 = sys_mmap(64) 72 if coa_type(coa_prefix, acct, ty) != 1 { return 0 } 73 let sd: *u8 = sys_mmap(64) 74 if coa_side(coa_prefix, ty, sd) != 1 { return 0 } 75 let s: *i64 = sys_mmap(8 * 4) as *i64 76 led_sums(ledger_prefix, acct, s) 77 if mt_streq(sd, "debit" as *u8) == 1 { out[0] = s[0] - s[1]; return 1 } 78 if mt_streq(sd, "credit" as *u8) == 1 { out[0] = s[1] - s[0]; return 1 } 79 return 0 80} 81 82// close an accounting period. Append-only fact; idempotent (rule 10). 83func coa_period_close(prefix: *u8, period: *u8) -> i64 { 84 let k: *i64 = sys_mmap(8 * 1) as *i64 85 let v: *i64 = sys_mmap(8 * 1) as *i64 86 k[0] = ("state" as *u8) as i64 87 v[0] = ("closed" as *u8) as i64 88 let rec: *u8 = sys_mmap(512) 89 let rl: i64 = canon_encode(k, v, 1, rec) 90 return reg_put(prefix, "coaper:" as *u8, "coaper:__idx__" as *u8, period, rec, rl) 91} 92 93// 1 if the period is closed 94func coa_period_closed(prefix: *u8, period: *u8) -> i64 { 95 let po: *i64 = sys_mmap(16) as *i64 96 let lo: *i64 = sys_mmap(16) as *i64 97 if reg_get(prefix, "coaper:" as *u8, period, po, lo) != 1 { return 0 } 98 let sf: *u8 = sys_mmap(32) 99 mt_field(po[0] as *u8, lo[0], "state" as *u8, 5, sf) 100 if mt_streq(sf, "closed" as *u8) == 1 { return 1 } 101 return 0 102} 103 104// ★THE GUARD: 1 only if a posting into `period` is permitted. A closed period REFUSES. 105func coa_post_ok(prefix: *u8, period: *u8) -> i64 { 106 if coa_period_closed(prefix, period) == 1 { return 0 } 107 return 1 108}