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}