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,"<" as *u8) } else { if c == 62 { p = cl_cat(d,p,">" as *u8) } else { if c == 38 { p = cl_cat(d,p,"&" 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}