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}