nx_claim_core.nx source
↩ module page · 384 lines · 15014 B
1// nx_claim_core.nx -- SESSION CLAIMS over the deploy-leash store (coordination rung 1,
2// operator 2026-07-16: "i have concurrent workstreams so coordinate and get our coordination
3// up to state of the art"). A CLAIM is a leash row named claude_<sess8> whose purpose field
4// carries the workstream + a file-scope csv: "ws=<slug>;scope=<prefix>,<prefix>,...". Because
5// claims ARE leash rows in knowledge/store/leash, they inherit the whole supervision fabric
6// for free: cadence_loop walks them, a crashed session stops beating -> ABANDONED -> reclaimed
7// BY CONSTRUCTION (the 2026-07-16 crash lesson: 7 sessions died holding an uncoordinated tree).
8// Liveness is RENEWAL-BASED like the leash (pid is registered as 1 = init: verdict rides ONLY
9// on beat age vs ttl -- a Claude session has no stable pid of its own).
10//
11// Verbs (see nx_claim_check.nx CLI): claim / beat / release / check <paths> / walk.
12// check = the PRE-COMMIT tooth: exit CC_DENY when a staged path sits inside ANOTHER live
13// session's scope (the holder is NAMED); grant otherwise. Overlap = prefix match, csv scopes,
14// trailing '*' tolerated. Pure cores (cc_live_verdict, cc_scope_hit) are gate-locked.
15//
16// REUSE (compose, do not reinvent): row shape + d:<name>/d:ids layout = nx_deploy_leash
17// (field helpers replicated cc_* to keep this importable next to it -- leash has a main);
18// store = sov_put/sov_get_copy (nx_sov_ledger via nx_model_lane_core); clock = nx_watch
19// nw_mark/nw_read ("leash" namespace, same as deployments). flock idiom = dl_add_id's.
20// license_tier: ORIGINAL
21import "nx_watch.nx"
22import "nx_model_lane_core.nx"
23const CC_MAGIC_999999999: i64 = 999999999
24
25const CC_STORE: *u8 = "knowledge/store/leash"
26const CC_LOCK: *u8 = "knowledge/status/leash.lock"
27const CC_NS: *u8 = "leash"
28const CC_GRANT: i64 = 0
29const CC_DENY: i64 = 3
30const CC_ROWCAP: i64 = 512
31const CC_IDSCAP: i64 = 262144
32const CC_NAMECAP: i64 = 96
33const CC_SCOPECAP: i64 = 256
34const CC_CLAIM_PID: i64 = 1
35const CC_CH_PIPE: i64 = 124
36const CC_CH_NL: i64 = 10
37const CC_CH_SEMI: i64 = 59
38const CC_CH_COMMA: i64 = 44
39const CC_CH_STAR: i64 = 42
40const CC_CH_NUL: i64 = 0
41
42func cc_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
43
44// "claude_" + sess8 -> out (NUL-term); returns len.
45func cc_name(sess8: *u8, out: *u8) -> i64 {
46 let pfx: *u8 = "claude_" as *u8
47 var o: i64 = 0
48 var i: i64 = 0
49 while pfx[i] != (0 as u8) { out[o] = pfx[i]; o = o + 1; i = i + 1 }
50 i = 0
51 while sess8[i] != (0 as u8) { if o < CC_NAMECAP - 2 { out[o] = sess8[i]; o = o + 1 } i = i + 1 }
52 out[o] = 0 as u8
53 return o
54}
55
56// "d:<name>" key (NUL-term) -- same layout as the leash.
57func cc_key(name: *u8, out: *u8) -> i64 {
58 out[0] = 100 as u8
59 out[1] = 58 as u8
60 var i: i64 = 0
61 while name[i] != (0 as u8) { out[2 + i] = name[i]; i = i + 1 }
62 out[2 + i] = 0 as u8
63 return 2 + i
64}
65
66// copy |-field k of row[0..n) into out (NUL-term); returns len or -1. (leash dl_field replica)
67func cc_field(row: *u8, n: i64, k: i64, out: *u8, cap: i64) -> i64 {
68 var field: i64 = 0
69 var o: i64 = 0
70 var i: i64 = 0
71 while i < n {
72 let c: i64 = row[i] as i64
73 if c == CC_CH_PIPE { field = field + 1; if field > k { i = n } }
74 if c != CC_CH_PIPE { if field == k { if o < cap - 1 { out[o] = row[i]; o = o + 1 } } }
75 if i < n { i = i + 1 }
76 }
77 out[o] = 0 as u8
78 if field < k { return 0 - 1 }
79 return o
80}
81
82// integer |-field k (digits only). (leash sq_field_int replica)
83func cc_field_int(row: *u8, n: i64, k: i64) -> i64 {
84 var field: i64 = 0
85 var v: i64 = 0
86 var seen: i64 = 0
87 var i: i64 = 0
88 while i < n {
89 let c: i64 = row[i] as i64
90 if c == CC_CH_PIPE { field = field + 1; if field > k { i = n } }
91 if c != CC_CH_PIPE { if field == k { if c >= 48 && c <= 57 { v = v * 10 + (c - 48); seen = 1 } } }
92 if i < n { i = i + 1 }
93 }
94 if seen == 0 { return 0 - 1 }
95 return v
96}
97
98// ensure name in d:ids under flock (leash dl_add_id replica; same lock file = same serialization).
99func cc_ids_add(name: *u8) -> i64 {
100 let lf: i64 = sys_openat_append(CC_LOCK, 0x1a4)
101 if lf >= 0 { sys_flock(lf, SYS_LOCK_EX) }
102 let ids: *u8 = sys_mmap(CC_IDSCAP) as *u8
103 let idn: i64 = sov_get_copy(CC_STORE, "d:ids" as *u8, ids, CC_IDSCAP - 1)
104 let nl: i64 = cc_slen(name)
105 var have: i64 = 0
106 if idn > 0 {
107 var i: i64 = 0
108 while i < idn {
109 var e: i64 = i
110 var sc: i64 = 1
111 while sc == 1 {
112 if e >= idn { sc = 0 }
113 if sc == 1 { if ids[e] == (CC_CH_NL as u8) { sc = 0 } else { e = e + 1 } }
114 }
115 if e - i == nl {
116 var m: i64 = 0
117 var eq: i64 = 1
118 while m < nl { if ids[i + m] != name[m] { eq = 0; m = nl } else { m = m + 1 } }
119 if eq == 1 { have = 1 }
120 }
121 i = e + 1
122 }
123 }
124 if have == 0 {
125 var o: i64 = idn
126 if o < 0 { o = 0 }
127 var m: i64 = 0
128 while m < nl { ids[o] = name[m]; o = o + 1; m = m + 1 }
129 ids[o] = CC_CH_NL as u8
130 o = o + 1
131 sov_put(CC_STORE, "d:ids" as *u8, ids, o)
132 }
133 if lf >= 0 { sys_flock(lf, SYS_LOCK_UN); sys_close(lf) }
134 return 0
135}
136
137// PURE liveness verdict for a claim: released/reclaimed word kills it; stale beat kills it.
138// word is the nx_watch state word. 1 = LIVE (blocks foreign paths) / 0 = not live. gate-locked.
139func cc_live_verdict(age_ms: i64, ttl_ms: i64, word: *u8) -> i64 {
140 if std_streq(word, "RELEASED" as *u8) == 1 { return 0 }
141 if std_streq(word, "RECLAIMED" as *u8) == 1 { return 0 }
142 if age_ms > ttl_ms { return 0 }
143 return 1
144}
145
146// PURE scope test: does path sit under ANY csv prefix in scope? Trailing '*' on a prefix is
147// tolerated (stripped). Empty prefixes never match. 1 hit / 0 miss. gate-locked.
148func cc_scope_hit(scope: *u8, path: *u8) -> i64 {
149 let sn: i64 = cc_slen(scope)
150 let pn: i64 = cc_slen(path)
151 var i: i64 = 0
152 while i < sn {
153 var e: i64 = i
154 var g: i64 = 1
155 while g == 1 { g = 0; if e < sn { if scope[e] != (CC_CH_COMMA as u8) { e = e + 1; g = 1 } } }
156 var pe: i64 = e
157 if pe > i { if scope[pe - 1] == (CC_CH_STAR as u8) { pe = pe - 1 } }
158 let plen: i64 = pe - i
159 if plen > 0 && plen <= pn {
160 var m: i64 = 0
161 var eq: i64 = 1
162 while m < plen { if path[m] != scope[i + m] { eq = 0; m = plen } else { m = m + 1 } }
163 if eq == 1 { return 1 }
164 }
165 i = e + 1
166 }
167 return 0
168}
169
170// copy the scope=... payload out of a purpose field ("ws=<slug>;scope=<csv>"). len or -1.
171func cc_purpose_scope(purpose: *u8, out: *u8, cap: i64) -> i64 {
172 let key: *u8 = "scope=" as *u8
173 let kn: i64 = cc_slen(key)
174 let n: i64 = cc_slen(purpose)
175 var i: i64 = 0
176 while i + kn <= n {
177 var j: i64 = 0
178 var ok: i64 = 1
179 while j < kn { if purpose[i + j] != key[j] { ok = 0; j = kn } else { j = j + 1 } }
180 if ok == 1 {
181 var o: i64 = 0
182 var p: i64 = i + kn
183 while p < n && purpose[p] != (CC_CH_SEMI as u8) { if o < cap - 1 { out[o] = purpose[p]; o = o + 1 } p = p + 1 }
184 out[o] = 0 as u8
185 return o
186 }
187 i = i + 1
188 }
189 out[0] = 0 as u8
190 return 0 - 1
191}
192
193// register a claim: leash row claude_<sess8> with purpose "ws=<ws>;scope=<scope>", pid=1
194// (verdict rides beats), REGISTERED clock started. Idempotent (latest-wins row + ids dedupe).
195func cc_register(sess8: *u8, ws: *u8, scope: *u8, ttl_ms: i64) -> i64 {
196 let name: *u8 = sys_mmap(CC_NAMECAP) as *u8
197 cc_name(sess8, name)
198 let row: *u8 = sys_mmap(CC_ROWCAP) as *u8
199 var o: i64 = 0
200 var i: i64 = 0
201 while name[i] != (0 as u8) { row[o] = name[i]; o = o + 1; i = i + 1 }
202 row[o] = CC_CH_PIPE as u8; o = o + 1
203 i = 0
204 while sess8[i] != (0 as u8) { row[o] = sess8[i]; o = o + 1; i = i + 1 }
205 row[o] = CC_CH_PIPE as u8; o = o + 1
206 let wk: *u8 = "ws=" as *u8
207 i = 0
208 while wk[i] != (0 as u8) { row[o] = wk[i]; o = o + 1; i = i + 1 }
209 i = 0
210 while ws[i] != (0 as u8) { if o < CC_ROWCAP - 8 { row[o] = ws[i]; o = o + 1 } i = i + 1 }
211 row[o] = CC_CH_SEMI as u8; o = o + 1
212 let sk: *u8 = "scope=" as *u8
213 i = 0
214 while sk[i] != (0 as u8) { row[o] = sk[i]; o = o + 1; i = i + 1 }
215 i = 0
216 while scope[i] != (0 as u8) { if o < CC_ROWCAP - 8 { row[o] = scope[i]; o = o + 1 } i = i + 1 }
217 row[o] = CC_CH_PIPE as u8; o = o + 1
218 o = o + std_itoa(CC_CLAIM_PID, row + o)
219 row[o] = CC_CH_PIPE as u8; o = o + 1
220 o = o + std_itoa(ttl_ms, row + o)
221 row[o] = CC_CH_PIPE as u8; o = o + 1
222 let sp: *u8 = "knowledge/status/claim_" as *u8
223 i = 0
224 while sp[i] != (0 as u8) { row[o] = sp[i]; o = o + 1; i = i + 1 }
225 i = 0
226 while sess8[i] != (0 as u8) { row[o] = sess8[i]; o = o + 1; i = i + 1 }
227 let se: *u8 = ".stop" as *u8
228 i = 0
229 while se[i] != (0 as u8) { row[o] = se[i]; o = o + 1; i = i + 1 }
230 row[o] = CC_CH_PIPE as u8; o = o + 1
231 o = o + std_itoa(sys_now_us(), row + o)
232 row[o] = 0 as u8
233 let key: *u8 = sys_mmap(CC_NAMECAP + 4) as *u8
234 cc_key(name, key)
235 sov_put(CC_STORE, key, row, o)
236 cc_ids_add(name)
237 nw_mark(CC_NS, name, "REGISTERED" as *u8)
238 return 0
239}
240
241func cc_beat(sess8: *u8) -> i64 {
242 let name: *u8 = sys_mmap(CC_NAMECAP) as *u8
243 cc_name(sess8, name)
244 nw_mark(CC_NS, name, "ACTIVE" as *u8)
245 return 0
246}
247
248func cc_release(sess8: *u8) -> i64 {
249 let name: *u8 = sys_mmap(CC_NAMECAP) as *u8
250 cc_name(sess8, name)
251 nw_mark(CC_NS, name, "RELEASED" as *u8)
252 return 0
253}
254
255// is ids[i..e) a claude_* name that is NOT self? copies it into name. 1 yes / 0 no.
256func cc_take_foreign(ids: *u8, i: i64, e: i64, self: *u8, name: *u8) -> i64 {
257 let nl: i64 = e - i
258 if nl <= 7 { return 0 }
259 if nl >= CC_NAMECAP - 1 { return 0 }
260 let pfx: *u8 = "claude_" as *u8
261 var m: i64 = 0
262 while m < 7 { if ids[i + m] != pfx[m] { return 0 } m = m + 1 }
263 m = 0
264 while m < nl { name[m] = ids[i + m]; m = m + 1 }
265 name[nl] = 0 as u8
266 if std_streq(name, self) == 1 { return 0 }
267 return 1
268}
269
270// THE PRE-COMMIT TOOTH: any path inside another LIVE session's scope -> DENY (holder named).
271func cc_check(self8: *u8, pathv: *i64, pathn: i64) -> i64 {
272 let self: *u8 = sys_mmap(CC_NAMECAP) as *u8
273 cc_name(self8, self)
274 let ids: *u8 = sys_mmap(CC_IDSCAP) as *u8
275 let idn: i64 = sov_get_copy(CC_STORE, "d:ids" as *u8, ids, CC_IDSCAP - 1)
276 if idn <= 0 { std_putln("CLAIM-GRANT no-claims" as *u8) return CC_GRANT }
277 let name: *u8 = sys_mmap(CC_NAMECAP) as *u8
278 let key: *u8 = sys_mmap(CC_NAMECAP + 4) as *u8
279 let row: *u8 = sys_mmap(CC_ROWCAP) as *u8
280 let purpose: *u8 = sys_mmap(CC_ROWCAP) as *u8
281 let scope: *u8 = sys_mmap(CC_SCOPECAP) as *u8
282 let word: *u8 = sys_mmap(64) as *u8
283 let age: *i64 = sys_mmap(8) as *i64
284 var live_claims: i64 = 0
285 var i: i64 = 0
286 while i < idn {
287 var e: i64 = i
288 var sc: i64 = 1
289 while sc == 1 {
290 if e >= idn { sc = 0 }
291 if sc == 1 { if ids[e] == (CC_CH_NL as u8) { sc = 0 } else { e = e + 1 } }
292 }
293 if cc_take_foreign(ids, i, e, self, name) == 1 {
294 cc_key(name, key)
295 let vn: i64 = sov_get_copy(CC_STORE, key, row, CC_ROWCAP - 1)
296 if vn > 0 {
297 let ttl: i64 = cc_field_int(row, vn, 4)
298 word[0] = 0 as u8
299 var bage: i64 = CC_MAGIC_999999999
300 let cls: i64 = nw_read(CC_NS, name, word, age)
301 if cls >= 0 { bage = age[0] }
302 if cc_live_verdict(bage, ttl, word) == 1 {
303 live_claims = live_claims + 1
304 if cc_field(row, vn, 2, purpose, CC_ROWCAP - 1) > 0 {
305 if cc_purpose_scope(purpose, scope, CC_SCOPECAP) > 0 {
306 var p: i64 = 0
307 while p < pathn {
308 let path: *u8 = pathv[p] as *u8
309 if cc_scope_hit(scope, path) == 1 {
310 std_puts("CLAIM-DENY holder=" as *u8)
311 std_puts(name)
312 std_puts(" purpose=" as *u8)
313 std_puts(purpose)
314 std_puts(" path=" as *u8)
315 std_putln(path)
316 return CC_DENY
317 }
318 p = p + 1
319 }
320 }
321 }
322 }
323 }
324 }
325 i = e + 1
326 }
327 std_puts("CLAIM-GRANT paths=" as *u8)
328 std_pdec(pathn)
329 std_puts(" live_foreign_claims=" as *u8)
330 std_pdec(live_claims)
331 std_puts("\n" as *u8)
332 return CC_GRANT
333}
334
335// print all claude_* claims with liveness verdicts. returns count printed.
336func cc_walk(self8: *u8) -> i64 {
337 let self: *u8 = sys_mmap(CC_NAMECAP) as *u8
338 cc_name(self8, self)
339 let ids: *u8 = sys_mmap(CC_IDSCAP) as *u8
340 let idn: i64 = sov_get_copy(CC_STORE, "d:ids" as *u8, ids, CC_IDSCAP - 1)
341 if idn <= 0 { std_putln("CLAIMS none registered" as *u8) return 0 }
342 let name: *u8 = sys_mmap(CC_NAMECAP) as *u8
343 let key: *u8 = sys_mmap(CC_NAMECAP + 4) as *u8
344 let row: *u8 = sys_mmap(CC_ROWCAP) as *u8
345 let purpose: *u8 = sys_mmap(CC_ROWCAP) as *u8
346 let word: *u8 = sys_mmap(64) as *u8
347 let age: *i64 = sys_mmap(8) as *i64
348 let none: *u8 = sys_mmap(8) as *u8
349 var count: i64 = 0
350 var i: i64 = 0
351 while i < idn {
352 var e: i64 = i
353 var sc: i64 = 1
354 while sc == 1 {
355 if e >= idn { sc = 0 }
356 if sc == 1 { if ids[e] == (CC_CH_NL as u8) { sc = 0 } else { e = e + 1 } }
357 }
358 none[0] = 0 as u8
359 if cc_take_foreign(ids, i, e, none, name) == 1 {
360 cc_key(name, key)
361 let vn: i64 = sov_get_copy(CC_STORE, key, row, CC_ROWCAP - 1)
362 if vn > 0 {
363 count = count + 1
364 let ttl: i64 = cc_field_int(row, vn, 4)
365 word[0] = 0 as u8
366 var bage: i64 = CC_MAGIC_999999999
367 let cls: i64 = nw_read(CC_NS, name, word, age)
368 if cls >= 0 { bage = age[0] }
369 std_puts("CLAIM " as *u8)
370 std_puts(name)
371 std_puts(" " as *u8)
372 cc_field(row, vn, 2, purpose, CC_ROWCAP - 1)
373 std_puts(purpose)
374 std_puts(" beat_age_ms=" as *u8)
375 std_pdec(bage)
376 std_puts(" ttl_ms=" as *u8)
377 std_pdec(ttl)
378 std_puts(" -> " as *u8)
379 if cc_live_verdict(bage, ttl, word) == 1 { std_putln("LIVE" as *u8) } else { std_putln("EXPIRED/RELEASED" as *u8) }
380 }
381 }
382 i = e + 1
383 }
384 return count
385}