code wiki / _hdl_build / nx_ecomat_domledger_lib.nx

nx_ecomat_domledger_lib.nx source

↩ module page · 139 lines · 6379 B

1// nx_ecomat_domledger_lib.nx -- emit the PER-DOMAIN maturity ledger, the missing data that makes 2// growth attribution exact. 3// 4// WHY: the aggregate ledger keeps only sum_cur/sum_bar/domains, so once a domain is added you can 5// never again separate a rung CLIMBED from a rung DECLARED (measured 2026-07-31: only 406 permil of 6// observed movement was attributable). Writing who held what, per beat, closes that permanently. 7// 8// The walk mirrors em_rollup_store EXACTLY -- same key order, same live-derive override via 9// em_domain_level -- so the per-domain rows must reconcile with the aggregate to the level. The 10// sibling gate asserts that reconciliation; two derivations of the same quantity that are never 11// compared are just two chances to be wrong. 12// license_tier: ORIGINAL No hw writes (Rule 26). 13import "nx_ecomat_lib.nx" 14 15const EDL_LOG: *u8 = "knowledge/status/ecomat_domains.log" 16const EDL_MODE: i64 = 0x1a4 17const EDL_KEY_CAP: i64 = 64 18const EDL_SUMS: i64 = 2 19 20// One row is composed here and emitted with a SINGLE sys_write. O_APPEND is atomic PER WRITE, not 21// per line -- the previous field-by-field style issued ~12 writes per row, so two seats appending 22// concurrently could SPLICE their rows into each other and produce a line that PARSES CLEANLY while 23// being semantically fabricated. Bound proof: 10 tag + 20 epoch + 8 " domain=" + <=127 name (ec_str 24// caps at ECOMAT_DOM_CAP) + 5 + 20 + 5 + 20 + 5 + 2 + 1 NL = 223 worst case, so 512 cannot overflow 25// by construction; the appenders check anyway and propagate refusal. 26const EDL_LINE_CAP: i64 = 512 27 28// BOUNDED + PROPAGATING appenders, mirroring ec_putstr: a negative offset in yields a negative 29// offset out, so ONE refusal aborts the whole line instead of letting a half-written row reach the 30// log. The bound is checked BEFORE any byte is written -- a check placed after the write it guards 31// is not a guard, it is a post-mortem. 32func edl_apps(b: *u8, cap: i64, o: i64, s: *u8) -> i64 { 33 if o < 0 { return 0 - 1 } 34 var n: i64 = 0 35 while s[n] != (0 as u8) { n = n + 1 } 36 if o + n > cap { return 0 - 1 } 37 var p: i64 = o 38 var i: i64 = 0 39 while i < n { b[p] = s[i]; p = p + 1; i = i + 1 } 40 return p 41} 42 43func edl_appn(b: *u8, cap: i64, o: i64, v: i64) -> i64 { 44 if o < 0 { return 0 - 1 } 45 let t: *u8 = sys_mmap(32) 46 var m: i64 = v 47 var neg: i64 = 0 48 if m < 0 { neg = 1; m = 0 - m } 49 var k: i64 = 0 50 if m == 0 { t[0] = 48 as u8; k = 1 } 51 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 52 if o + neg + k > cap { return 0 - 1 } 53 var p: i64 = o 54 if neg == 1 { b[p] = 45 as u8; p = p + 1 } 55 var i: i64 = 0 56 while i < k { b[p] = t[k - 1 - i]; p = p + 1; i = i + 1 } 57 return p 58} 59 60func edl_appc(b: *u8, cap: i64, o: i64, c: i64) -> i64 { 61 if o < 0 { return 0 - 1 } 62 if o + 1 > cap { return 0 - 1 } 63 b[o] = c as u8 64 return o + 1 65} 66 67// Walk the ecomat store in key order. When fd >= 0 each domain is appended as a row; when fd < 0 68// the walk is a pure dry run (the gate reconciles without writing). sums[0]=sum_cur sums[1]=sum_bar. 69// Returns the domain count, or -1 when the store will not open -- never 0-as-success, because an 70// unopenable store and a genuinely empty one demand opposite responses. 71func edl_walk(prefix: *u8, fd: i64, now: i64, sums: *i64) -> i64 { 72 sums[0] = 0 73 sums[1] = 0 74 let h: *i64 = ss_open(prefix) 75 if (h as i64) == 0 { return 0 - 1 } 76 let pq: *i64 = sys_mmap(16) as *i64 77 let lq: *i64 = sys_mmap(16) as *i64 78 let dom: *u8 = sys_mmap(ECOMAT_DOM_CAP) 79 var k: i64 = 0 80 var go: i64 = 1 81 var n: i64 = 0 82 while go == 1 { 83 let key: *u8 = sys_mmap(EDL_KEY_CAP) 84 ec_key(k, key) 85 if ss_hget(h, key, pq, lq) == 1 { 86 let v: *u8 = pq[0] as *u8 87 ec_str(v, 0, dom, ECOMAT_DOM_CAP) 88 var cur: i64 = ec_cur(v) 89 let bar: i64 = ec_bar(v) 90 let tout: *i64 = sys_mmap(16) as *i64 91 let dl: i64 = em_domain_level(v, tout) 92 if tout[0] != 0 { cur = dl } 93 sums[0] = sums[0] + cur 94 sums[1] = sums[1] + bar 95 if fd >= 0 { 96 let lb: *u8 = sys_mmap(EDL_LINE_CAP) 97 var o: i64 = edl_apps(lb, EDL_LINE_CAP, 0, "ECOMATDOM epoch=" as *u8) 98 o = edl_appn(lb, EDL_LINE_CAP, o, now) 99 o = edl_apps(lb, EDL_LINE_CAP, o, " domain=" as *u8) 100 o = edl_apps(lb, EDL_LINE_CAP, o, dom) 101 o = edl_apps(lb, EDL_LINE_CAP, o, " cur=" as *u8) 102 o = edl_appn(lb, EDL_LINE_CAP, o, cur) 103 o = edl_apps(lb, EDL_LINE_CAP, o, " bar=" as *u8) 104 o = edl_appn(lb, EDL_LINE_CAP, o, bar) 105 o = edl_apps(lb, EDL_LINE_CAP, o, " tri=" as *u8) 106 o = edl_appn(lb, EDL_LINE_CAP, o, tout[0]) 107 o = edl_appc(lb, EDL_LINE_CAP, o, 10) 108 if o < 0 { return 0 - 1 } 109 sys_write(fd, lb, o) 110 } 111 n = n + 1 112 k = k + 1 113 } else { go = 0 } 114 } 115 return n 116} 117 118// Terminator carrying the count the writer BELIEVES it wrote. A reader that finds rows without a 119// matching END row is looking at a truncated beat and must say so rather than average what it got. 120func edl_end(fd: i64, now: i64, n: i64, sums: *i64) -> i64 { 121 if fd < 0 { return 0 } 122 // SINGLE write, and it matters MORE here than for the rows: a SPLICED terminator would defeat 123 // the very completeness check that reads it, so the honesty signal must be the one line that 124 // cannot be torn. REFUSE rather than emit a truncated END -- a half-written terminator is worse 125 // than none, because the reader would take it as a real claim about a beat that never finished. 126 let lb: *u8 = sys_mmap(EDL_LINE_CAP) 127 var o: i64 = edl_apps(lb, EDL_LINE_CAP, 0, "ECOMATDOM-END epoch=" as *u8) 128 o = edl_appn(lb, EDL_LINE_CAP, o, now) 129 o = edl_apps(lb, EDL_LINE_CAP, o, " domains=" as *u8) 130 o = edl_appn(lb, EDL_LINE_CAP, o, n) 131 o = edl_apps(lb, EDL_LINE_CAP, o, " sum_cur=" as *u8) 132 o = edl_appn(lb, EDL_LINE_CAP, o, sums[0]) 133 o = edl_apps(lb, EDL_LINE_CAP, o, " sum_bar=" as *u8) 134 o = edl_appn(lb, EDL_LINE_CAP, o, sums[1]) 135 o = edl_apps(lb, EDL_LINE_CAP, o, " coverage_complete=1\n" as *u8) 136 if o < 0 { return 0 - 1 } 137 sys_write(fd, lb, o) 138 return 0 139}