code wiki / _hdl_build / nx_leasemine.nx

nx_leasemine.nx source

↩ module page · 345 lines · 16507 B

1// nx_leasemine.nx -- LEASES-CLASS MINER over the claims journal (miner-sota F1017, 2026-07-23). 2// The census (nx_minecov MC04) measured leases COVERED-but-UNMINED: nx_claims walk derives CURRENT 3// holder/TTL state, but nobody mines the HISTORY. This derives what a point-in-time walk cannot: 4// ORPHANED = CLAIMs with no matching RELEASE (the F214 crash-lease signal, per resource) 5// CONTENDED = a resource claimed by MORE THAN ONE distinct actor (where lanes actually collide) 6// CHURN = claims per resource, ranked; plus hold-time (avg/max) from matched CLAIM->RELEASE pairs 7// A resource claimed once and released is healthy; one claimed 9 times by 4 actors is a coordination 8// hotspot, and one claimed and never released is an orphan a walker must reap. now is INJECTABLE so 9// hold-time arithmetic is deterministic under test. Composes nx_sovjson_lib (sj_*). Read-only. Rule 26. 10// mine <journal> [minsup] [now] -> JSON: totals, by_resource ranked, orphans, contention 11// selftest <scratch-prefix> -> gate T1..T8 12// frame cols: ts TAB verb(CLAIM|BEAT|RELEASE) TAB resource TAB actor TAB ttl TAB note 13// license_tier: ORIGINAL 14import "nx_sovjson_lib.nx" 15import "nx_syscalls.nx" 16import "nx_gate_verdict.nx" 17const LM_MAGIC_1785000000: i64 = 1785000000 18const LM_MAGIC_4088: i64 = 4088 19 20const LM_WIN: i64 = 4194304 21const LM_OUT: i64 = 65536 22const LM_PATH: i64 = 512 23const LM_RES: i64 = 512 24const LM_ACTORS: i64 = 8 25const LM_TOPN: i64 = 20 26const LM_PERMIL: i64 = 1000 27const LM_NAME_MAX: i64 = 120 28const LM_MODE_644: i64 = 420 29 30func lm_read(path: *u8, buf: *u8, cap: i64) -> i64 { 31 let fd: i64 = sys_openat_rd(path) 32 if fd < 0 { return -1 } 33 var n: i64 = 0 34 var go: i64 = 1 35 while go == 1 { let r: i64 = sys_read(fd, ((buf as i64)+n) as *u8, cap-n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap { go = 0 } } 36 sys_close(fd) 37 return n 38} 39func lm_find(q: *u8, n: i64, lit: *u8) -> i64 { 40 let ll: i64 = sj_vlen(lit) 41 if ll == 0 { return 0 } 42 var i: i64 = 0 43 while i + ll <= n { 44 var k: i64 = 0 45 var ok: i64 = 1 46 while k < ll { if q[i+k] != lit[k] { ok = 0; k = ll } else { k = k + 1 } } 47 if ok == 1 { return 1 } 48 i = i + 1 49 } 50 return 0 51} 52func lm_key(d: *u8, o: i64, name: *u8) -> i64 { 53 d[o] = 34 as u8 54 var p: i64 = o + 1 55 p = sj_cat(d, p, name) 56 d[p] = 34 as u8 57 p = p + 1 58 d[p] = 58 as u8 59 p = p + 1 60 return p 61} 62func lm_digits(q: *u8, s: i64, e: i64) -> i64 { 63 if e <= s { return 0 } 64 var i: i64 = s 65 while i < e { let c: i64 = q[i] as i64; if c < 48 { return 0 } if c > 57 { return 0 } i = i + 1 } 66 return 1 67} 68func lm_span_eq(q: *u8, a: i64, ae: i64, b: i64, be: i64) -> i64 { 69 if ae - a != be - b { return 0 } 70 var i: i64 = 0 71 while a + i < ae { if q[a+i] != q[b+i] { return 0 } i = i + 1 } 72 return 1 73} 74func lm_mine_json(jpath: *u8, minsup: i64, now: i64, d: *u8) -> i64 { 75 let q: *u8 = sys_mmap(LM_WIN) 76 let n: i64 = lm_read(jpath, q, LM_WIN - 8) 77 if n <= 0 { return -1 } 78 var trunc: i64 = 0 79 if n >= LM_WIN - 8 { trunc = 1 } 80 let rs: *i64 = sys_mmap(8 * LM_RES) as *i64 81 let re: *i64 = sys_mmap(8 * LM_RES) as *i64 82 let cl: *i64 = sys_mmap(8 * LM_RES) as *i64 83 let rel: *i64 = sys_mmap(8 * LM_RES) as *i64 84 let bt: *i64 = sys_mmap(8 * LM_RES) as *i64 85 let lastc: *i64 = sys_mmap(8 * LM_RES) as *i64 86 let holds: *i64 = sys_mmap(8 * LM_RES) as *i64 87 let holdn: *i64 = sys_mmap(8 * LM_RES) as *i64 88 let holdx: *i64 = sys_mmap(8 * LM_RES) as *i64 89 let nact: *i64 = sys_mmap(8 * LM_RES) as *i64 90 let acts: *i64 = sys_mmap(8 * LM_RES * LM_ACTORS) as *i64 91 let acte: *i64 = sys_mmap(8 * LM_RES * LM_ACTORS) as *i64 92 let done: *i64 = sys_mmap(8 * LM_RES) as *i64 93 let sp: *i64 = sys_mmap(16) as *i64 94 var nres: i64 = 0 95 var rcapped: i64 = 0 96 var frames: i64 = 0 97 var malformed: i64 = 0 98 var nclaim: i64 = 0 99 var nrel: i64 = 0 100 var nbeat: i64 = 0 101 var i: i64 = 0 102 while i < n { 103 let le: i64 = sj_le(q, i, n) 104 var ok: i64 = 0 105 var cmt: i64 = 0 106 if le > i { if q[i] == (35 as u8) { cmt = 1 } } 107 if le > i { if cmt == 0 { if sj_col(q, i, le, 3, sp) == 1 { ok = 1 } } } 108 if ok == 0 { if le > i { if cmt == 0 { malformed = malformed + 1 } } } else { 109 let as0: i64 = sp[0] 110 let ae0: i64 = sp[1] 111 sj_col(q, i, le, 1, sp) 112 var vb: i64 = 3 113 if sj_lit_eq(q, sp[0], sp[1], "CLAIM" as *u8) == 1 { vb = 0 } 114 if sj_lit_eq(q, sp[0], sp[1], "RELEASE" as *u8) == 1 { vb = 1 } 115 if sj_lit_eq(q, sp[0], sp[1], "BEAT" as *u8) == 1 { vb = 2 } 116 if vb == 3 { malformed = malformed + 1 } else { 117 frames = frames + 1 118 if vb == 0 { nclaim = nclaim + 1 } 119 if vb == 1 { nrel = nrel + 1 } 120 if vb == 2 { nbeat = nbeat + 1 } 121 var ts: i64 = 0 122 if sj_col(q, i, le, 0, sp) == 1 { if lm_digits(q, sp[0], sp[1]) == 1 { ts = sj_atoi_span(q, sp[0], sp[1]) } } 123 sj_col(q, i, le, 2, sp) 124 var ri: i64 = 0 - 1 125 var k: i64 = 0 126 while k < nres { 127 if lm_span_eq(q, sp[0], sp[1], rs[k], re[k]) == 1 { ri = k; k = nres } 128 k = k + 1 129 } 130 if ri < 0 { 131 if nres >= LM_RES { rcapped = 1 } else { 132 rs[nres] = sp[0] 133 re[nres] = sp[1] 134 ri = nres 135 nres = nres + 1 136 } 137 } 138 if ri >= 0 { 139 if vb == 0 { cl[ri] = cl[ri] + 1; lastc[ri] = ts } 140 if vb == 2 { bt[ri] = bt[ri] + 1 } 141 if vb == 1 { 142 rel[ri] = rel[ri] + 1 143 if lastc[ri] > 0 { if ts >= lastc[ri] { 144 let h: i64 = ts - lastc[ri] 145 holds[ri] = holds[ri] + h 146 holdn[ri] = holdn[ri] + 1 147 if h > holdx[ri] { holdx[ri] = h } 148 lastc[ri] = 0 149 } } 150 } 151 // distinct actors per resource (bounded, capped+declared) 152 var ai: i64 = 0 - 1 153 var k2: i64 = 0 154 while k2 < nact[ri] { 155 if lm_span_eq(q, as0, ae0, acts[ri * LM_ACTORS + k2], acte[ri * LM_ACTORS + k2]) == 1 { ai = k2; k2 = nact[ri] } 156 k2 = k2 + 1 157 } 158 if ai < 0 { if nact[ri] < LM_ACTORS { 159 acts[ri * LM_ACTORS + nact[ri]] = as0 160 acte[ri * LM_ACTORS + nact[ri]] = ae0 161 nact[ri] = nact[ri] + 1 162 } else { rcapped = 1 } } 163 } 164 } 165 } 166 i = le + 1 167 } 168 var orphan_res: i64 = 0 169 var contended: i64 = 0 170 var k3: i64 = 0 171 while k3 < nres { 172 if cl[k3] > rel[k3] { orphan_res = orphan_res + 1 } 173 if nact[k3] > 1 { contended = contended + 1 } 174 k3 = k3 + 1 175 } 176 var p: i64 = 0 177 d[p] = 123 as u8; p = p + 1 178 p = lm_key(d, p, "v" as *u8); p = sj_catn(d, p, 1); d[p] = 44 as u8; p = p + 1 179 p = lm_key(d, p, "tool" as *u8) 180 d[p] = 34 as u8; p = p + 1 181 p = sj_cat(d, p, "nx_leasemine" as *u8) 182 d[p] = 34 as u8; p = p + 1 183 d[p] = 44 as u8; p = p + 1 184 p = lm_key(d, p, "averb" as *u8) 185 d[p] = 34 as u8; p = p + 1 186 p = sj_cat(d, p, "mine" as *u8) 187 d[p] = 34 as u8; p = p + 1 188 d[p] = 44 as u8; p = p + 1 189 p = lm_key(d, p, "journal" as *u8) 190 d[p] = 34 as u8; p = p + 1 191 p = sj_cat_esc(d, p, jpath, 0, sj_vlen(jpath), LM_NAME_MAX) 192 d[p] = 34 as u8; p = p + 1 193 d[p] = 44 as u8; p = p + 1 194 p = lm_key(d, p, "journal_bytes" as *u8); p = sj_catn(d, p, n); d[p] = 44 as u8; p = p + 1 195 p = lm_key(d, p, "truncated" as *u8); p = sj_catn(d, p, trunc); d[p] = 44 as u8; p = p + 1 196 p = lm_key(d, p, "frames" as *u8); p = sj_catn(d, p, frames); d[p] = 44 as u8; p = p + 1 197 p = lm_key(d, p, "malformed" as *u8); p = sj_catn(d, p, malformed); d[p] = 44 as u8; p = p + 1 198 p = lm_key(d, p, "claims" as *u8); p = sj_catn(d, p, nclaim); d[p] = 44 as u8; p = p + 1 199 p = lm_key(d, p, "releases" as *u8); p = sj_catn(d, p, nrel); d[p] = 44 as u8; p = p + 1 200 p = lm_key(d, p, "beats" as *u8); p = sj_catn(d, p, nbeat); d[p] = 44 as u8; p = p + 1 201 p = lm_key(d, p, "resources" as *u8); p = sj_catn(d, p, nres); d[p] = 44 as u8; p = p + 1 202 p = lm_key(d, p, "orphaned_resources" as *u8); p = sj_catn(d, p, orphan_res); d[p] = 44 as u8; p = p + 1 203 p = lm_key(d, p, "contended_resources" as *u8); p = sj_catn(d, p, contended); d[p] = 44 as u8; p = p + 1 204 var rp: i64 = 0 - 1 205 if nclaim > 0 { rp = nrel * LM_PERMIL / nclaim } 206 p = lm_key(d, p, "release_permil" as *u8); p = sj_catn(d, p, rp); d[p] = 44 as u8; p = p + 1 207 p = lm_key(d, p, "by_resource" as *u8) 208 d[p] = 91 as u8; p = p + 1 209 var shown: i64 = 0 210 var below: i64 = 0 211 var efirst: i64 = 1 212 var pass: i64 = 0 213 while pass < nres { 214 var mi: i64 = 0 - 1 215 var mx: i64 = 0 - 1 216 var k4: i64 = 0 217 while k4 < nres { 218 if done[k4] == 0 { if cl[k4] > mx { mx = cl[k4]; mi = k4 } } 219 k4 = k4 + 1 220 } 221 if mi >= 0 { 222 done[mi] = 1 223 if mx < minsup { below = below + 1 } else { 224 if shown < LM_TOPN { 225 if efirst == 0 { d[p] = 44 as u8; p = p + 1 } 226 efirst = 0 227 d[p] = 123 as u8; p = p + 1 228 p = lm_key(d, p, "resource" as *u8) 229 d[p] = 34 as u8; p = p + 1 230 p = sj_cat_esc(d, p, q, rs[mi], re[mi], LM_NAME_MAX) 231 d[p] = 34 as u8; p = p + 1 232 d[p] = 44 as u8; p = p + 1 233 p = lm_key(d, p, "claims" as *u8); p = sj_catn(d, p, cl[mi]); d[p] = 44 as u8; p = p + 1 234 p = lm_key(d, p, "releases" as *u8); p = sj_catn(d, p, rel[mi]); d[p] = 44 as u8; p = p + 1 235 p = lm_key(d, p, "orphaned" as *u8); p = sj_catn(d, p, cl[mi] - rel[mi]); d[p] = 44 as u8; p = p + 1 236 p = lm_key(d, p, "actors" as *u8); p = sj_catn(d, p, nact[mi]); d[p] = 44 as u8; p = p + 1 237 var av: i64 = 0 - 1 238 if holdn[mi] > 0 { av = holds[mi] / holdn[mi] } 239 p = lm_key(d, p, "avg_hold_s" as *u8); p = sj_catn(d, p, av); d[p] = 44 as u8; p = p + 1 240 p = lm_key(d, p, "max_hold_s" as *u8); p = sj_catn(d, p, holdx[mi]) 241 d[p] = 125 as u8; p = p + 1 242 shown = shown + 1 243 } 244 } 245 } 246 pass = pass + 1 247 } 248 d[p] = 93 as u8; p = p + 1 249 d[p] = 44 as u8; p = p + 1 250 p = lm_key(d, p, "resources_shown" as *u8); p = sj_catn(d, p, shown); d[p] = 44 as u8; p = p + 1 251 p = lm_key(d, p, "resources_below_minsup" as *u8); p = sj_catn(d, p, below); d[p] = 44 as u8; p = p + 1 252 p = lm_key(d, p, "slots_capped" as *u8); p = sj_catn(d, p, rcapped) 253 d[p] = 125 as u8; p = p + 1 254 d[p] = 10 as u8; p = p + 1 255 return p 256} 257func lm_selftest(prefix: *u8) -> i64 { 258 let ctr: *i64 = gv_ctr() 259 gv_head("nx_leasemine selftest -- orphan and contention teeth (a walk sees NOW, a mine sees HISTORY)" as *u8) 260 let now: i64 = LM_MAGIC_1785000000 261 let fx: *u8 = sys_mmap(LM_PATH) 262 var o: i64 = sj_cat(fx, 0, prefix) 263 o = sj_catn(fx, o, sys_now_realtime_sec()) 264 o = sj_cat(fx, o, ".lm" as *u8) 265 fx[o] = 0 as u8 266 let miss: *u8 = sys_mmap(LM_PATH) 267 o = sj_cat(miss, 0, fx) 268 o = sj_cat(miss, o, ".absent" as *u8) 269 miss[o] = 0 as u8 270 // RA: 2 claims 2 releases by ONE actor, holds 100 and 300 => avg 200 max 300, healthy 271 // RB: claimed by TWO actors, 1 release => contended AND 1 orphan 272 // RC: claimed once, never released => orphan (the F214 crash-lease shape) 273 let fd: i64 = sys_openat_wr(fx, LM_MODE_644) 274 let lb: *u8 = sys_mmap(LM_MAGIC_4088) 275 var lo: i64 = sj_cat(lb, 0, "# fixture\n" as *u8) 276 lo = sj_cat(lb, lo, "1784900000\tCLAIM\tRA\tactor1\t3600\tfirst\n" as *u8) 277 lo = sj_cat(lb, lo, "1784900050\tBEAT\tRA\tactor1\t0\tbeat\n" as *u8) 278 lo = sj_cat(lb, lo, "1784900100\tRELEASE\tRA\tactor1\t0\tdone\n" as *u8) 279 lo = sj_cat(lb, lo, "1784900200\tCLAIM\tRA\tactor1\t3600\tsecond\n" as *u8) 280 lo = sj_cat(lb, lo, "1784900500\tRELEASE\tRA\tactor1\t0\tdone\n" as *u8) 281 lo = sj_cat(lb, lo, "1784901000\tCLAIM\tRB\tactor1\t3600\tmine\n" as *u8) 282 lo = sj_cat(lb, lo, "1784901100\tRELEASE\tRB\tactor1\t0\tdone\n" as *u8) 283 lo = sj_cat(lb, lo, "1784901200\tCLAIM\tRB\tactor2\t3600\tnow mine\n" as *u8) 284 lo = sj_cat(lb, lo, "1784902000\tCLAIM\tRC\tactor3\t3600\tnever released\n" as *u8) 285 lo = sj_cat(lb, lo, "1784903000\tPURPLE\tRD\tactor4\t0\tbogus verb\n" as *u8) 286 lo = sj_cat(lb, lo, "junk\n" as *u8) 287 sys_write(fd, lb, lo) 288 sys_close(fd) 289 let d: *u8 = sys_mmap(LM_OUT) 290 let r1: i64 = lm_mine_json(miss, 1, now, d) 291 var ok1: i64 = 0 292 if r1 < 0 { ok1 = 1 } 293 gv_check("T1 missing journal refused fail closed" as *u8, ok1, ctr) 294 let dl: i64 = lm_mine_json(fx, 1, now, d) 295 var ok2: i64 = 0 296 if dl > 0 { if lm_find(d, dl, "\"frames\":9,\"malformed\":2,\"claims\":5,\"releases\":3,\"beats\":1" as *u8) == 1 { ok2 = 1 } } 297 gv_check("T2 exact verb split; BOTH the bogus verb and the junk line counted malformed" as *u8, ok2, ctr) 298 var ok3: i64 = 0 299 if lm_find(d, dl, "\"resources\":3" as *u8) == 1 { if lm_find(d, dl, "\"resource\":\"RD\"" as *u8) == 0 { ok3 = 1 } } 300 gv_check("T3 a bogus-verb frame never invents a resource (3 resources, RD absent)" as *u8, ok3, ctr) 301 var ok4: i64 = 0 302 if lm_find(d, dl, "\"resource\":\"RA\",\"claims\":2,\"releases\":2,\"orphaned\":0,\"actors\":1,\"avg_hold_s\":200,\"max_hold_s\":300" as *u8) == 1 { ok4 = 1 } 303 gv_check("T4 matched pairs: 0 orphans, hold-time avg 200 max 300 EXACT" as *u8, ok4, ctr) 304 var ok5: i64 = 0 305 if lm_find(d, dl, "\"resource\":\"RB\",\"claims\":2,\"releases\":1,\"orphaned\":1,\"actors\":2" as *u8) == 1 { ok5 = 1 } 306 gv_check("T5 CONTENDED resource: 2 distinct actors and 1 unreleased claim" as *u8, ok5, ctr) 307 var ok6: i64 = 0 308 if lm_find(d, dl, "\"resource\":\"RC\",\"claims\":1,\"releases\":0,\"orphaned\":1,\"actors\":1,\"avg_hold_s\":-1" as *u8) == 1 { ok6 = 1 } 309 gv_check("T6 never-released claim is an ORPHAN and its hold-time REFUSES (-1), never 0" as *u8, ok6, ctr) 310 var ok7: i64 = 0 311 if lm_find(d, dl, "\"orphaned_resources\":2,\"contended_resources\":1" as *u8) == 1 { if lm_find(d, dl, "\"release_permil\":600" as *u8) == 1 { ok7 = 1 } } 312 gv_check("T7 totals exact: 2 orphaned, only 1 contended, release_permil 600" as *u8, ok7, ctr) 313 let dl8: i64 = lm_mine_json(fx, 2, now, d) 314 var ok8: i64 = 0 315 if dl8 > 0 { if lm_find(d, dl8, "\"resource\":\"RC\"" as *u8) == 0 { if lm_find(d, dl8, "\"resources_below_minsup\":1" as *u8) == 1 { ok8 = 1 } } } 316 gv_check("T8 minsup 2 suppresses the single-claim resource and DECLARES it" as *u8, ok8, ctr) 317 let rc: i64 = gv_verdict("LEASEMINE-GATE" as *u8, ctr, "lease mining: orphans and contention derived from history, hold-time exact, refusal over zero" as *u8) 318 return rc 319} 320 321func main(argc: i64, argv: *i64) -> i64 { 322 if argc < 3 { sj_puts("usage: nx_leasemine {mine <journal> [minsup] [now] | selftest <scratch-prefix>}\n" as *u8); sys_exit(2); return 2 } 323 let verb: *u8 = argv[1] as *u8 324 let vl: i64 = sj_vlen(verb) 325 if sj_lit_eq(verb, 0, vl, "selftest" as *u8) == 1 { 326 let rc: i64 = lm_selftest(argv[2] as *u8) 327 sys_exit(rc) 328 return rc 329 } 330 if sj_lit_eq(verb, 0, vl, "mine" as *u8) == 1 { 331 var minsup: i64 = 1 332 if argc >= 4 { let mz: i64 = sj_atoi_z(argv[3] as *u8); if mz > 0 { minsup = mz } } 333 var now: i64 = sys_now_realtime_sec() 334 if argc >= 5 { let nz: i64 = sj_atoi_z(argv[4] as *u8); if nz > 0 { now = nz } } 335 let d: *u8 = sys_mmap(LM_OUT) 336 let dl: i64 = lm_mine_json(argv[2] as *u8, minsup, now, d) 337 if dl < 0 { sj_puts("REFUSED journal missing or empty\n" as *u8); sys_exit(3); return 3 } 338 sys_write(1, d, dl) 339 sys_exit(0) 340 return 0 341 } 342 sj_puts("unknown verb\n" as *u8) 343 sys_exit(2) 344 return 2 345}