code wiki / (root) / nx_field_beat_lib.nx

nx_field_beat_lib.nx source

↩ module page · 193 lines · 9295 B

1// nx_field_beat_lib.nx -- THE DECISION CORE OF THE FIELD BEAT (/compare/fieldwatch FW4, 2026-09-05). Pure functions 2// only, so nx_field_beat_gate drives every decision in-process on fixtures assembled at runtime: which domains a list 3// names, what a discover receipt says, how one run is classified, where the stamp goes and what it says. The program 4// (nx_field_beat) owns the forks and the file writes; nothing here touches the network or the evidence store. 5// license_tier: ORIGINAL No hw writes (Rule 26). 6import "nx_syscalls.nx" 7 8const FB_NAME_CAP: i64 = 64 // one domain name slot; the longest name on regen.list is 16 bytes, a longer one is cut and the cut is visible in the stamp's domains= count staying honest 9const FB_MAX_DOMAINS: i64 = 256 // regen.list carries 111 domains today; a list past this sets capped=1 (a FLOOR, announced), never a silent cut 10const FB_CH_NL: i64 = 10 11const FB_CH_CR: i64 = 13 12const FB_CH_HASH: i64 = 35 13const FB_CH_SP: i64 = 32 14const FB_CH_EQ: i64 = 61 15const FB_CH_MINUS: i64 = 45 16const FB_CH_0: i64 = 48 17const FB_CH_9: i64 = 57 18const FB_NUM_BASE: i64 = 10 19const FB_NUM_W: i64 = 24 20const FB_UNPARSED: i64 = 0 - 1 // a field the receipt did not carry: its own bucket, never a zero 21const FB_CLASS_OK: i64 = 1 22const FB_CLASS_SKIP: i64 = 2 // no seeds file: the domain has declared no field yet, so there is nothing to re-read 23const FB_CLASS_FAIL: i64 = 3 24const FB_STAMP_DEFAULT: *u8 = "knowledge/status/fieldbeat.stamp" 25const FB_STAMP_SUFFIX: *u8 = ".stamp" 26const FB_OK_LINE: *u8 = "FIELD-OK " 27const FB_STAMP_HEAD: *u8 = "fieldbeat" // every stamp key is space-preceded, so one span reader serves receipt and stamp 28 29func fb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 30func fb_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 31func fb_puts(fd: i64, s: *u8) -> i64 { sys_write(fd, s, fb_slen(s)); return 0 } 32func fb_putn(fd: i64, v: i64) -> i64 { 33 let b: *u8 = sys_mmap(FB_NUM_W) 34 var x: i64 = v 35 var neg: i64 = 0 36 if x < 0 { neg = 1; x = 0 - x } 37 var i: i64 = FB_NUM_W 38 if x == 0 { i = i - 1; b[i] = FB_CH_0 as u8 } 39 while x > 0 { i = i - 1; b[i] = (FB_CH_0 + (x - (x / FB_NUM_BASE) * FB_NUM_BASE)) as u8; x = x / FB_NUM_BASE } 40 if neg == 1 { i = i - 1; b[i] = FB_CH_MINUS as u8 } 41 sys_write(fd, (b as i64 + i) as *u8, FB_NUM_W - i) 42 return 0 43} 44func fb_cat(dst: *u8, off: i64, s: *u8, cap: i64) -> i64 { 45 var i: i64 = 0 46 while s[i] != (0 as u8) { if off + i < cap - 1 { dst[off + i] = s[i] } i = i + 1 } 47 var o: i64 = off + i 48 if o > cap - 1 { o = cap - 1 } 49 dst[o] = 0 as u8 50 return o 51} 52func fb_domain_at(names: *u8, i: i64) -> *u8 { return ((names as i64) + i * FB_NAME_CAP) as *u8 } 53 54// PURE: one domain per line; lines whose first non-space byte is '#' and blank lines are skipped; CR and surrounding 55// spaces are trimmed. names is an arena of FB_NAME_CAP * FB_MAX_DOMAINS bytes. capped[0]=1 when the arena filled. 56func fb_list_domains(b: *u8, n: i64, names: *u8, capped: *i64) -> i64 { 57 capped[0] = 0 58 var cnt: i64 = 0 59 var i: i64 = 0 60 while i < n { 61 var e: i64 = i 62 while e < n { if (b[e] as i64) == FB_CH_NL { break } e = e + 1 } 63 var s: i64 = i 64 var t: i64 = e 65 var trimming: i64 = 1 66 while trimming == 1 { 67 trimming = 0 68 if t > s { if (b[t - 1] as i64) == FB_CH_CR { t = t - 1; trimming = 1 } } 69 if t > s { if (b[t - 1] as i64) == FB_CH_SP { t = t - 1; trimming = 1 } } 70 } 71 while s < t { if (b[s] as i64) == FB_CH_SP { s = s + 1 } else { break } } 72 if t > s { if (b[s] as i64) != FB_CH_HASH { 73 if cnt < FB_MAX_DOMAINS { 74 let d: *u8 = fb_domain_at(names, cnt) 75 var k: i64 = 0 76 while s + k < t { if k < FB_NAME_CAP - 1 { d[k] = b[s + k] } k = k + 1 } 77 if k > FB_NAME_CAP - 1 { k = FB_NAME_CAP - 1 } 78 d[k] = 0 as u8 79 cnt = cnt + 1 80 } else { capped[0] = 1 } 81 } } 82 i = e + 1 83 } 84 return cnt 85} 86 87func fb_prefix_at(b: *u8, s: i64, e: i64, lit: *u8) -> i64 { 88 var k: i64 = 0 89 while lit[k] != (0 as u8) { if s + k >= e { return 0 } if b[s + k] != lit[k] { return 0 } k = k + 1 } 90 return 1 91} 92// PURE: the LAST line that starts with FB_OK_LINE (seed lines may mention the same keys, so the receipt line is anchored 93// by its own prefix, never by a key search over the whole capture). Returns 1 and the span, or 0. 94func fb_ok_line(cap: *u8, n: i64, s_out: *i64, e_out: *i64) -> i64 { 95 var found: i64 = 0 96 var i: i64 = 0 97 while i < n { 98 var e: i64 = i 99 while e < n { if (cap[e] as i64) == FB_CH_NL { break } e = e + 1 } 100 if fb_prefix_at(cap, i, e, FB_OK_LINE) == 1 { s_out[0] = i; e_out[0] = e; found = 1 } 101 i = e + 1 102 } 103 return found 104} 105// PURE: the integer after " <key>=" inside [s,e); FB_UNPARSED when the key is absent or carries no digits. 106func fb_span_int(b: *u8, s: i64, e: i64, key: *u8) -> i64 { 107 let kl: i64 = fb_slen(key) 108 var i: i64 = s 109 while i + kl + 1 < e { 110 if (b[i] as i64) == FB_CH_SP { if fb_prefix_at(b, i + 1, e, key) == 1 { if (b[i + 1 + kl] as i64) == FB_CH_EQ { 111 var j: i64 = i + 2 + kl 112 var neg: i64 = 0 113 if j < e { if (b[j] as i64) == FB_CH_MINUS { neg = 1; j = j + 1 } } 114 var v: i64 = 0 115 var nd: i64 = 0 116 var scanning: i64 = 1 117 while scanning == 1 { 118 scanning = 0 119 if j < e { let c: i64 = b[j] as i64; if c >= FB_CH_0 { if c <= FB_CH_9 { v = v * FB_NUM_BASE + (c - FB_CH_0); nd = nd + 1; j = j + 1; scanning = 1 } } } 120 } 121 if nd == 0 { return FB_UNPARSED } 122 if neg == 1 { return 0 - v } 123 return v 124 } } } 125 i = i + 1 126 } 127 return FB_UNPARSED 128} 129func fb_receipt_ok(cap: *u8, n: i64) -> i64 { 130 let so: *i64 = sys_mmap(16) as *i64 131 let eo: *i64 = sys_mmap(16) as *i64 132 return fb_ok_line(cap, n, so, eo) 133} 134func fb_receipt_int(cap: *u8, n: i64, key: *u8) -> i64 { 135 let so: *i64 = sys_mmap(16) as *i64 136 let eo: *i64 = sys_mmap(16) as *i64 137 if fb_ok_line(cap, n, so, eo) == 0 { return FB_UNPARSED } 138 return fb_span_int(cap, so[0], eo[0], key) 139} 140// PURE: one run's class. No seeds file -> SKIP (nothing declared). Exit 0 WITH a FIELD-OK line -> OK. Everything else, 141// including a clean exit that printed no receipt (the silent manifestation), is FAIL. 142func fb_classify(rc: i64, ok: i64, seeds_present: i64) -> i64 { 143 if seeds_present == 0 { return FB_CLASS_SKIP } 144 if rc == 0 { if ok == 1 { return FB_CLASS_OK } } 145 return FB_CLASS_FAIL 146} 147func fb_class_name(c: i64) -> *u8 { 148 if c == FB_CLASS_OK { return "OK" as *u8 } 149 if c == FB_CLASS_SKIP { return "SKIP-NO-SEEDS" as *u8 } 150 return "FAIL" as *u8 151} 152// PURE: the production stamp only for the default list; any other list stamps beside itself, so a trial run can never 153// forge the heartbeat the watch plane reads (the nx_gate_roster_run lesson, 2026-08-20). 154func fb_stamp_path(list: *u8, is_default: i64, dst: *u8, cap: i64) -> i64 { 155 if is_default == 1 { return fb_cat(dst, 0, FB_STAMP_DEFAULT, cap) } 156 let o: i64 = fb_cat(dst, 0, list, cap) 157 return fb_cat(dst, o, FB_STAMP_SUFFIX, cap) 158} 159func fb_stamp_verdict(ok: i64, failed: i64) -> *u8 { 160 if ok + failed == 0 { return "EMPTY" as *u8 } 161 if failed > 0 { return "RED" as *u8 } 162 return "GREEN" as *u8 163} 164// PURE over an fd: the stamp line. Every key is space-preceded; the partition ok + failed + skipped = sum is printed. 165func fb_stamp_write(fd: i64, ts: i64, list: *u8, domains: i64, ok: i64, failed: i64, skipped: i64, cands: i64, capped: i64, drifted: i64) -> i64 { 166 fb_puts(fd, FB_STAMP_HEAD) 167 fb_puts(fd, " ts=" as *u8); fb_putn(fd, ts) 168 fb_puts(fd, " list=" as *u8); fb_puts(fd, list) 169 fb_puts(fd, " domains=" as *u8); fb_putn(fd, domains) 170 fb_puts(fd, " ran=" as *u8); fb_putn(fd, ok + failed) 171 fb_puts(fd, " ok=" as *u8); fb_putn(fd, ok) 172 fb_puts(fd, " failed=" as *u8); fb_putn(fd, failed) 173 fb_puts(fd, " skipped_no_seeds=" as *u8); fb_putn(fd, skipped) 174 fb_puts(fd, " sum=" as *u8); fb_putn(fd, ok + failed + skipped) 175 fb_puts(fd, " candidates_total=" as *u8); fb_putn(fd, cands) 176 fb_puts(fd, " drifted_total=" as *u8); fb_putn(fd, drifted) 177 fb_puts(fd, " capped=" as *u8); fb_putn(fd, capped) 178 fb_puts(fd, " verdict=" as *u8); fb_puts(fd, fb_stamp_verdict(ok, failed)) 179 fb_puts(fd, "\n" as *u8) 180 return 0 181} 182// PURE over an fd: one journal row per domain per run. 183func fb_jrnl_row(fd: i64, ts: i64, dom: *u8, cls: i64, rc: i64, cands: i64, named: i64, failed_seeds: i64) -> i64 { 184 fb_puts(fd, "fieldbeat|" as *u8); fb_putn(fd, ts) 185 fb_puts(fd, "|" as *u8); fb_puts(fd, dom) 186 fb_puts(fd, "|" as *u8); fb_puts(fd, fb_class_name(cls)) 187 fb_puts(fd, "|rc=" as *u8); fb_putn(fd, rc) 188 fb_puts(fd, "|candidates=" as *u8); fb_putn(fd, cands) 189 fb_puts(fd, "|named=" as *u8); fb_putn(fd, named) 190 fb_puts(fd, "|failed_seeds=" as *u8); fb_putn(fd, failed_seeds) 191 fb_puts(fd, "\n" as *u8) 192 return 0 193}