code wiki / _hdl_build / nx_estate.nx
nx_estate.nx source
↩ module page · 165 lines · 10226 B
1// nx_estate.nx -- LIB: renders a will's SCHEDULE OF ASSETS (a tangible-personal-property memorandum) from the
2// universal inventory's `estate` domain. Sovereign no-JS document: estate summary (net worth, item count),
3// per-beneficiary bequests with subtotals, residuary (unassigned) section, and the full asset schedule.
4// Boundary defense (rule 12): every asset name / owner is HTML-escaped. Money is integer CENTS (no float).
5// One more CONSUMER of the universal inventory (like the restaurant site is a consumer of the food engine).
6// license_tier: ORIGINAL
7import "nx_inventory.nx"
8import "nx_syscalls.nx"
9
10func er_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 }
11
12// unassigned assets carry this explicit sentinel owner -> the residuary estate. (NishiLang gotcha: an empty
13// string literal "" aliases to another literal's bytes, so never store "" -- use a real sentinel.)
14const ER_RESID: *u8 = "(residuary)"
15const ER_OWNCAP: i64 = 64
16func er_is_residuary(o: *u8) -> i64 { return er_streq(o, ER_RESID) }
17
18// distinct owner strings (incl. "" = residuary) into owners_out (ptrs to copies); returns count
19// ★TWO DEFECTS ON ONE LINE, FIXED TOGETHER. (1) `ids` is 2 KiB of real VMA never released -- found by
20// nx_mmapbal as page-backed and in a loop. (2) `8 * 256` was an UNENFORCEABLE cap: iv_list took no bound,
21// so a domain with more than 256 ids wrote past the end of this allocation. iv_list now takes the cap and
22// announces truncation; IV_IDCAP names the one number that both sizes the buffer and bounds the write.
23// ⚠The per-item `ow` slots are NOT freed or hoisted: 64 B is bump-arena (munmap is a no-op there) and
24// owners_out RETAINS those pointers -- hoisting would alias every owner to one buffer and collapse the
25// distinct count to 1. ★A LEAK FIX THAT DOES NOT READ THE POINTER'S LIFETIME TURNS A LEAK INTO A CORRUPTION.
26// ★SAME CLASS AS THE iv_list OVERRUN, CLOSED THE SAME WAY. owners_out is the CALLER's array and only
27// the caller knows how big it is, so the bound is a parameter. Flagging this and moving on would have
28// left the identical unenforceable-cap defect one function away from the one I had just fixed.
29func er_distinct_owners(inv_prefix: *u8, domain: *u8, owners_out: *i64, cap: i64) -> i64 {
30 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
31 let n: i64 = iv_list(inv_prefix, domain, ids, IV_IDCAP)
32 var c: i64 = 0
33 var i: i64 = 0
34 while i < n {
35 let ow: *u8 = sys_mmap(64)
36 iv_field_of(inv_prefix, domain, ids[i] as *u8, 7, ow)
37 var seen: i64 = 0
38 var j: i64 = 0
39 while j < c { if er_streq(owners_out[j] as *u8, ow) == 1 { seen = 1 } j = j + 1 }
40 if seen == 0 {
41 if c >= cap {
42 // ★NO SILENT CAP. An estate with more distribution groups than the caller's array holds
43 // would otherwise write past its end; it now stops and SAYS SO, so a partial schedule is
44 // visible rather than a corrupted one.
45 let tm: *u8 = "er_distinct_owners: TRUNCATED -- more distribution groups than the caller's array holds\n" as *u8
46 var tl: i64 = 0
47 while tm[tl] != (0 as u8) { tl = tl + 1 }
48 sys_write(2, tm, tl)
49 i = n
50 } else { owners_out[c] = ow as i64; c = c + 1 }
51 }
52 i = i + 1
53 }
54 sys_munmap(ids as *u8, 8 * IV_IDCAP)
55 return c
56}
57
58// total value (cents) of items willed to `owner`
59func er_owner_total(inv_prefix: *u8, domain: *u8, owner: *u8) -> i64 {
60 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
61 let n: i64 = iv_list(inv_prefix, domain, ids, IV_IDCAP)
62 var tot: i64 = 0
63 var i: i64 = 0
64 while i < n {
65 let ow: *u8 = sys_mmap(64)
66 iv_field_of(inv_prefix, domain, ids[i] as *u8, 7, ow)
67 if er_streq(ow, owner) == 1 { tot = tot + iv_value(inv_prefix, domain, ids[i] as *u8) }
68 i = i + 1
69 }
70 sys_munmap(ids as *u8, 8 * IV_IDCAP)
71 return tot
72}
73
74// emit one item row "<tr>...<td>$value</td></tr>" (cols depend on with_ben); returns new off
75func er_item_row(inv_prefix: *u8, domain: *u8, id: *u8, with_ben: i64, out: *u8, off: i64) -> i64 {
76 let f: *u8 = sys_mmap(128)
77 var o: i64 = off
78 o = as_append(out, o, "<tr><td>" as *u8)
79 iv_field_of(inv_prefix, domain, id, 0, f); o = as_append_escaped(out, o, f, as_len(f)) // name
80 o = as_append(out, o, "</td><td>" as *u8)
81 iv_field_of(inv_prefix, domain, id, 1, f); o = as_append_escaped(out, o, f, as_len(f)) // category
82 o = as_append(out, o, "</td><td>" as *u8)
83 iv_field_of(inv_prefix, domain, id, 4, f); o = as_append_escaped(out, o, f, as_len(f)) // location
84 if with_ben == 1 {
85 o = as_append(out, o, "</td><td>" as *u8)
86 iv_field_of(inv_prefix, domain, id, 7, f) // beneficiary
87 if er_is_residuary(f) == 1 { o = as_append(out, o, "(residuary)" as *u8) } else { o = as_append_escaped(out, o, f, as_len(f)) }
88 }
89 o = as_append(out, o, "</td><td class='v'>" as *u8)
90 o = iv_money(out, o, iv_value(inv_prefix, domain, id))
91 o = as_append(out, o, "</td></tr>" as *u8)
92 return o
93}
94
95// render a per-beneficiary bequest section
96func er_owner_section(inv_prefix: *u8, domain: *u8, owner: *u8, out: *u8, off: i64) -> i64 {
97 var o: i64 = off
98 o = as_append(out, o, "<section class='ben'><h3>" as *u8)
99 if er_is_residuary(owner) == 1 { o = as_append(out, o, "Residuary estate (unassigned)" as *u8) } else {
100 o = as_append(out, o, "Bequeathed to " as *u8)
101 o = as_append_escaped(out, o, owner, as_len(owner))
102 }
103 o = as_append(out, o, "</h3><table><tr><th>Item</th><th>Category</th><th>Location</th><th>Value</th></tr>" as *u8)
104 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
105 let n: i64 = iv_list(inv_prefix, domain, ids, IV_IDCAP)
106 var i: i64 = 0
107 while i < n {
108 let ow: *u8 = sys_mmap(64)
109 iv_field_of(inv_prefix, domain, ids[i] as *u8, 7, ow)
110 if er_streq(ow, owner) == 1 { o = er_item_row(inv_prefix, domain, ids[i] as *u8, 0, out, o) }
111 i = i + 1
112 }
113 sys_munmap(ids as *u8, 8 * IV_IDCAP)
114 o = as_append(out, o, "</table><p class='sub'>Subtotal: " as *u8)
115 o = iv_money(out, o, er_owner_total(inv_prefix, domain, owner))
116 o = as_append(out, o, "</p></section>" as *u8)
117 return o
118}
119
120// render the full Schedule of Assets document (NUL-terminated); returns byte length.
121func er_render_schedule(inv_prefix: *u8, domain: *u8, estate_owner: *u8, out: *u8) -> i64 {
122 var o: i64 = 0
123 o = as_append(out, o, "<!doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Schedule of Assets</title><style>body{margin:0;font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;background:#f6f4ef;line-height:1.5}.doc{max-width:720px;margin:0 auto;padding:32px 28px;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.08)}h1{font-size:1.5rem;margin:0 0 2px;text-align:center}.estof{text-align:center;color:#555;margin:0 0 18px}.summary{background:#f0ece3;border:1px solid #ddd5c6;border-radius:6px;padding:12px 16px;margin:0 0 22px}h3{font-size:1.05rem;border-bottom:1px solid #c9b896;padding-bottom:4px;margin:22px 0 8px}table{border-collapse:collapse;width:100%;font-size:.95rem}th{text-align:left;border-bottom:2px solid #c9b896;padding:5px 8px;color:#555}td{padding:5px 8px;border-bottom:1px solid #ece5d8}td.v{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}.sub{text-align:right;font-weight:700;margin:6px 0 0}.tot{font-size:1.1rem;font-weight:700}.ftr{margin-top:26px;border-top:1px solid #ddd5c6;padding-top:12px;color:#777;font-size:.82rem;text-align:center}</style></head><body><div class='doc'>" as *u8)
124 o = as_append(out, o, "<h1>Schedule of Assets</h1><p class='estof'>Estate of " as *u8)
125 o = as_append_escaped(out, o, estate_owner, as_len(estate_owner))
126 o = as_append(out, o, "</p>" as *u8)
127
128 let ids: *i64 = sys_mmap(8 * IV_IDCAP) as *i64
129 let n: i64 = iv_list(inv_prefix, domain, ids, IV_IDCAP)
130 // ER_OWNCAP sizes the array AND is passed as the bound, so the two cannot drift.
131 let owners: *i64 = sys_mmap(8 * ER_OWNCAP) as *i64
132 let no: i64 = er_distinct_owners(inv_prefix, domain, owners, ER_OWNCAP)
133 let total: i64 = iv_value_total(inv_prefix, domain)
134
135 o = as_append(out, o, "<div class='summary'><span class='tot'>Total estate value: " as *u8)
136 o = iv_money(out, o, total)
137 o = as_append(out, o, "</span><br>" as *u8)
138 o = iv_num(out, o, n)
139 o = as_append(out, o, " catalogued assets across " as *u8)
140 o = iv_num(out, o, no)
141 o = as_append(out, o, " distribution group(s).</div>" as *u8)
142
143 // named beneficiaries first
144 var i: i64 = 0
145 while i < no { if er_is_residuary(owners[i] as *u8) == 0 { o = er_owner_section(inv_prefix, domain, owners[i] as *u8, out, o) } i = i + 1 }
146 // residuary last
147 i = 0
148 while i < no { if er_is_residuary(owners[i] as *u8) == 1 { o = er_owner_section(inv_prefix, domain, owners[i] as *u8, out, o) } i = i + 1 }
149
150 // full schedule
151 o = as_append(out, o, "<h3>Full schedule</h3><table><tr><th>Item</th><th>Category</th><th>Location</th><th>Beneficiary</th><th>Value</th></tr>" as *u8)
152 i = 0
153 while i < n { o = er_item_row(inv_prefix, domain, ids[i] as *u8, 1, out, o); i = i + 1 }
154 o = as_append(out, o, "<tr><td colspan='4' class='tot'>Total</td><td class='v tot'>" as *u8)
155 o = iv_money(out, o, total)
156 o = as_append(out, o, "</td></tr></table>" as *u8)
157
158 o = as_append(out, o, "<p class='ftr'>Generated by the Nishi inventory engine — sovereign, no tracking. A tangible-personal-property memorandum for use with your will; review with your estate attorney.</p></div></body></html>" as *u8)
159 out[o] = 0 as u8
160 // Both arrays are live until here and dead after; released rather than left mapped for the life of
161 // the process. owners[] holds pointers into arena slots, which are not ours to free.
162 sys_munmap(owners as *u8, 8 * ER_OWNCAP)
163 sys_munmap(ids as *u8, 8 * IV_IDCAP)
164 return o
165}