nx_cell_lib.nx source
↩ module page · 157 lines · 6732 B
1// nx_cell_lib.nx -- ORGANISATION AND PLACE CELLS AS ROWS (shared lib, 2026-08-24).
2//
3// A CELL is one node of the hierarchy a feedback surface rolls up through: a congregation, an agency, a
4// nonprofit program (organisation cells) or a locality, region, nation (place cells). One record kind,
5// one key family, one parent pointer -- the tenant row every multi-organisation surface needs and none
6// had (absence proven 2026-08-24: no tenant lib in 23,167 sources). Composes the survey engine's store
7// discipline: canon-encoded records on the append-only seg_store, latest version wins, history kept.
8// key "cell:<id>"
9// fields type=cell id kind name parent denom usec
10// id a-z 0-9 only, 1..CEL_ID_MAX -- no dash, so "<instrument>-<cell>" survey ids split without
11// ambiguity; CEL_ID_MAX is DERIVED: the engine's survey-id bound is 40 (se_id_ok) and the
12// instrument name is bounded at CEL_INST_MAX, so CEL_INST_MAX + 1 + CEL_ID_MAX == 40.
13// kind must appear in the caller-supplied comma list (conf data, never a literal here)
14// parent another cell id that EXISTS, or "-" for a root; an unknown parent REFUSES by name
15// denom declared denominator (roster size, population) for post-stratification; 0 = undeclared
16// license_tier: ORIGINAL
17import "nx_survey_engine.nx"
18
19const CEL_SURVEY_ID_MAX: i64 = 40 // se_id_ok's bound, restated once so the derivation below is visible
20const CEL_INST_MAX: i64 = 15
21const CEL_ID_MAX: i64 = 24 // CEL_SURVEY_ID_MAX - CEL_INST_MAX - 1 (the dash)
22const CEL_KEY_CAP: i64 = 64
23const CEL_FIELDS: i64 = 16
24const CEL_NUM_BYTES: i64 = 32
25const CEL_WORD: i64 = 8
26const CEL_OK: i64 = 0
27const CEL_ERR_ID: i64 = 0 - 1
28const CEL_ERR_KIND: i64 = 0 - 2
29const CEL_ERR_PARENT: i64 = 0 - 3
30const CEL_ERR_DENOM: i64 = 0 - 4
31const CEL_ERR_STORE: i64 = 0 - 5
32const CEL_ERR_NAME: i64 = 0 - 6
33const CEL_ROOT: *u8 = "-"
34const CEL_ASCII_A: i64 = 97
35const CEL_ASCII_Z: i64 = 122
36const CEL_ASCII_0: i64 = 48
37const CEL_ASCII_9: i64 = 57
38const CEL_ASCII_COMMA: i64 = 44
39const CEL_ASCII_DASH: i64 = 45
40
41func cel_id_ok(s: *u8) -> i64 {
42 var i: i64 = 0
43 while s[i] != (0 as u8) {
44 let c: i64 = s[i] as i64
45 var ok: i64 = 0
46 if c >= CEL_ASCII_A { if c <= CEL_ASCII_Z { ok = 1 } }
47 if c >= CEL_ASCII_0 { if c <= CEL_ASCII_9 { ok = 1 } }
48 if ok == 0 { return 0 }
49 i = i + 1
50 }
51 if i < 1 { return 0 }
52 if i > CEL_ID_MAX { return 0 }
53 return 1
54}
55func cel_inst_ok(s: *u8) -> i64 {
56 if cel_id_ok(s) == 0 { return 0 }
57 if se_slen(s) > CEL_INST_MAX { return 0 }
58 return 1
59}
60// kind must equal one comma-separated entry of kinds exactly
61func cel_kind_ok(kind: *u8, kinds: *u8) -> i64 {
62 let kl: i64 = se_slen(kind)
63 if kl < 1 { return 0 }
64 var i: i64 = 0
65 while kinds[i] != (0 as u8) {
66 var j: i64 = 0
67 var m: i64 = 1
68 while j < kl {
69 if kinds[i + j] == (0 as u8) { m = 0; j = kl } else {
70 if kinds[i + j] != kind[j] { m = 0; j = kl } else { j = j + 1 }
71 }
72 }
73 if m == 1 {
74 var endc: i64 = kinds[i + kl] as i64
75 if endc == 0 { return 1 }
76 if endc == CEL_ASCII_COMMA { return 1 }
77 }
78 while kinds[i] != (0 as u8) { if kinds[i] == (CEL_ASCII_COMMA as u8) { break } i = i + 1 }
79 if kinds[i] == (CEL_ASCII_COMMA as u8) { i = i + 1 }
80 }
81 return 0
82}
83func cel_key(id: *u8, out: *u8) -> i64 {
84 var o: i64 = se_cat(out, 0, "cell:" as *u8)
85 o = se_cat(out, o, id)
86 out[o] = 0 as u8
87 return o
88}
89// survey id of an instrument at a cell: "<inst>-<cell>"
90func cel_survey_id(inst: *u8, id: *u8, out: *u8) -> i64 {
91 var o: i64 = se_cat(out, 0, inst)
92 o = se_catc(out, o, CEL_ASCII_DASH)
93 o = se_cat(out, o, id)
94 out[o] = 0 as u8
95 return o
96}
97func cel_get(prefix: *u8, id: *u8, ks: *i64, vs: *i64, maxf: i64) -> i64 {
98 let key: *u8 = sys_mmap(CEL_KEY_CAP)
99 cel_key(id, key)
100 return se_get_dec(prefix, key, ks, vs, maxf)
101}
102func cel_field(ks: *i64, vs: *i64, nf: i64, key: *u8) -> *u8 { return sv_get(ks, vs, nf, key) }
103func cel_field_n(ks: *i64, vs: *i64, nf: i64, key: *u8, dflt: i64) -> i64 { return se_field_n(ks, vs, nf, key, dflt) }
104
105// put (or re-state) a cell. Every refusal is a NAMED code; nothing is written on refusal.
106func cel_put(prefix: *u8, id: *u8, kind: *u8, name: *u8, parent: *u8, denom: i64, kinds: *u8) -> i64 {
107 if cel_id_ok(id) == 0 { return CEL_ERR_ID }
108 if cel_kind_ok(kind, kinds) == 0 { return CEL_ERR_KIND }
109 if se_slen(name) < 1 { return CEL_ERR_NAME }
110 if denom < 0 { return CEL_ERR_DENOM }
111 if se_seq(parent, CEL_ROOT) == 0 {
112 if cel_id_ok(parent) == 0 { return CEL_ERR_PARENT }
113 if se_seq(parent, id) == 1 { return CEL_ERR_PARENT }
114 let pks: *i64 = sys_mmap(CEL_WORD * CEL_FIELDS) as *i64
115 let pvs: *i64 = sys_mmap(CEL_WORD * CEL_FIELDS) as *i64
116 if cel_get(prefix, parent, pks, pvs, CEL_FIELDS) < 0 { return CEL_ERR_PARENT }
117 }
118 let nowb: *u8 = sys_mmap(CEL_NUM_BYTES)
119 se_catn(nowb, 0, sys_now_us())
120 let denomb: *u8 = sys_mmap(CEL_NUM_BYTES)
121 se_catn(denomb, 0, denom)
122 let ks: *i64 = sys_mmap(CEL_WORD * CEL_FIELDS) as *i64
123 let vs: *i64 = sys_mmap(CEL_WORD * CEL_FIELDS) as *i64
124 var nf: i64 = 0
125 ks[nf] = "type" as *u8 as i64; vs[nf] = "cell" as *u8 as i64; nf = nf + 1
126 ks[nf] = "id" as *u8 as i64; vs[nf] = id as i64; nf = nf + 1
127 ks[nf] = "kind" as *u8 as i64; vs[nf] = kind as i64; nf = nf + 1
128 ks[nf] = "name" as *u8 as i64; vs[nf] = name as i64; nf = nf + 1
129 ks[nf] = "parent" as *u8 as i64; vs[nf] = parent as i64; nf = nf + 1
130 ks[nf] = "denom" as *u8 as i64; vs[nf] = denomb as i64; nf = nf + 1
131 ks[nf] = "usec" as *u8 as i64; vs[nf] = nowb as i64; nf = nf + 1
132 let rec: *u8 = sys_mmap(SE_MAGIC_4096)
133 let rl: i64 = canon_encode(ks, vs, nf, rec)
134 let w: *i64 = ss_begin()
135 let key: *u8 = sys_mmap(CEL_KEY_CAP)
136 cel_key(id, key)
137 if ss_add(w, 1, key, rec, rl) != 0 { return CEL_ERR_STORE }
138 if ss_commit(prefix, w, sys_now_us()) != 0 { return CEL_ERR_STORE }
139 return CEL_OK
140}
141// every live cell (latest version, tombstones squeezed). Returns count, or LOUD -1 past the walk bound.
142func cel_list(prefix: *u8, kout: *i64, vout: *i64, lout: *i64, cap: i64) -> i64 {
143 let n: i64 = se_walk_latest(prefix, "cell:" as *u8, kout, vout, lout, cap)
144 if n < 0 { return 0 - 1 }
145 var m: i64 = 0
146 var i: i64 = 0
147 while i < n {
148 if vout[i] != 0 {
149 kout[m] = kout[i]
150 vout[m] = vout[i]
151 lout[m] = lout[i]
152 m = m + 1
153 }
154 i = i + 1
155 }
156 return m
157}