nx_native_config.nx source
↩ module page · 128 lines · 6243 B
1// nx_native_config.nx -- ordered CONFIG/POLICY storage on the NATIVE substrate, replacing borrowed TSV.
2//
3// Operator directive (2026-06-20): "stop using tsv, migrate, get everything to nishi ecosystem only."
4// A config is an ORDERED list of (key,value) field-records. Instead of a flat .tsv parsed by ad-hoc
5// tab-splitting, it lives in ONE content-addressed seg_store (the SAME substrate the asset catalog
6// uses): each row is a canon-encoded record under key "<tag>:NNNN", plus "<tag>:count". Rows read
7// back IN ORDER (so first-match-wins policy semantics are preserved). No TSV, no SQL -- nishi
8// ecosystem only (#feedback-pure-nishi-infomgmt-no-sql). Authored BY AN ORGAN (the seeder), data
9// lives in the store (data-driven, #11/#17). No hardware writes (Rule 26). license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_canon_cid.nx"
12import "nx_uxf_decode.nx"
13import "nx_seg_store.nx"
14const K_MAGIC_8192: i64 = 8192
15
16func ncfg_streq(a: *u8, b: *u8) -> i64 {
17 var i: i64 = 0
18 while 1 == 1 { if a[i] != b[i] { return 0 } if a[i] == (0 as u8) { return 1 } i = i + 1 }
19 return 1
20}
21
22// "<tag>:NNNN" (4-digit, zero-padded) into out; returns length.
23func ncfg_key(tag: *u8, idx: i64, out: *u8) -> i64 {
24 var o: i64 = 0
25 while tag[o] != (0 as u8) { out[o] = tag[o]; o = o + 1 }
26 out[o] = 58 as u8; o = o + 1 // ':'
27 out[o] = (48 + ((idx / 1000) % 10)) as u8; o = o + 1
28 out[o] = (48 + ((idx / 100) % 10)) as u8; o = o + 1
29 out[o] = (48 + ((idx / 10) % 10)) as u8; o = o + 1
30 out[o] = (48 + (idx % 10)) as u8; o = o + 1
31 out[o] = 0 as u8
32 return o
33}
34// 2026-07-29 seq1232: per-call scratch was FRESH page-granular mmaps per call (ncfg_row runs per ROW;
35// hub_gw measured ~1 MB leaked per request, no munmap exists in the runtime). Lazy statics: one page
36// each for the life of the process. Safe: no ncfg consumer forks mid-call, and every boxed value is
37// consumed before the next ncfg_* call reuses the box (pp/ll point INTO the mapped store, the box is
38// just the out-param).
39static ncfg_scr_key: *u8
40static ncfg_scr_pp: *i64
41static ncfg_scr_ll: *i64
42static ncfg_scr_t: *u8
43// "<tag>:count" into out.
44func ncfg_count_key(tag: *u8, out: *u8) -> i64 {
45 var o: i64 = 0
46 while tag[o] != (0 as u8) { out[o] = tag[o]; o = o + 1 }
47 let suf: *u8 = ":count\x00" as *u8
48 var i: i64 = 0
49 while suf[i] != (0 as u8) { out[o] = suf[i]; o = o + 1; i = i + 1 }
50 out[o] = 0 as u8
51 return o
52}
53func ncfg_itoa(v: i64, out: *u8) -> i64 {
54 if (ncfg_scr_t as i64) == 0 { ncfg_scr_t = sys_mmap(28) }
55 let t: *u8 = ncfg_scr_t
56 var m: i64 = v
57 var k: i64 = 0
58 if m == 0 { t[0] = 48 as u8; k = 1 }
59 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
60 var i: i64 = 0
61 while i < k { out[i] = t[k - 1 - i]; i = i + 1 }
62 out[k] = 0 as u8
63 return k
64}
65func ncfg_atoi(s: *u8, n: i64) -> i64 {
66 var v: i64 = 0
67 var i: i64 = 0
68 while i < n { let c: i64 = s[i]; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 }
69 return v
70}
71
72// ---- SEED (write an ordered config) ----
73func ncfg_begin() -> *i64 { return ss_begin() }
74// add row `idx` of config `tag` = canon_encode(keys,vals,n). Returns ss_add rc (0 ok).
75func ncfg_add_row(w: *i64, tag: *u8, idx: i64, keys: *i64, vals: *i64, n: i64) -> i64 {
76 let buf: *u8 = sys_mmap(K_MAGIC_8192)
77 let blen: i64 = canon_encode(keys, vals, n, buf)
78 let key: *u8 = sys_mmap(64)
79 ncfg_key(tag, idx, key)
80 return ss_add(w, 1, key, buf, blen)
81}
82func ncfg_set_count(w: *i64, tag: *u8, count: i64) -> i64 {
83 let key: *u8 = sys_mmap(64); ncfg_count_key(tag, key)
84 let val: *u8 = sys_mmap(28); let vl: i64 = ncfg_itoa(count, val)
85 return ss_add(w, 1, key, val, vl)
86}
87func ncfg_commit(prefix: *u8, w: *i64) -> i64 { return ss_commit(prefix, w, sys_now_ms()) }
88
89// ---- READ ----
90// 2026-07-25 (debt seq962): route EVERY native-config reader through the cached open. ncfg_open had
91// 23 call sites, several of them PER-REQUEST in long-lived daemons (nx_hub_gw calls it 3x per HTTP
92// request), and bare ss_open reads the whole store into fresh anonymous RAM with no ss_close in the
93// tree -- measured 1336 kB leaked per hub_gw request. One line here fixes the whole class.
94// Freshness is preserved: ss_open_cached invalidates on the manifest (st_size, st_mtime), so a store
95// edit is still visible with no restart. KNOWN BOUND: mtime has 1-second granularity, so a rewrite
96// that lands in the SAME second AND leaves the manifest byte-length identical can serve one stale
97// read; the manifest changes length on essentially every real commit, and this is the same bound the
98// already-proven dss_open_maybe_cached cache has run under in production.
99func ncfg_open(prefix: *u8) -> *i64 { return ss_open_cached(prefix) }
100func ncfg_count(h: *i64, tag: *u8) -> i64 {
101 if (h as i64) == 0 { return 0 }
102 if (ncfg_scr_key as i64) == 0 { ncfg_scr_key = sys_mmap(64) }
103 if (ncfg_scr_pp as i64) == 0 { ncfg_scr_pp = sys_mmap(16) as *i64 }
104 if (ncfg_scr_ll as i64) == 0 { ncfg_scr_ll = sys_mmap(16) as *i64 }
105 let key: *u8 = ncfg_scr_key; ncfg_count_key(tag, key)
106 let pp: *i64 = ncfg_scr_pp
107 let ll: *i64 = ncfg_scr_ll
108 if ss_hget(h, key, pp, ll) != 1 { return 0 }
109 return ncfg_atoi(pp[0] as *u8, ll[0])
110}
111// decode row idx of config tag into out_keys/out_vals; returns field count, or -1 if absent.
112func ncfg_row(h: *i64, tag: *u8, idx: i64, out_keys: *i64, out_vals: *i64, maxf: i64) -> i64 {
113 if (h as i64) == 0 { return 0 - 1 }
114 if (ncfg_scr_key as i64) == 0 { ncfg_scr_key = sys_mmap(64) }
115 if (ncfg_scr_pp as i64) == 0 { ncfg_scr_pp = sys_mmap(16) as *i64 }
116 if (ncfg_scr_ll as i64) == 0 { ncfg_scr_ll = sys_mmap(16) as *i64 }
117 let key: *u8 = ncfg_scr_key; ncfg_key(tag, idx, key)
118 let pp: *i64 = ncfg_scr_pp
119 let ll: *i64 = ncfg_scr_ll
120 if ss_hget(h, key, pp, ll) != 1 { return 0 - 1 }
121 return canon_decode(pp[0] as *u8, ll[0], out_keys, out_vals, maxf)
122}
123// field value by name within a decoded row (null if absent).
124func ncfg_field(keys: *i64, vals: *i64, nf: i64, name: *u8) -> *u8 {
125 var i: i64 = 0
126 while i < nf { if ncfg_streq(keys[i] as *u8, name) == 1 { return vals[i] as *u8 } i = i + 1 }
127 return 0 as *u8
128}