code wiki / (root) / nx_claim_core.nx

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}