code wiki / _hdl_build / nx_claims.nx

nx_claims.nx source

↩ module page · 836 lines · 34455 B

1// nx_claims.nx -- SESSION-CLAIMS AS AN ECOSYSTEM CAPABILITY (ws=ws-intel2, 2026-07-20). 2// Eats debt 1784408445 (claims protocol shell-only via WSL) + feeds 1784408708 (claims hub 3// to NAS = standup claim-holder liveness): claim/beat/release/walk become MCP tools/call 4// verbs on an append-only NAS journal -- peers of nx_ws_sync (journal) / nx_ws_cycle (phase). 5// HOLDERSHIP IS DERIVED, NEVER STORED (evidence law): latest CLAIM not RELEASEd by its 6// holder = the claim; freshness = latest CLAIM/BEAT ts by that holder vs its TTL -> 7// HELD | EXPIRED (crash-expiry BY CONSTRUCTION -- a dead session's claim self-expires, 8// the F214 lease semantics) | FREE (released) | ABSENT. Claim on a contended resource is 9// REFUSED fail-closed (exit 3) -- never silently stolen; an EXPIRED claim is claimable. 10// Frame: <ts>\t<VERB>\t<resource>\t<actor>\t<ttl>\t<note> VERB in {CLAIM, BEAT, RELEASE} 11// SCALE LAW: windowed reads + declared envelope (window_bytes/truncated/claims_capped). 12// license_tier: ORIGINAL No hw writes (Rule 26). 13// claim <journal> <resource> <actor> <ttl_sec> <note> -> HELD | CONTENDED(3) 14// beat <journal> <resource> <actor> <note> -> BEAT | NOT-HOLDER(3) 15// release <journal> <resource> <actor> <note> -> RELEASED | NOT-HOLDER(3) 16// state <journal> <resource> -> ONE-LINE probe: state=HELD|EXPIRED|FREE|ABSENT holder=.. 17// (seq543/seq552: lets nx_seat checkout classify NOT-HOLDER honestly; exit 0 always) 18// walk <journal> -> JSON all resources 19// reap <journal> <reaper> <note> -> F214: release ONLY provably-EXPIRED claims 20// (age>ttl by the claim's OWN ttl field) + ORPHAN-REAPED audit note; idempotent; exit 0 21// selftest <scratch-journal> -> gate T1..T8 (caller pre-cleans) 22import "nx_syscalls.nx" 23import "nx_gate_verdict.nx" 24const CL_MAGIC_4096: i64 = 4096 25const CL_MAGIC_2000: i64 = 2000 26const CL_MAGIC_200000: i64 = 200000 27const CL_MAGIC_5000: i64 = 5000 28 29const CL_WIN: i64 = 4194304 30const CL_OUT: i64 = 262144 31const CL_SOFT: i64 = 49152 32const CL_NOTE_MAX: i64 = 200 33const CL_NAME_MAX: i64 = 120 34 35func cl_cat(d: *u8, o: i64, s: *u8) -> i64 { var p: i64=o; var i: i64=0; while s[i]!=(0 as u8){ d[p]=s[i]; p=p+1; i=i+1 } return p } 36func cl_catn(d: *u8, o: i64, v: i64) -> i64 { 37 let t: *u8 = sys_mmap(28) 38 var m: i64 = v 39 var p: i64 = o 40 if m < 0 { d[p]=45 as u8; p=p+1; m=0-m } 41 var k: i64 = 0 42 if m == 0 { t[0]=48 as u8; k=1 } 43 while m > 0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 44 var i: i64 = 0 45 while i < k { d[p]=t[k-1-i]; p=p+1; i=i+1 } 46 return p 47} 48func cl_vlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 49func cl_atoi_z(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while s[i]!=(0 as u8){ let c: i64=s[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48) } } i=i+1 } return v } 50func cl_read(path: *u8, buf: *u8, cap: i64) -> i64 { 51 let fd: i64 = sys_openat_rd(path) 52 if fd < 0 { return 0 } 53 var n: i64 = 0 54 var go: i64 = 1 55 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 } } 56 sys_close(fd) 57 return n 58} 59// append one frame: <ts>\t<verb>\t<resource>\t<actor>\t<ttl>\t<note>\n (ts<0 => real clock; note flattened) 60func cl_append(journal: *u8, ts: i64, verb: *u8, res: *u8, actor: *u8, ttl: i64, note: *u8) -> i64 { 61 var t: i64 = ts 62 if t < 0 { t = sys_now_realtime_sec() } 63 let ln: *u8 = sys_mmap(CL_MAGIC_4096) 64 var o: i64 = 0 65 o = cl_catn(ln, o, t) 66 ln[o]=9 as u8; o=o+1 67 o = cl_cat(ln, o, verb) 68 ln[o]=9 as u8; o=o+1 69 o = cl_cat(ln, o, res) 70 ln[o]=9 as u8; o=o+1 71 o = cl_cat(ln, o, actor) 72 ln[o]=9 as u8; o=o+1 73 o = cl_catn(ln, o, ttl) 74 ln[o]=9 as u8; o=o+1 75 var i: i64 = 0 76 var used: i64 = 0 77 while note[i]!=(0 as u8) { 78 if used < CL_MAGIC_2000 { 79 let c: i64 = note[i] as i64 80 if c==9 { ln[o]=32 as u8; o=o+1 } else { if c==10 { ln[o]=32 as u8; o=o+1 } else { if c==13 { ln[o]=32 as u8; o=o+1 } else { ln[o]=c as u8; o=o+1 } } } 81 used = used + 1 82 } 83 i = i + 1 84 } 85 ln[o]=10 as u8; o=o+1 86 let fd: i64 = sys_openat_append(journal, 0x1a4) 87 if fd < 0 { return -1 } 88 sys_write(fd, ln, o) 89 sys_close(fd) 90 return 0 91} 92func cl_le(q: *u8, i: i64, n: i64) -> i64 { 93 var le: i64 = i 94 var s: i64 = 1 95 while s==1 { if le>=n { s=0 } else { if q[le]==(10 as u8){s=0} else {le=le+1} } } 96 return le 97} 98func cl_col(q: *u8, ls: i64, le: i64, c: i64, out: *i64) -> i64 { 99 var col: i64 = 0 100 var p: i64 = ls 101 while col < c { 102 var s: i64 = 1 103 while s == 1 { if p >= le { return 0 } if q[p]==(9 as u8) { s = 0 } else { p = p+1 } } 104 p = p + 1 105 col = col + 1 106 } 107 var e: i64 = p 108 var s2: i64 = 1 109 while s2 == 1 { if e >= le { s2 = 0 } else { if q[e]==(9 as u8) { s2 = 0 } else { e = e+1 } } } 110 out[0] = p 111 out[1] = e 112 return 1 113} 114func cl_lit_eq(q: *u8, s: i64, e: i64, lit: *u8) -> i64 { 115 var i: i64 = 0 116 while s+i < e { if lit[i]==(0 as u8) { return 0 } if q[s+i]!=lit[i] { return 0 } i=i+1 } 117 if lit[i]!=(0 as u8) { return 0 } 118 return 1 119} 120func cl_span_eq(q: *u8, s1: i64, e1: i64, s2: i64, e2: i64) -> i64 { 121 if e1-s1 != e2-s2 { return 0 } 122 var i: i64 = 0 123 while s1+i < e1 { if q[s1+i]!=q[s2+i] { return 0 } i=i+1 } 124 return 1 125} 126func cl_atoi(q: *u8, s: i64, e: i64) -> i64 { 127 var v: i64 = 0 128 var i: i64 = s 129 while i < e { let c: i64 = q[i] as i64; if c>=48 { if c<=57 { v = v*10 + (c-48) } } i=i+1 } 130 return v 131} 132// DERIVED claim state for a resource span. Returns -1 ABSENT / 1 HELD / 2 EXPIRED / 3 FREE. 133// out[0..1]=holder span, out[2]=ttl, out[3]=last_activity_ts (valid when state 1|2). 134func cl_state(q: *u8, cx: *i64, res_s: i64, res_e: i64, out: *i64) -> i64 { 135 let n: i64 = cx[0] 136 let cv: *i64 = sys_mmap(16) as *i64 137 let cr: *i64 = sys_mmap(16) as *i64 138 let ca: *i64 = sys_mmap(16) as *i64 139 let ct: *i64 = sys_mmap(16) as *i64 140 let c0: *i64 = sys_mmap(16) as *i64 141 var mode: i64 = 0 142 var ever: i64 = 0 143 var hs: i64 = -1 144 var he: i64 = -1 145 var ttl: i64 = 0 146 var last: i64 = 0 147 var i: i64 = 0 148 while i < n { 149 let le: i64 = cl_le(q,i,n) 150 if cl_col(q,i,le,2,cr)==1 { if cl_span_eq(q,cr[0],cr[1],res_s,res_e)==1 { 151 if cl_col(q,i,le,1,cv)==1 { if cl_col(q,i,le,0,c0)==1 { if cl_col(q,i,le,3,ca)==1 { 152 let ts: i64 = cl_atoi(q,c0[0],c0[1]) 153 if cl_lit_eq(q,cv[0],cv[1],"CLAIM" as *u8)==1 { 154 mode=1 155 ever=1 156 hs=ca[0] 157 he=ca[1] 158 last=ts 159 if cl_col(q,i,le,4,ct)==1 { ttl = cl_atoi(q,ct[0],ct[1]) } 160 } 161 if cl_lit_eq(q,cv[0],cv[1],"BEAT" as *u8)==1 { 162 if mode==1 { if cl_span_eq(q,ca[0],ca[1],hs,he)==1 { last=ts } } 163 } 164 if cl_lit_eq(q,cv[0],cv[1],"RELEASE" as *u8)==1 { 165 if mode==1 { if cl_span_eq(q,ca[0],ca[1],hs,he)==1 { mode=0 } } 166 } 167 } } } 168 } } 169 i = le + 1 170 } 171 if ever==0 { return -1 } 172 if mode==0 { return 3 } 173 out[0]=hs 174 out[1]=he 175 out[2]=ttl 176 out[3]=last 177 let age: i64 = cx[1] - last 178 if age > ttl { return 2 } 179 return 1 180} 181func cl_state_name(st: i64) -> *u8 { 182 if st==1 { return "HELD" as *u8 } 183 if st==2 { return "EXPIRED" as *u8 } 184 if st==3 { return "FREE" as *u8 } 185 return "ABSENT" as *u8 186} 187// resource span first occurrence? (walk dedupe) 188func cl_res_seen(q: *u8, upto: i64, r_s: i64, r_e: i64) -> i64 { 189 let cr: *i64 = sys_mmap(16) as *i64 190 var i: i64 = 0 191 while i < upto { 192 let le: i64 = cl_le(q,i,upto) 193 if cl_col(q,i,le,2,cr)==1 { if cl_span_eq(q,cr[0],cr[1],r_s,r_e)==1 { return 1 } } 194 i = le + 1 195 } 196 return 0 197} 198// find span of a resource by literal name; 1 if any frame carries it 199func cl_res_span_lit(q: *u8, cx: *i64, res: *u8, out: *i64) -> i64 { 200 let n: i64 = cx[0] 201 let cr: *i64 = sys_mmap(16) as *i64 202 var i: i64 = 0 203 while i < n { 204 let le: i64 = cl_le(q,i,n) 205 if cl_col(q,i,le,2,cr)==1 { if cl_lit_eq(q,cr[0],cr[1],res)==1 { out[0]=cr[0]; out[1]=cr[1]; return 1 } } 206 i = le + 1 207 } 208 return 0 209} 210// last note (col5) for a resource 211func cl_note_span(q: *u8, cx: *i64, res_s: i64, res_e: i64, out: *i64) -> i64 { 212 let n: i64 = cx[0] 213 let cr: *i64 = sys_mmap(16) as *i64 214 let cn: *i64 = sys_mmap(16) as *i64 215 var ns: i64 = -1 216 var ne: i64 = -1 217 var i: i64 = 0 218 while i < n { 219 let le: i64 = cl_le(q,i,n) 220 if cl_col(q,i,le,2,cr)==1 { if cl_span_eq(q,cr[0],cr[1],res_s,res_e)==1 { 221 if cl_col(q,i,le,5,cn)==1 { ns=cn[0]; ne=cn[1] } 222 } } 223 i = le + 1 224 } 225 if ns >= 0 { out[0]=ns; out[1]=ne; return 1 } 226 return 0 227} 228func cl_jesc(d: *u8, o: i64, q: *u8, s: i64, e: i64, maxb: i64) -> i64 { 229 var p: i64 = o 230 var i: i64 = s 231 var used: i64 = 0 232 while i < e { 233 if used >= maxb { i = e } else { 234 let c: i64 = q[i] as i64 235 if c == 34 { d[p]=92 as u8; p=p+1; d[p]=34 as u8; p=p+1 } else { if c == 92 { d[p]=92 as u8; p=p+1; d[p]=92 as u8; p=p+1 } else { if c < 32 { d[p]=32 as u8; p=p+1 } else { d[p]=c as u8; p=p+1 } } } 236 used = used + 2 237 i = i + 1 238 } 239 } 240 return p 241} 242func cl_key(d: *u8, o: i64, name: *u8) -> i64 { 243 d[o]=34 as u8 244 var p: i64 = o + 1 245 p = cl_cat(d,p,name) 246 d[p]=34 as u8 247 p = p + 1 248 d[p]=58 as u8 249 p = p + 1 250 return p 251} 252func cl_qlit(d: *u8, o: i64, s: *u8) -> i64 { 253 var p: i64 = o 254 d[p]=34 as u8; p=p+1 255 p = cl_cat(d,p,s) 256 d[p]=34 as u8; p=p+1 257 return p 258} 259func cl_emit_res(d: *u8, o: i64, q: *u8, cx: *i64, r_s: i64, r_e: i64) -> i64 { 260 var p: i64 = o 261 let st_out: *i64 = sys_mmap(32) as *i64 262 let no: *i64 = sys_mmap(16) as *i64 263 d[p]=123 as u8; p=p+1 264 p = cl_key(d,p,"resource" as *u8) 265 d[p]=34 as u8; p=p+1 266 p = cl_jesc(d,p,q,r_s,r_e,CL_NAME_MAX) 267 d[p]=34 as u8; p=p+1 268 d[p]=44 as u8; p=p+1 269 let st: i64 = cl_state(q,cx,r_s,r_e,st_out) 270 p = cl_key(d,p,"state" as *u8) 271 let sn: *u8 = cl_state_name(st) 272 p = cl_qlit(d,p,sn) 273 d[p]=44 as u8; p=p+1 274 p = cl_key(d,p,"holder" as *u8) 275 d[p]=34 as u8; p=p+1 276 if st==1 { p = cl_jesc(d,p,q,st_out[0],st_out[1],CL_NAME_MAX) } 277 if st==2 { p = cl_jesc(d,p,q,st_out[0],st_out[1],CL_NAME_MAX) } 278 d[p]=34 as u8; p=p+1 279 d[p]=44 as u8; p=p+1 280 p = cl_key(d,p,"ttl_sec" as *u8) 281 var tv: i64 = -1 282 if st==1 { tv = st_out[2] } 283 if st==2 { tv = st_out[2] } 284 p = cl_catn(d,p,tv) 285 d[p]=44 as u8; p=p+1 286 p = cl_key(d,p,"age_sec" as *u8) 287 var age: i64 = -1 288 if st==1 { age = cx[1] - st_out[3] } 289 if st==2 { age = cx[1] - st_out[3] } 290 p = cl_catn(d,p,age) 291 d[p]=44 as u8; p=p+1 292 p = cl_key(d,p,"last_note" as *u8) 293 d[p]=34 as u8; p=p+1 294 let hn: i64 = cl_note_span(q,cx,r_s,r_e,no) 295 if hn==1 { p = cl_jesc(d,p,q,no[0],no[1],CL_NOTE_MAX) } 296 d[p]=34 as u8; p=p+1 297 d[p]=125 as u8; p=p+1 298 return p 299} 300func cl_walk_json(q: *u8, cx: *i64, d: *u8) -> i64 { 301 let n: i64 = cx[0] 302 var p: i64 = 0 303 d[p]=123 as u8; p=p+1 304 p = cl_key(d,p,"v" as *u8) 305 p = cl_catn(d,p,1) 306 d[p]=44 as u8; p=p+1 307 p = cl_key(d,p,"tool" as *u8) 308 p = cl_qlit(d,p,"nx_claims" as *u8) 309 d[p]=44 as u8; p=p+1 310 p = cl_key(d,p,"journal" as *u8) 311 d[p]=34 as u8; p=p+1 312 let jp: *u8 = cx[5] as *u8 313 let jl: i64 = cl_vlen(jp) 314 p = cl_jesc(d,p,jp,0,jl,200) 315 d[p]=34 as u8; p=p+1 316 d[p]=44 as u8; p=p+1 317 p = cl_key(d,p,"journal_bytes" as *u8) 318 p = cl_catn(d,p,n) 319 d[p]=44 as u8; p=p+1 320 p = cl_key(d,p,"window_bytes" as *u8) 321 p = cl_catn(d,p,CL_WIN) 322 d[p]=44 as u8; p=p+1 323 p = cl_key(d,p,"truncated" as *u8) 324 p = cl_catn(d,p,cx[4]) 325 d[p]=44 as u8; p=p+1 326 p = cl_key(d,p,"now" as *u8) 327 p = cl_catn(d,p,cx[1]) 328 d[p]=44 as u8; p=p+1 329 // totals pass 330 let cr: *i64 = sys_mmap(16) as *i64 331 let st_out: *i64 = sys_mmap(32) as *i64 332 var theld: i64 = 0 333 var texp: i64 = 0 334 var tfree: i64 = 0 335 var i: i64 = 0 336 while i < n { 337 let le: i64 = cl_le(q,i,n) 338 if cl_col(q,i,le,2,cr)==1 { if cl_res_seen(q,i,cr[0],cr[1])==0 { 339 let st: i64 = cl_state(q,cx,cr[0],cr[1],st_out) 340 if st==1 { theld=theld+1 } 341 if st==2 { texp=texp+1 } 342 if st==3 { tfree=tfree+1 } 343 } } 344 i = le + 1 345 } 346 p = cl_key(d,p,"totals" as *u8) 347 d[p]=123 as u8; p=p+1 348 p = cl_key(d,p,"held" as *u8) 349 p = cl_catn(d,p,theld) 350 d[p]=44 as u8; p=p+1 351 p = cl_key(d,p,"expired" as *u8) 352 p = cl_catn(d,p,texp) 353 d[p]=44 as u8; p=p+1 354 p = cl_key(d,p,"free" as *u8) 355 p = cl_catn(d,p,tfree) 356 d[p]=125 as u8; p=p+1 357 d[p]=44 as u8; p=p+1 358 p = cl_key(d,p,"resources" as *u8) 359 d[p]=91 as u8; p=p+1 360 var capped: i64 = 0 361 var first: i64 = 1 362 var j: i64 = 0 363 while j < n { 364 let je: i64 = cl_le(q,j,n) 365 if cl_col(q,j,je,2,cr)==1 { if cl_res_seen(q,j,cr[0],cr[1])==0 { 366 if p > CL_SOFT { capped=1 } else { 367 if first==0 { d[p]=44 as u8; p=p+1 } 368 first=0 369 p = cl_emit_res(d,p,q,cx,cr[0],cr[1]) 370 } 371 } } 372 j = je + 1 373 } 374 d[p]=93 as u8; p=p+1 375 d[p]=44 as u8; p=p+1 376 p = cl_key(d,p,"claims_capped" as *u8) 377 p = cl_catn(d,p,capped) 378 d[p]=125 as u8; p=p+1 379 d[p]=10 as u8; p=p+1 380 return p 381} 382func cl_hesc(d: *u8, o: i64, q: *u8, s: i64, e: i64, maxb: i64) -> i64 { 383 var p: i64 = o 384 var i: i64 = s 385 var used: i64 = 0 386 while i < e { 387 if used >= maxb { i = e } else { 388 let c: i64 = q[i] as i64 389 if c == 60 { p = cl_cat(d,p,"&lt;" as *u8) } else { if c == 62 { p = cl_cat(d,p,"&gt;" as *u8) } else { if c == 38 { p = cl_cat(d,p,"&amp;" as *u8) } else { if c < 32 { d[p]=32 as u8; p=p+1 } else { d[p]=c as u8; p=p+1 } } } } 390 used = used + 5 391 i = i + 1 392 } 393 } 394 return p 395} 396func cl_page_html(q: *u8, cx: *i64, d: *u8) -> i64 { 397 let n: i64 = cx[0] 398 var p: i64 = 0 399 p = cl_cat(d,p,"<" as *u8) 400 d[p]=33 as u8 401 p = p + 1 402 p = cl_cat(d,p,"doctype html><html><head><meta charset=utf-8><title>Nishi Claims</title></head><body>" as *u8) 403 p = cl_cat(d,p,"<h1>Nishi Claims -- who holds what (TTL crash-expiry, derived never asserted)</h1>" as *u8) 404 let cr: *i64 = sys_mmap(16) as *i64 405 let st_out: *i64 = sys_mmap(32) as *i64 406 let no: *i64 = sys_mmap(16) as *i64 407 var theld: i64 = 0 408 var texp: i64 = 0 409 var tfree: i64 = 0 410 var i: i64 = 0 411 while i < n { 412 let le: i64 = cl_le(q,i,n) 413 if cl_col(q,i,le,2,cr)==1 { if cl_res_seen(q,i,cr[0],cr[1])==0 { 414 let st: i64 = cl_state(q,cx,cr[0],cr[1],st_out) 415 if st==1 { theld=theld+1 } 416 if st==2 { texp=texp+1 } 417 if st==3 { tfree=tfree+1 } 418 } } 419 i = le + 1 420 } 421 p = cl_cat(d,p,"<p>held=" as *u8) 422 p = cl_catn(d,p,theld) 423 p = cl_cat(d,p," expired=" as *u8) 424 p = cl_catn(d,p,texp) 425 p = cl_cat(d,p," free=" as *u8) 426 p = cl_catn(d,p,tfree) 427 p = cl_cat(d,p,"</p>" as *u8) 428 p = cl_cat(d,p,"<table border=1><tr><th>resource</th><th>state</th><th>holder</th><th>ttl-sec</th><th>age-sec</th><th>last note</th></tr>" as *u8) 429 var truncated_rows: i64 = 0 430 var j: i64 = 0 431 while j < n { 432 let je: i64 = cl_le(q,j,n) 433 if cl_col(q,j,je,2,cr)==1 { if cl_res_seen(q,j,cr[0],cr[1])==0 { 434 if p > CL_MAGIC_200000 { truncated_rows = truncated_rows + 1 } else { 435 p = cl_cat(d,p,"<tr><td>" as *u8) 436 p = cl_hesc(d,p,q,cr[0],cr[1],CL_NAME_MAX) 437 p = cl_cat(d,p,"</td><td>" as *u8) 438 let st2: i64 = cl_state(q,cx,cr[0],cr[1],st_out) 439 let sn: *u8 = cl_state_name(st2) 440 p = cl_cat(d,p,sn) 441 p = cl_cat(d,p,"</td><td>" as *u8) 442 if st2==1 { p = cl_hesc(d,p,q,st_out[0],st_out[1],CL_NAME_MAX) } 443 if st2==2 { p = cl_hesc(d,p,q,st_out[0],st_out[1],CL_NAME_MAX) } 444 p = cl_cat(d,p,"</td><td>" as *u8) 445 var tv: i64 = -1 446 if st2==1 { tv = st_out[2] } 447 if st2==2 { tv = st_out[2] } 448 p = cl_catn(d,p,tv) 449 p = cl_cat(d,p,"</td><td>" as *u8) 450 var age: i64 = -1 451 if st2==1 { age = cx[1] - st_out[3] } 452 if st2==2 { age = cx[1] - st_out[3] } 453 p = cl_catn(d,p,age) 454 p = cl_cat(d,p,"</td><td>" as *u8) 455 let hn: i64 = cl_note_span(q,cx,cr[0],cr[1],no) 456 if hn==1 { p = cl_hesc(d,p,q,no[0],no[1],CL_NOTE_MAX) } 457 p = cl_cat(d,p,"</td></tr>" as *u8) 458 } 459 } } 460 j = je + 1 461 } 462 p = cl_cat(d,p,"</table>" as *u8) 463 if truncated_rows > 0 { 464 p = cl_cat(d,p,"<p>ENVELOPE: rows_truncated=" as *u8) 465 p = cl_catn(d,p,truncated_rows) 466 p = cl_cat(d,p,"</p>" as *u8) 467 } 468 p = cl_cat(d,p,"<p>envelope window_bytes=" as *u8) 469 p = cl_catn(d,p,CL_WIN) 470 p = cl_cat(d,p," journal_bytes=" as *u8) 471 p = cl_catn(d,p,n) 472 p = cl_cat(d,p," generated=" as *u8) 473 p = cl_catn(d,p,cx[1]) 474 p = cl_cat(d,p," | source=knowledge/status/claims.jrnl | tool=nx_claims (MCP) | <a href=/standup>standup</a> <a href=/wsmine>wsmine</a> <a href=/frontier>frontier</a></p>" as *u8) 475 p = cl_cat(d,p,"</body></html>" as *u8) 476 d[p]=10 as u8 477 p = p + 1 478 return p 479} 480func cl_find(d: *u8, dn: i64, lit: *u8) -> i64 { 481 let ll: i64 = cl_vlen(lit) 482 if ll == 0 { return 0 } 483 var i: i64 = 0 484 while i + ll <= dn { 485 var k: i64 = 0 486 var ok: i64 = 1 487 while k < ll { if d[i+k]!=lit[k] { ok=0; k=ll } else { k=k+1 } } 488 if ok==1 { return 1 } 489 i = i + 1 490 } 491 return 0 492} 493 494// F214 ORPHAN REAP -- fail-safe BY CONSTRUCTION: derive+append on ONE snapshot; release ONLY 495// claims whose state derives EXPIRED (age>ttl by the claim's OWN ttl field). NEVER by name, 496// NEVER by looks-stale, no special cases. Additive-only (RELEASE frames appended, history kept). 497// Idempotent: post-reap state=FREE so a re-run appends NOTHING. Audit=who/when/why in the note. 498func cl_span_z(q: *u8, s: i64, e: i64, d: *u8, cap: i64) -> i64 { 499 if e - s >= cap { return 0 } 500 var i: i64 = 0 501 while s + i < e { d[i] = q[s+i]; i = i + 1 } 502 d[i] = 0 as u8 503 return 1 504} 505func cl_reap(journal: *u8, q: *u8, cx: *i64, reaper: *u8, note: *u8) -> i64 { 506 let n: i64 = cx[0] 507 let cr: *i64 = sys_mmap(16) as *i64 508 let so: *i64 = sys_mmap(32) as *i64 509 let rname: *u8 = sys_mmap(512) 510 let hname: *u8 = sys_mmap(512) 511 let rnote: *u8 = sys_mmap(CL_MAGIC_4096) 512 var reaped: i64 = 0 513 var scanned: i64 = 0 514 var i: i64 = 0 515 while i < n { 516 let le: i64 = cl_le(q,i,n) 517 if cl_col(q,i,le,2,cr)==1 { if cl_res_seen(q,i,cr[0],cr[1])==0 { 518 scanned = scanned + 1 519 let st: i64 = cl_state(q,cx,cr[0],cr[1],so) 520 if st==2 { 521 let okr: i64 = cl_span_z(q,cr[0],cr[1],rname,500) 522 let okh: i64 = cl_span_z(q,so[0],so[1],hname,500) 523 if okr==1 { if okh==1 { 524 let age: i64 = cx[1] - so[3] 525 var o: i64 = 0 526 o = cl_cat(rnote,0,"ORPHAN-REAPED by=" as *u8) 527 o = cl_cat(rnote,o,reaper) 528 o = cl_cat(rnote,o," expired age=" as *u8) 529 o = cl_catn(rnote,o,age) 530 o = cl_cat(rnote,o,"s ttl=" as *u8) 531 o = cl_catn(rnote,o,so[2]) 532 o = cl_cat(rnote,o,"s " as *u8) 533 o = cl_cat(rnote,o,note) 534 rnote[o] = 0 as u8 535 let arc: i64 = cl_append(journal,-1,"RELEASE" as *u8,rname,hname,0,rnote) 536 if arc==0 { 537 reaped = reaped + 1 538 gv_puts("NX-CLAIMS REAPED resource=" as *u8) 539 gv_puts(rname) 540 gv_puts(" holder=" as *u8) 541 gv_puts(hname) 542 gv_puts(" age=" as *u8) 543 gv_num(age) 544 gv_puts(" ttl=" as *u8) 545 gv_num(so[2]) 546 gv_puts("\n" as *u8) 547 } else { 548 gv_puts("NX-CLAIMS REAP-APPEND-FAIL resource=" as *u8) 549 gv_puts(rname) 550 gv_puts("\n" as *u8) 551 } 552 } } 553 if okr==0 { gv_puts("NX-CLAIMS REAP-SKIP resource-name-too-long\n" as *u8) } else { if okh==0 { gv_puts("NX-CLAIMS REAP-SKIP holder-too-long\n" as *u8) } } 554 } 555 } } 556 i = le + 1 557 } 558 gv_puts("NX-CLAIMS REAP-DONE reaped=" as *u8) 559 gv_num(reaped) 560 gv_puts(" scanned=" as *u8) 561 gv_num(scanned) 562 gv_puts(" now=" as *u8) 563 gv_num(cx[1]) 564 gv_puts("\n" as *u8) 565 return reaped 566} 567 568// ---------- selftest gate (T1..T13, deterministic via injected timestamps) ---------- 569func cl_selftest(journal: *u8) -> i64 { 570 let ctr: *i64 = gv_ctr() 571 gv_head("nx_claims selftest -- lease/claim teeth (caller pre-cleans journal)" as *u8) 572 let q: *u8 = sys_mmap(CL_WIN) 573 let cx: *i64 = sys_mmap(64) as *i64 574 let sp: *i64 = sys_mmap(16) as *i64 575 let st_out: *i64 = sys_mmap(32) as *i64 576 let now: i64 = sys_now_realtime_sec() 577 let rcap: i64 = CL_WIN - 8 578 // T1 empty journal -> absent resource honestly ABSENT 579 var n: i64 = cl_read(journal,q,rcap) 580 cx[0]=n; cx[1]=now; cx[2]=0; cx[3]=0; cx[4]=0; cx[5]=journal as i64 581 var ok1: i64 = 0 582 if n==0 { ok1=1 } 583 gv_check("T1 empty journal no fabricated claims" as *u8, ok1, ctr) 584 // T2 fresh claim -> HELD by claimer 585 let t2ts: i64 = now - 100 586 cl_append(journal, t2ts, "CLAIM" as *u8, "resA" as *u8, "sess1" as *u8, 1000, "claimA" as *u8) 587 n = cl_read(journal,q,rcap) 588 cx[0]=n 589 var ok2: i64 = 0 590 if cl_res_span_lit(q,cx,"resA" as *u8,sp)==1 { 591 let st2: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 592 if st2==1 { if cl_lit_eq(q,st_out[0],st_out[1],"sess1" as *u8)==1 { ok2=1 } } 593 } 594 gv_check("T2 fresh claim derives HELD by claimer" as *u8, ok2, ctr) 595 // T3 contender predicate: HELD + holder != sess2 -> claim verb would refuse 596 var ok3: i64 = 0 597 if cl_res_span_lit(q,cx,"resA" as *u8,sp)==1 { 598 let st3: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 599 if st3==1 { if cl_lit_eq(q,st_out[0],st_out[1],"sess2" as *u8)==0 { ok3=1 } } 600 } 601 gv_check("T3 contended claim predicate refuses other actor" as *u8, ok3, ctr) 602 // T4 beat by holder revives freshness: old claim + fresh beat -> HELD 603 let t4c: i64 = now - CL_MAGIC_5000 604 let t4b: i64 = now - 50 605 cl_append(journal, t4c, "CLAIM" as *u8, "resB" as *u8, "sess1" as *u8, 1000, "claimB" as *u8) 606 cl_append(journal, t4b, "BEAT" as *u8, "resB" as *u8, "sess1" as *u8, 0, "beatB" as *u8) 607 n = cl_read(journal,q,rcap) 608 cx[0]=n 609 var ok4: i64 = 0 610 if cl_res_span_lit(q,cx,"resB" as *u8,sp)==1 { let st4: i64 = cl_state(q,cx,sp[0],sp[1],st_out); if st4==1 { ok4=1 } } 611 gv_check("T4 holder beat revives freshness HELD" as *u8, ok4, ctr) 612 // T5 stale claim without beats -> EXPIRED (crash-expiry) 613 let t5c: i64 = now - CL_MAGIC_5000 614 cl_append(journal, t5c, "CLAIM" as *u8, "resC" as *u8, "sess3" as *u8, 1000, "claimC" as *u8) 615 n = cl_read(journal,q,rcap) 616 cx[0]=n 617 var ok5: i64 = 0 618 if cl_res_span_lit(q,cx,"resC" as *u8,sp)==1 { let st5: i64 = cl_state(q,cx,sp[0],sp[1],st_out); if st5==2 { ok5=1 } } 619 gv_check("T5 stale claim derives EXPIRED crash-expiry" as *u8, ok5, ctr) 620 // T6 release -> FREE, then re-claim by other -> HELD by new holder 621 let t6c: i64 = now - 300 622 let t6r: i64 = now - 200 623 let t6c2: i64 = now - 100 624 cl_append(journal, t6c, "CLAIM" as *u8, "resD" as *u8, "sess1" as *u8, 1000, "claimD" as *u8) 625 cl_append(journal, t6r, "RELEASE" as *u8, "resD" as *u8, "sess1" as *u8, 0, "releaseD" as *u8) 626 n = cl_read(journal,q,rcap) 627 cx[0]=n 628 var ok6a: i64 = 0 629 if cl_res_span_lit(q,cx,"resD" as *u8,sp)==1 { let st6: i64 = cl_state(q,cx,sp[0],sp[1],st_out); if st6==3 { ok6a=1 } } 630 cl_append(journal, t6c2, "CLAIM" as *u8, "resD" as *u8, "sess2" as *u8, 1000, "reclaimD" as *u8) 631 n = cl_read(journal,q,rcap) 632 cx[0]=n 633 var ok6: i64 = 0 634 if ok6a==1 { if cl_res_span_lit(q,cx,"resD" as *u8,sp)==1 { 635 let st6b: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 636 if st6b==1 { if cl_lit_eq(q,st_out[0],st_out[1],"sess2" as *u8)==1 { ok6=1 } } 637 } } 638 gv_check("T6 release frees then other actor claims" as *u8, ok6, ctr) 639 // T7 neg-control: absent resource stays absent 640 var ok7: i64 = 0 641 if cl_res_span_lit(q,cx,"resNOPE" as *u8,sp)==0 { ok7=1 } 642 gv_check("T7 neg-control absent resource stays absent" as *u8, ok7, ctr) 643 // T8 walk JSON smoke: totals + states + envelope 644 let d: *u8 = sys_mmap(CL_OUT) 645 let dl: i64 = cl_walk_json(q,cx,d) 646 var ok8: i64 = 0 647 if dl > 0 { if d[0]==(123 as u8) { if cl_find(d,dl,"EXPIRED" as *u8)==1 { if cl_find(d,dl,"claims_capped" as *u8)==1 { if cl_find(d,dl,"held" as *u8)==1 { ok8=1 } } } } } 648 gv_check("T8 walk JSON carries totals plus declared envelope" as *u8, ok8, ctr) 649 // T9 page HTML smoke: table + states + envelope 650 let d2: *u8 = sys_mmap(CL_OUT) 651 let dl9: i64 = cl_page_html(q,cx,d2) 652 var ok9: i64 = 0 653 if dl9 > 0 { if cl_find(d2,dl9,"<table" as *u8)==1 { if cl_find(d2,dl9,"EXPIRED" as *u8)==1 { if cl_find(d2,dl9,"window_bytes" as *u8)==1 { ok9=1 } } } } 654 gv_check("T9 page HTML carries table states envelope" as *u8, ok9, ctr) 655 // T10 F214 reap: ONLY the provably-expired claim (resC age>ttl) is released; audit note lands 656 n = cl_read(journal,q,rcap) 657 cx[0]=n 658 let r10: i64 = cl_reap(journal,q,cx,"reaper1" as *u8,"t10-orphan-sweep" as *u8) 659 n = cl_read(journal,q,rcap) 660 cx[0]=n 661 var ok10: i64 = 0 662 if r10==1 { if cl_res_span_lit(q,cx,"resC" as *u8,sp)==1 { 663 let st10: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 664 if st10==3 { if cl_find(q,n,"ORPHAN-REAPED by=reaper1" as *u8)==1 { ok10=1 } } 665 } } 666 gv_check("T10 reap releases only the expired claim with audit note" as *u8, ok10, ctr) 667 // T11 NEGATIVE: unexpired claims SURVIVE the reap untouched 668 var ok11: i64 = 0 669 if cl_res_span_lit(q,cx,"resA" as *u8,sp)==1 { 670 let st11a: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 671 if st11a==1 { if cl_lit_eq(q,st_out[0],st_out[1],"sess1" as *u8)==1 { 672 if cl_res_span_lit(q,cx,"resD" as *u8,sp)==1 { 673 let st11d: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 674 if st11d==1 { if cl_lit_eq(q,st_out[0],st_out[1],"sess2" as *u8)==1 { ok11=1 } } 675 } 676 } } 677 } 678 gv_check("T11 negative unexpired claims survive reap" as *u8, ok11, ctr) 679 // T12 idempotent: nothing expired -> reap changes NOTHING (0 reaped, journal byte-identical) 680 let nb12: i64 = n 681 let r12: i64 = cl_reap(journal,q,cx,"reaper1" as *u8,"t12-rerun" as *u8) 682 let n12: i64 = cl_read(journal,q,rcap) 683 cx[0]=n12 684 var ok12: i64 = 0 685 if r12==0 { if n12==nb12 { ok12=1 } } 686 gv_check("T12 reap idempotent when nothing expired" as *u8, ok12, ctr) 687 // T13 a reaped resource is claimable by a NEW actor (the unblocked-sibling tooth) 688 cl_append(journal, -1, "CLAIM" as *u8, "resC" as *u8, "sess9" as *u8, 1000, "post-reap-claim" as *u8) 689 let n13: i64 = cl_read(journal,q,rcap) 690 cx[0]=n13 691 var ok13: i64 = 0 692 if cl_res_span_lit(q,cx,"resC" as *u8,sp)==1 { 693 let st13: i64 = cl_state(q,cx,sp[0],sp[1],st_out) 694 if st13==1 { if cl_lit_eq(q,st_out[0],st_out[1],"sess9" as *u8)==1 { ok13=1 } } 695 } 696 gv_check("T13 reaped resource claimable by new actor" as *u8, ok13, ctr) 697 let rc: i64 = gv_verdict("CLAIMS-GATE" as *u8, ctr, "claim lease + F214 orphan-reap teeth green" as *u8) 698 return rc 699} 700 701func main(argc: i64, argv: *i64) -> i64 { 702 if argc < 3 { gv_puts("usage: nx_claims {claim <journal> <resource> <actor> <ttl_sec> <note> | beat <journal> <resource> <actor> <note> | release <journal> <resource> <actor> <note> | walk <journal> | state <journal> <resource> | reap <journal> <reaper> <note> | selftest <journal>}\n" as *u8); sys_exit(2); return 2 } 703 let verb: *u8 = argv[1] as *u8 704 let journal: *u8 = argv[2] as *u8 705 let vl: i64 = cl_vlen(verb) 706 if cl_lit_eq(verb,0,vl,"selftest" as *u8)==1 { let rc: i64 = cl_selftest(journal); sys_exit(rc); return rc } 707 let q: *u8 = sys_mmap(CL_WIN) 708 let rcap: i64 = CL_WIN - 8 709 let n: i64 = cl_read(journal,q,rcap) 710 let cx: *i64 = sys_mmap(64) as *i64 711 cx[0]=n 712 cx[1]=sys_now_realtime_sec() 713 cx[2]=0 714 cx[3]=0 715 cx[4]=0 716 if n >= rcap { cx[4]=1 } 717 cx[5]=journal as i64 718 if cl_lit_eq(verb,0,vl,"walk" as *u8)==1 { 719 let d: *u8 = sys_mmap(CL_OUT) 720 let dl: i64 = cl_walk_json(q,cx,d) 721 sys_write(1,d,dl) 722 sys_exit(0) 723 return 0 724 } 725 if cl_lit_eq(verb,0,vl,"page" as *u8)==1 { 726 if argc < 4 { gv_puts("page needs <journal> <outfile>\n" as *u8); sys_exit(2); return 2 } 727 let outfile: *u8 = argv[3] as *u8 728 let d: *u8 = sys_mmap(CL_OUT) 729 let dl: i64 = cl_page_html(q,cx,d) 730 let fd: i64 = sys_openat_wr(outfile, 0x1a4) 731 if fd < 0 { gv_puts("CLAIMS-PAGE OPEN-FAIL\n" as *u8); sys_exit(1); return 1 } 732 sys_write(fd,d,dl) 733 sys_close(fd) 734 gv_puts("CLAIMS-PAGE bytes=" as *u8) 735 gv_num(dl) 736 gv_puts("\n" as *u8) 737 sys_exit(0) 738 return 0 739 } 740 // seq543/seq552: single-resource STATE probe (additive, rule 19) -- the SAME derivation the 741 // beat/release path uses (cl_res_span_lit + cl_state), so holdership judgment stays one copy. 742 // Lets nx_seat checkout classify a NOT-HOLDER release honestly (already-free vs live-foreign) 743 // without a full walk. Read-only, exit 0 always. 744 if cl_lit_eq(verb,0,vl,"state" as *u8)==1 { 745 if argc < 4 { gv_puts("state needs <journal> <resource>\n" as *u8); sys_exit(2); return 2 } 746 let sres: *u8 = argv[3] as *u8 747 let ssp: *i64 = sys_mmap(16) as *i64 748 let sso: *i64 = sys_mmap(32) as *i64 749 var sv: i64 = -1 750 if cl_res_span_lit(q,cx,sres,ssp)==1 { sv = cl_state(q,cx,ssp[0],ssp[1],sso) } 751 gv_puts("NX-CLAIMS STATE resource=" as *u8) 752 gv_puts(sres) 753 gv_puts(" state=" as *u8) 754 gv_puts(cl_state_name(sv)) 755 gv_puts(" holder=" as *u8) 756 var svh: i64 = 0 757 if sv == 1 { svh = 1 } 758 if sv == 2 { svh = 1 } 759 if svh == 1 { sys_write(1,(q as i64 + sso[0]) as *u8, sso[1]-sso[0]) } 760 if svh == 0 { gv_puts("-" as *u8) } 761 gv_puts("\n" as *u8) 762 sys_exit(0) 763 return 0 764 } 765 if cl_lit_eq(verb,0,vl,"reap" as *u8)==1 { 766 if argc < 5 { gv_puts("reap needs <journal> <reaper> <note>\n" as *u8); sys_exit(2); return 2 } 767 let reaper: *u8 = argv[3] as *u8 768 let rpnote: *u8 = argv[4] as *u8 769 let rk: i64 = cl_reap(journal, q, cx, reaper, rpnote) 770 if rk >= 0 { sys_exit(0); return 0 } 771 sys_exit(1) 772 return 1 773 } 774 if argc < 5 { gv_puts("claim|beat|release need <journal> <resource> <actor> ...\n" as *u8); sys_exit(2); return 2 } 775 let res: *u8 = argv[3] as *u8 776 let actor: *u8 = argv[4] as *u8 777 let sp: *i64 = sys_mmap(16) as *i64 778 let st_out: *i64 = sys_mmap(32) as *i64 779 var st: i64 = -1 780 if cl_res_span_lit(q,cx,res,sp)==1 { st = cl_state(q,cx,sp[0],sp[1],st_out) } 781 if cl_lit_eq(verb,0,vl,"claim" as *u8)==1 { 782 if argc < 7 { gv_puts("claim needs <journal> <resource> <actor> <ttl_sec> <note>\n" as *u8); sys_exit(2); return 2 } 783 let ttlz: *u8 = argv[5] as *u8 784 let ttl: i64 = cl_atoi_z(ttlz) 785 let note: *u8 = argv[6] as *u8 786 if ttl <= 0 { gv_puts("NX-CLAIMS BAD-TTL (must be positive seconds)\n" as *u8); sys_exit(2); return 2 } 787 if st == 1 { 788 if cl_lit_eq(q,st_out[0],st_out[1],actor)==0 { 789 gv_puts("NX-CLAIMS CONTENDED resource=" as *u8) 790 gv_puts(res) 791 gv_puts(" holder=" as *u8) 792 sys_write(1,(q as i64 + st_out[0]) as *u8, st_out[1]-st_out[0]) 793 gv_puts("\n" as *u8) 794 sys_exit(3) 795 return 3 796 } 797 } 798 let rc: i64 = cl_append(journal, -1, "CLAIM" as *u8, res, actor, ttl, note) 799 if rc==0 { gv_puts("NX-CLAIMS HELD resource=" as *u8); gv_puts(res); gv_puts(" actor=" as *u8); gv_puts(actor); gv_puts(" ttl=" as *u8); gv_num(ttl); gv_puts("\n" as *u8); sys_exit(0); return 0 } 800 gv_puts("append FAILED\n" as *u8) 801 sys_exit(1) 802 return 1 803 } 804 // beat | release require an un-released claim whose holder == actor (EXPIRED still owned) 805 if argc < 6 { gv_puts("beat|release need <journal> <resource> <actor> <note>\n" as *u8); sys_exit(2); return 2 } 806 let note2: *u8 = argv[5] as *u8 807 var owner_ok: i64 = 0 808 if st == 1 { if cl_lit_eq(q,st_out[0],st_out[1],actor)==1 { owner_ok=1 } } 809 if st == 2 { if cl_lit_eq(q,st_out[0],st_out[1],actor)==1 { owner_ok=1 } } 810 if owner_ok == 0 { 811 gv_puts("NX-CLAIMS NOT-HOLDER resource=" as *u8) 812 gv_puts(res) 813 gv_puts(" actor=" as *u8) 814 gv_puts(actor) 815 gv_puts("\n" as *u8) 816 sys_exit(3) 817 return 3 818 } 819 if cl_lit_eq(verb,0,vl,"beat" as *u8)==1 { 820 let rc: i64 = cl_append(journal, -1, "BEAT" as *u8, res, actor, 0, note2) 821 if rc==0 { gv_puts("NX-CLAIMS BEAT resource=" as *u8); gv_puts(res); gv_puts("\n" as *u8); sys_exit(0); return 0 } 822 gv_puts("append FAILED\n" as *u8) 823 sys_exit(1) 824 return 1 825 } 826 if cl_lit_eq(verb,0,vl,"release" as *u8)==1 { 827 let rc: i64 = cl_append(journal, -1, "RELEASE" as *u8, res, actor, 0, note2) 828 if rc==0 { gv_puts("NX-CLAIMS RELEASED resource=" as *u8); gv_puts(res); gv_puts("\n" as *u8); sys_exit(0); return 0 } 829 gv_puts("append FAILED\n" as *u8) 830 sys_exit(1) 831 return 1 832 } 833 gv_puts("unknown verb\n" as *u8) 834 sys_exit(2) 835 return 2 836}