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}