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 &mdash; 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}