nx_communitypulse.nx source
↩ module page · 892 lines · 48468 B
1// nx_communitypulse.nx -- COMMUNITY PULSE: an anonymous how-are-things-going pulse for a congregation, an
2// agency or a nonprofit (MICRO), rolled up to locality, region and nation under one k-anonymity floor with
3// complementary suppression (MACRO), on ONE open instrument. Admitted 2026-08-24 against /compare/communitypulse.
4//
5// COMPOSES, never re-implements: nx_survey_engine (specs as data, anonymous ballots, latest-wins revote),
6// nx_survey_stats (collect, isqrt, trend split, the 95pct z), nx_cell_lib (organisation and place rows),
7// nx_kfloor_lib (the floor and the complementary rule), nx_sha256 + nx_ed25519_signature (the signed export).
8// EVERY THRESHOLD IS A CONF ROW read line-anchored from argv[1]; a missing row REFUSES by name, never defaults.
9//
10// VERBS nx_communitypulse <conf> <verb> ...
11// cell-put <prefix> <id> <kind> <name> <parent|-> <denom> cp_org_scope
12// cell-list <prefix>
13// instrument <prefix> <specfile> <instrument> <cell> cp_instrument (spec rows -> a survey at that cell)
14// kiosk <prefix> <instrument> <cell> <qid=val>... cp_kiosk (token-less, per-cell rate row)
15// rollup <prefix> <instrument> <qid> cp_rollup + cp_complement_suppress
16// weights <prefix> <instrument> <qid> cp_weights (declared denominators, printed)
17// page <prefix> <instrument> <qid> <cell> <out.html> cp_public_page + cp_benchmark
18// alert <prefix> <instrument> <cell> <qid> cp_alert (count only, floor applies)
19// export <prefix> <instrument> <qid> <out> cp_open_export (sha256 pin + ed25519 signature)
20// verify <file> cp_verify_export
21// field <prefix> <instrument> <qid> cp_field_gate (two real cells from the conf, or abstains)
22// EXITS 0 OK 1 REFUSED (also ALERT fired, TAMPERED) 2 usage 3 UNOBSERVABLE (abstain, never acquit)
23// license_tier: ORIGINAL
24import "nx_survey_stats.nx"
25import "nx_cell_lib.nx"
26import "nx_kfloor_lib.nx"
27import "nx_lineconf_lib.nx"
28import "nx_sha256.nx"
29import "nx_ed25519_signature.nx"
30
31const CP_EXIT_OK: i64 = 0
32const CP_EXIT_REFUSED: i64 = 1
33const CP_EXIT_USAGE: i64 = 2
34const CP_EXIT_UNOBSERVABLE: i64 = 3
35const CP_CELLS: i64 = 512 // == SE_WALK_CAPMAX, the engine's walk bound (a larger cap REFUSES there)
36const CP_BALLOTS: i64 = 512 // == SE_WALK_CAPMAX
37const CP_ROW_BYTES: i64 = 512 // one emitted row or html block per cell, bounded by the name cap below
38const CP_HDR_BYTES: i64 = 8192 // page chrome + export header + trailer
39const CP_OUT_BYTES: i64 = 270336 // CP_CELLS * CP_ROW_BYTES + CP_HDR_BYTES
40const CP_NAME_CAP: i64 = 128
41const CP_PATH_CAP: i64 = 512
42const CP_SPEC_CAP: i64 = 32768 // an instrument spec; 12 items x 512-byte prompts fits with room
43const CP_FIELDS: i64 = 32
44const CP_PM: i64 = 1000 // == ST_SCALE
45const CP_PM2: i64 = 1000000 // CP_PM * CP_PM
46const CP_CENTI: i64 = 10 // pm -> hundredths for display
47const CP_SEED_HEX: i64 = 64
48const CP_SEED_BYTES: i64 = 32
49const CP_PUB_BYTES: i64 = 32
50const CP_SIG_BYTES: i64 = 64
51const CP_HASH_BYTES: i64 = 32
52const CP_HEX_CAP: i64 = 130 // 64 bytes -> 128 hex + NUL, rounded up
53const CP_STATE_PUB: *u8 = "PUBLISHED"
54const CP_STATE_K: *u8 = "WITHHELD-UNDER-K"
55const CP_STATE_COMP: *u8 = "WITHHELD-COMPLEMENTARY"
56const CP_TOK_PFX: *u8 = "kiosk-"
57const CP_ASCII_NL: i64 = 10
58const CP_ASCII_CR: i64 = 13
59const CP_ASCII_EQ: i64 = 61
60const CP_ASCII_COMMA: i64 = 44
61const CP_ASCII_BANG: i64 = 33
62const CP_ASCII_HASH: i64 = 35
63const CP_ASCII_DOT: i64 = 46
64const CP_ASCII_ZERO: i64 = 48
65const CP_ASCII_A: i64 = 97
66const CP_HEX_RADIX: i64 = 16
67const CP_HEX_LETTER: i64 = 10
68const CP_BYTE_MASK: i64 = 255
69const CP_TWO: i64 = 2
70const CP_WORD: i64 = 8 // sizeof(i64)
71const CP_ERR_WALK: i64 = 0 - 1
72const CP_ERR_PARENT: i64 = 0 - 2
73const CP_ERR_CONF: i64 = 0 - 3
74const CP_ERR_TREE: i64 = 0 - 4
75const CP_ASCII_LT: i64 = 60
76const CP_ASCII_GT: i64 = 62
77const CP_ASCII_AMP: i64 = 38
78const CP_ASCII_APOS: i64 = 39
79const CP_ASCII_PIPE: i64 = 124
80const CP_ASCII_COLON: i64 = 58
81const CP_ASCII_DASH: i64 = 45
82const CP_DECIMAL: i64 = 10
83const CP_DEC_LAST: i64 = 9
84const CP_HEX_LAST: i64 = 5
85const CP_LP_BYTES: i64 = 16 // two i64 slots for sys_read_file
86const CP_SCR_BYTES: i64 = 64
87const CP_PART_SLOTS: i64 = 4
88const CP_TREND_SLOTS: i64 = 8
89const CP_SHA_LINE_PFX: i64 = 7 // strlen("sha256=")
90
91// ---- output helpers (stdout; the gate anchors on these tokens) ----
92static cp_scr_g: *u8
93func cp_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
94func cp_pn(v: i64) -> i64 {
95 if (cp_scr_g as i64) == 0 { cp_scr_g = sys_mmap(CP_SCR_BYTES) }
96 let o: i64 = se_catn(cp_scr_g, 0, v)
97 sys_write(1, cp_scr_g, o)
98 return 0
99}
100// permille -> "d.dd"
101func cp_fixed2(dst: *u8, off: i64, pm: i64) -> i64 {
102 var o: i64 = off
103 var m: i64 = pm
104 if m < 0 { dst[o] = CP_ASCII_DASH as u8; o = o + 1; m = 0 - m }
105 o = se_catn(dst, o, m / CP_PM)
106 dst[o] = CP_ASCII_DOT as u8
107 o = o + 1
108 let c: i64 = (m % CP_PM) / CP_CENTI
109 dst[o] = (CP_ASCII_ZERO + c / CP_DECIMAL) as u8
110 dst[o + 1] = (CP_ASCII_ZERO + c % CP_DECIMAL) as u8
111 return o + CP_TWO
112}
113func cp_hex(b: *u8, n: i64, out: *u8) -> i64 {
114 var i: i64 = 0
115 while i < n {
116 let v: i64 = (b[i] as i64) & CP_BYTE_MASK
117 var hi: i64 = v / CP_HEX_RADIX
118 var lo: i64 = v % CP_HEX_RADIX
119 if hi < CP_HEX_LETTER { hi = CP_ASCII_ZERO + hi } else { hi = CP_ASCII_A + hi - CP_HEX_LETTER }
120 if lo < CP_HEX_LETTER { lo = CP_ASCII_ZERO + lo } else { lo = CP_ASCII_A + lo - CP_HEX_LETTER }
121 out[i * CP_TWO] = hi as u8
122 out[i * CP_TWO + 1] = lo as u8
123 i = i + 1
124 }
125 out[n * CP_TWO] = 0 as u8
126 return n * CP_TWO
127}
128func cp_hexval(c: i64) -> i64 {
129 if c >= CP_ASCII_ZERO { if c <= CP_ASCII_ZERO + CP_DEC_LAST { return c - CP_ASCII_ZERO } }
130 if c >= CP_ASCII_A { if c <= CP_ASCII_A + CP_HEX_LAST { return c - CP_ASCII_A + CP_HEX_LETTER } }
131 return 0 - 1
132}
133func cp_unhex(s: *u8, nbytes: i64, out: *u8) -> i64 {
134 var i: i64 = 0
135 while i < nbytes {
136 let h: i64 = cp_hexval(s[i * CP_TWO] as i64)
137 let l: i64 = cp_hexval(s[i * CP_TWO + 1] as i64)
138 if h < 0 { return 0 }
139 if l < 0 { return 0 }
140 out[i] = (h * CP_HEX_RADIX + l) as u8
141 i = i + 1
142 }
143 return 1
144}
145func cp_write_file(path: *u8, buf: *u8, n: i64) -> i64 {
146 let fd: i64 = sys_openat_wr(path, MODE_0644)
147 if fd < 0 { return 0 - 1 }
148 let wr: i64 = sys_write(fd, buf, n)
149 sys_close(fd)
150 if wr != n { return 0 - 1 }
151 return n
152}
153
154// ---- conf: every threshold, read on demand, refusing on absence ----
155static cp_conf_g: *u8
156static cp_confn_g: i64
157func cp_conf_load(path: *u8) -> i64 {
158 let lp: *i64 = sys_mmap(CP_LP_BYTES) as *i64
159 let b: *u8 = sys_read_file(path, lp)
160 if (b as i64) == 0 { return 0 }
161 cp_conf_g = b
162 cp_confn_g = lp[0]
163 return 1
164}
165// integer row or REFUSE by name; returns LCF_MISS after printing so every caller can exit
166func cp_conf_i(key: *u8) -> i64 {
167 let v: i64 = lcf_int(cp_conf_g, cp_confn_g, key)
168 if v == LCF_MISS { cp_p("REFUSED-CONF-ROW-ABSENT key=" as *u8); cp_p(key); cp_p("\n" as *u8) }
169 return v
170}
171func cp_conf_s(key: *u8, out: *u8, cap: i64) -> i64 { return lcf_str(cp_conf_g, cp_confn_g, key, out, cap) }
172
173// ---- the model: cells + per-cell measurements + the release, parallel arrays ----
174static cp_nc_g: i64
175static cp_cid_g: *i64
176static cp_ckind_g: *i64
177static cp_cname_g: *i64
178static cp_cparent_g: *i64
179static cp_cpar_g: *i64
180static cp_cdenom_g: *i64
181static cp_own_g: *i64
182static cp_osum_g: *i64
183static cp_osq_g: *i64
184static cp_tot_g: *i64
185static cp_tsum_g: *i64
186static cp_tsq_g: *i64
187static cp_state_g: *i64
188static cp_k_g: i64
189static cp_bk_g: *i64
190static cp_bv_g: *i64
191static cp_bl_g: *i64
192static cp_xs_g: *i64
193func cp_model_alloc() -> i64 {
194 if (cp_cid_g as i64) != 0 { return 0 }
195 cp_cid_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
196 cp_ckind_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
197 cp_cname_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
198 cp_cparent_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
199 cp_cpar_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
200 cp_cdenom_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
201 cp_own_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
202 cp_osum_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
203 cp_osq_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
204 cp_tot_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
205 cp_tsum_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
206 cp_tsq_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
207 cp_state_g = sys_mmap(CP_WORD * CP_CELLS) as *i64
208 cp_bk_g = sys_mmap(CP_WORD * CP_BALLOTS) as *i64
209 cp_bv_g = sys_mmap(CP_WORD * CP_BALLOTS) as *i64
210 cp_bl_g = sys_mmap(CP_WORD * CP_BALLOTS) as *i64
211 cp_xs_g = sys_mmap(CP_WORD * CP_BALLOTS) as *i64
212 return 0
213}
214func cp_cell_index(id: *u8) -> i64 {
215 var i: i64 = 0
216 while i < cp_nc_g { if se_seq(cp_cid_g[i] as *u8, id) == 1 { return i } i = i + 1 }
217 return 0 - 1
218}
219// load every cell row; resolve parents to indices. Returns cell count, or a negative after printing why.
220func cp_load_cells(prefix: *u8) -> i64 {
221 cp_model_alloc()
222 let kout: *i64 = sys_mmap(CP_WORD * CP_CELLS) as *i64
223 let vout: *i64 = sys_mmap(CP_WORD * CP_CELLS) as *i64
224 let lout: *i64 = sys_mmap(CP_WORD * CP_CELLS) as *i64
225 let n: i64 = cel_list(prefix, kout, vout, lout, CP_CELLS)
226 if n < 0 { cp_p("REFUSED-WALK-BOUND cells exceed the store walk cap; compact the plane\n" as *u8); return CP_ERR_WALK }
227 var i: i64 = 0
228 var m: i64 = 0
229 while i < n {
230 let ks: *i64 = sys_mmap(CP_WORD * CP_FIELDS) as *i64
231 let vs: *i64 = sys_mmap(CP_WORD * CP_FIELDS) as *i64
232 let nf: i64 = canon_decode(vout[i] as *u8, lout[i], ks, vs, CP_FIELDS)
233 if nf > 0 {
234 let id: *u8 = cel_field(ks, vs, nf, "id" as *u8)
235 let kind: *u8 = cel_field(ks, vs, nf, "kind" as *u8)
236 let name: *u8 = cel_field(ks, vs, nf, "name" as *u8)
237 let parent: *u8 = cel_field(ks, vs, nf, "parent" as *u8)
238 if (id as i64) != 0 { if (kind as i64) != 0 { if (name as i64) != 0 { if (parent as i64) != 0 {
239 cp_cid_g[m] = id as i64
240 cp_ckind_g[m] = kind as i64
241 cp_cname_g[m] = name as i64
242 cp_cparent_g[m] = parent as i64
243 cp_cdenom_g[m] = cel_field_n(ks, vs, nf, "denom" as *u8, 0)
244 m = m + 1
245 } } } }
246 }
247 i = i + 1
248 }
249 cp_nc_g = m
250 i = 0
251 while i < m {
252 let par: *u8 = cp_cparent_g[i] as *u8
253 if se_seq(par, CEL_ROOT) == 1 { cp_cpar_g[i] = KFL_NOPARENT } else {
254 let pi: i64 = cp_cell_index(par)
255 if pi < 0 {
256 cp_p("REFUSED-UNKNOWN-PARENT cell=" as *u8); cp_p(cp_cid_g[i] as *u8)
257 cp_p(" parent=" as *u8); cp_p(par); cp_p("\n" as *u8)
258 return CP_ERR_PARENT
259 }
260 cp_cpar_g[i] = pi
261 }
262 i = i + 1
263 }
264 return m
265}
266// ballots of one cell's survey for the instrument: fills cp_b*_g, returns count (0 when no survey) or -1 LOUD
267func cp_cell_ballots(prefix: *u8, inst: *u8, id: *u8) -> i64 {
268 let sid: *u8 = sys_mmap(CEL_KEY_CAP)
269 cel_survey_id(inst, id, sid)
270 return se_ballots(prefix, sid, cp_bk_g, cp_bv_g, cp_bl_g, CP_BALLOTS)
271}
272// the floor for this run: the conf row, raised to the highest @kmin any measured survey declares
273func cp_floor(prefix: *u8, inst: *u8) -> i64 {
274 var k: i64 = cp_conf_i("kmin" as *u8)
275 if k == LCF_MISS { return LCF_MISS }
276 var i: i64 = 0
277 while i < cp_nc_g {
278 let sid: *u8 = sys_mmap(CEL_KEY_CAP)
279 cel_survey_id(inst, cp_cid_g[i] as *u8, sid)
280 let mks: *i64 = sys_mmap(CP_WORD * CP_FIELDS) as *i64
281 let mvs: *i64 = sys_mmap(CP_WORD * CP_FIELDS) as *i64
282 let mnf: i64 = se_meta(prefix, sid, mks, mvs, CP_FIELDS)
283 if mnf > 0 {
284 let sk: i64 = se_field_n(mks, mvs, mnf, "kmin" as *u8, 0)
285 if sk > k { k = sk }
286 }
287 i = i + 1
288 }
289 return k
290}
291// measure every cell (own n, sum, sum of squares for qid), then release. Returns published count or negative.
292func cp_model(prefix: *u8, inst: *u8, qid: *u8) -> i64 {
293 let nc: i64 = cp_load_cells(prefix)
294 if nc < 0 { return nc }
295 if nc == 0 { return 0 }
296 let k: i64 = cp_floor(prefix, inst)
297 if k == LCF_MISS { return CP_ERR_CONF }
298 cp_k_g = k
299 var i: i64 = 0
300 while i < nc {
301 let nb: i64 = cp_cell_ballots(prefix, inst, cp_cid_g[i] as *u8)
302 if nb < 0 { cp_p("REFUSED-WALK-BOUND ballots exceed the store walk cap; compact the plane\n" as *u8); return CP_ERR_WALK }
303 let m: i64 = st_collect(cp_bv_g, cp_bl_g, nb, qid, cp_xs_g)
304 var s: i64 = 0
305 var sq: i64 = 0
306 var j: i64 = 0
307 while j < m { s = s + cp_xs_g[j]; sq = sq + cp_xs_g[j] * cp_xs_g[j]; j = j + 1 }
308 cp_own_g[i] = m
309 cp_osum_g[i] = s
310 cp_osq_g[i] = sq
311 cp_tsum_g[i] = 0
312 cp_tsq_g[i] = 0
313 i = i + 1
314 }
315 let pub: i64 = kfl_release(cp_own_g, cp_cpar_g, nc, k, cp_tot_g, cp_state_g)
316 if pub < 0 { cp_p("REFUSED-TREE the cell hierarchy is not a forest\n" as *u8); return CP_ERR_TREE }
317 // subtree sums, same walk the lib uses for counts
318 i = 0
319 while i < nc {
320 var j2: i64 = i
321 var depth: i64 = 0
322 while j2 != KFL_NOPARENT {
323 cp_tsum_g[j2] = cp_tsum_g[j2] + cp_osum_g[i]
324 cp_tsq_g[j2] = cp_tsq_g[j2] + cp_osq_g[i]
325 j2 = cp_cpar_g[j2]
326 depth = depth + 1
327 if depth > nc { return CP_ERR_TREE }
328 }
329 i = i + 1
330 }
331 return pub
332}
333func cp_mean_pm(i: i64) -> i64 { if cp_tot_g[i] <= 0 { return 0 } return cp_tsum_g[i] * CP_PM / cp_tot_g[i] }
334// 95pct margin of a mean in permille: z * sqrt(var / n); var from the sum of squares, all integer
335func cp_margin_pm(i: i64) -> i64 {
336 let n: i64 = cp_tot_g[i]
337 if n <= 0 { return 0 }
338 let mean: i64 = cp_mean_pm(i)
339 var var_pm2: i64 = cp_tsq_g[i] * CP_PM2 / n - mean * mean
340 if var_pm2 < 0 { var_pm2 = 0 }
341 return st_isqrt(ST_Z95SQ * var_pm2 / n) / CP_PM
342}
343func cp_state_str(i: i64) -> *u8 {
344 if cp_state_g[i] == KFL_PUB { return CP_STATE_PUB }
345 if cp_state_g[i] == KFL_WITHHELD_COMP { return CP_STATE_COMP }
346 return CP_STATE_K
347}
348// the two contract names the board watches, as the functions the organ actually calls
349func cp_complement_suppress(k: i64) -> i64 { return kfl_complement_suppress(cp_tot_g, cp_cpar_g, cp_nc_g, k, cp_state_g) }
350func cp_rollup(prefix: *u8, inst: *u8, qid: *u8) -> i64 {
351 let pub: i64 = cp_model(prefix, inst, qid)
352 if pub < 0 { return CP_EXIT_REFUSED }
353 if cp_nc_g == 0 { cp_p("UNOBSERVABLE cells=0 -- no cell rows on this plane\n" as *u8); return CP_EXIT_UNOBSERVABLE }
354 // the fixpoint is re-run here so the contract symbol is on the executed path, not only inside the lib
355 cp_complement_suppress(cp_k_g)
356 cp_p("NX-COMMUNITYPULSE rollup instrument=" as *u8); cp_p(inst)
357 cp_p(" qid=" as *u8); cp_p(qid)
358 cp_p(" k=" as *u8); cp_pn(cp_k_g)
359 cp_p(" cells=" as *u8); cp_pn(cp_nc_g); cp_p("\n" as *u8)
360 var i: i64 = 0
361 var own_sum: i64 = 0
362 var roots_total: i64 = 0
363 while i < cp_nc_g {
364 cp_p("row|" as *u8); cp_p(cp_cid_g[i] as *u8)
365 cp_p("|" as *u8); cp_p(cp_ckind_g[i] as *u8)
366 cp_p("|" as *u8); cp_p(cp_cparent_g[i] as *u8)
367 cp_p("|own=" as *u8); cp_pn(cp_own_g[i])
368 cp_p("|total=" as *u8); cp_pn(cp_tot_g[i])
369 cp_p("|" as *u8); cp_p(cp_state_str(i))
370 if cp_state_g[i] == KFL_PUB {
371 cp_p("|mean_pm=" as *u8); cp_pn(cp_mean_pm(i))
372 cp_p("|margin_pm=" as *u8); cp_pn(cp_margin_pm(i))
373 } else { cp_p("|mean_pm=-|margin_pm=-" as *u8) }
374 cp_p("\n" as *u8)
375 own_sum = own_sum + cp_own_g[i]
376 if cp_cpar_g[i] == KFL_NOPARENT { roots_total = roots_total + cp_tot_g[i] }
377 i = i + 1
378 }
379 let part: *i64 = sys_mmap(CP_WORD * CP_PART_SLOTS) as *i64
380 let sums: i64 = kfl_partition(cp_state_g, cp_nc_g, part)
381 cp_p("partition published=" as *u8); cp_pn(part[0])
382 cp_p(" withheld_k=" as *u8); cp_pn(part[1])
383 cp_p(" withheld_comp=" as *u8); cp_pn(part[2])
384 cp_p(" sum=" as *u8); cp_pn(part[0] + part[1] + part[2])
385 cp_p(" cells=" as *u8); cp_pn(cp_nc_g)
386 if sums == 1 { cp_p(" SUMS\n" as *u8) } else { cp_p(" LEAK\n" as *u8) }
387 cp_p("ballots own_sum=" as *u8); cp_pn(own_sum)
388 cp_p(" roots_total=" as *u8); cp_pn(roots_total)
389 if own_sum == roots_total { cp_p(" SUMS\n" as *u8) } else { cp_p(" LEAK\n" as *u8) }
390 if sums == 0 { return CP_EXIT_REFUSED }
391 if own_sum != roots_total { return CP_EXIT_REFUSED }
392 return CP_EXIT_OK
393}
394// is j a descendant of i (or i itself)
395func cp_under(j: i64, i: i64) -> i64 {
396 var x: i64 = j
397 var depth: i64 = 0
398 while x != KFL_NOPARENT {
399 if x == i { return 1 }
400 x = cp_cpar_g[x]
401 depth = depth + 1
402 if depth > cp_nc_g { return 0 }
403 }
404 return 0
405}
406// post-stratification: a published cell's weighted mean over its contributing organisation cells, each weighted by
407// its DECLARED denominator; a contributing cell with no denominator REFUSES the whole verb by name (fail closed).
408func cp_weights(prefix: *u8, inst: *u8, qid: *u8) -> i64 {
409 let pub: i64 = cp_model(prefix, inst, qid)
410 if pub < 0 { return CP_EXIT_REFUSED }
411 if cp_nc_g == 0 { cp_p("UNOBSERVABLE cells=0\n" as *u8); return CP_EXIT_UNOBSERVABLE }
412 cp_p("NX-COMMUNITYPULSE weights instrument=" as *u8); cp_p(inst)
413 cp_p(" qid=" as *u8); cp_p(qid); cp_p(" k=" as *u8); cp_pn(cp_k_g); cp_p("\n" as *u8)
414 var i: i64 = 0
415 while i < cp_nc_g {
416 if cp_state_g[i] == KFL_PUB {
417 var wsum: i64 = 0
418 var dsum: i64 = 0
419 var leaves: i64 = 0
420 var j: i64 = 0
421 while j < cp_nc_g {
422 if cp_own_g[j] > 0 { if cp_under(j, i) == 1 {
423 if cp_cdenom_g[j] <= 0 {
424 cp_p("REFUSED-NO-DENOMINATOR cell=" as *u8); cp_p(cp_cid_g[j] as *u8)
425 cp_p(" under=" as *u8); cp_p(cp_cid_g[i] as *u8); cp_p("\n" as *u8)
426 return CP_EXIT_REFUSED
427 }
428 let mean_j: i64 = cp_osum_g[j] * CP_PM / cp_own_g[j]
429 wsum = wsum + mean_j * cp_cdenom_g[j]
430 dsum = dsum + cp_cdenom_g[j]
431 leaves = leaves + 1
432 } }
433 j = j + 1
434 }
435 cp_p("weighted|" as *u8); cp_p(cp_cid_g[i] as *u8)
436 cp_p("|PUBLISHED|wmean_pm=" as *u8)
437 if dsum > 0 { cp_pn(wsum / dsum) } else { cp_p("-" as *u8) }
438 cp_p("|denom_sum=" as *u8); cp_pn(dsum)
439 cp_p("|leaves=" as *u8); cp_pn(leaves); cp_p("\n" as *u8)
440 } else {
441 cp_p("weighted|" as *u8); cp_p(cp_cid_g[i] as *u8)
442 cp_p("|" as *u8); cp_p(cp_state_str(i)); cp_p("|-\n" as *u8)
443 }
444 i = i + 1
445 }
446 return CP_EXIT_OK
447}
448// the join: child versus the level above, both with margins. out[0]=delta_pm out[1]=0 WITHIN 1 ABOVE 2 BELOW
449func cp_benchmark(child_mean: i64, child_margin: i64, parent_mean: i64, parent_margin: i64, out: *i64) -> i64 {
450 let d: i64 = child_mean - parent_mean
451 out[0] = d
452 var ad: i64 = d
453 if ad < 0 { ad = 0 - ad }
454 if ad <= child_margin + parent_margin { out[1] = 0; return 0 }
455 if d > 0 { out[1] = 1; return 1 }
456 out[1] = 2
457 return 2
458}
459func cp_html_esc(dst: *u8, off: i64, s: *u8) -> i64 {
460 var o: i64 = off
461 var i: i64 = 0
462 while s[i] != (0 as u8) {
463 let c: i64 = s[i] as i64
464 if c == CP_ASCII_LT { o = se_cat(dst, o, "<" as *u8) } else {
465 if c == CP_ASCII_GT { o = se_cat(dst, o, ">" as *u8) } else {
466 if c == CP_ASCII_AMP { o = se_cat(dst, o, "&" as *u8) } else {
467 if c == CP_ASCII_APOS { o = se_cat(dst, o, "'" as *u8) } else { dst[o] = s[i]; o = o + 1 } } } }
468 i = i + 1
469 }
470 return o
471}
472// the public page: zero-JS, the cell's number with its margin and trend beside the level above it
473func cp_public_page(prefix: *u8, inst: *u8, qid: *u8, cell: *u8, path: *u8) -> i64 {
474 let pub: i64 = cp_model(prefix, inst, qid)
475 if pub < 0 { return CP_EXIT_REFUSED }
476 let ci: i64 = cp_cell_index(cell)
477 if ci < 0 { cp_p("REFUSED-UNKNOWN-CELL cell=" as *u8); cp_p(cell); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
478 let tw: i64 = cp_conf_i("trend_window_us" as *u8)
479 if tw == LCF_MISS { return CP_EXIT_REFUSED }
480 let out: *u8 = sys_mmap(CP_OUT_BYTES)
481 var o: i64 = 0
482 o = se_catc(out, o, CP_ASCII_LT); o = se_catc(out, o, CP_ASCII_BANG)
483 o = se_cat(out, o, "doctype html><html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>How are things going: " as *u8)
484 o = cp_html_esc(out, o, cp_cname_g[ci] as *u8)
485 o = se_cat(out, o, "</title><style>body{font-family:system-ui,sans-serif;max-width:720px;margin:2rem auto;padding:0 1rem;color:rgb(24,26,32);background:rgb(250,250,252)}.card{border:1px solid rgb(220,224,232);border-radius:12px;padding:1rem 1.25rem;margin:1rem 0;background:rgb(255,255,255)}.num{font-size:2rem;font-weight:700}.meta{color:rgb(105,110,125);font-size:.9rem}.w{color:rgb(150,60,20)}</style></head><body>" as *u8)
486 o = se_cat(out, o, "<h1>How are things going: " as *u8)
487 o = cp_html_esc(out, o, cp_cname_g[ci] as *u8)
488 o = se_cat(out, o, "</h1><p class='meta'>instrument " as *u8); o = se_cat(out, o, inst)
489 o = se_cat(out, o, " · item " as *u8); o = se_cat(out, o, qid)
490 o = se_cat(out, o, " · anonymous, published only above the k-anonymity floor (k=" as *u8); o = se_catn(out, o, cp_k_g)
491 o = se_cat(out, o, ")</p><div class='card' data-cell='" as *u8); o = se_cat(out, o, cell)
492 o = se_cat(out, o, "' data-kind='" as *u8); o = se_cat(out, o, cp_ckind_g[ci] as *u8)
493 o = se_cat(out, o, "' data-n='" as *u8); o = se_catn(out, o, cp_tot_g[ci])
494 o = se_cat(out, o, "' data-state='" as *u8); o = se_cat(out, o, cp_state_str(ci)); o = se_cat(out, o, "'>" as *u8)
495 if cp_state_g[ci] == KFL_PUB {
496 let mp: i64 = cp_mean_pm(ci)
497 let mg: i64 = cp_margin_pm(ci)
498 o = se_cat(out, o, "<p class='num' data-mean-pm='" as *u8); o = se_catn(out, o, mp)
499 o = se_cat(out, o, "' data-margin-pm='" as *u8); o = se_catn(out, o, mg)
500 o = se_cat(out, o, "'>" as *u8); o = cp_fixed2(out, o, mp)
501 o = se_cat(out, o, " of 10</p><p class='meta'>" as *u8); o = se_catn(out, o, cp_tot_g[ci])
502 o = se_cat(out, o, " people · 95pct margin ±" as *u8); o = cp_fixed2(out, o, mg); o = se_cat(out, o, "</p>" as *u8)
503 // trend on this cell's own ballots, both halves under the same floor
504 if cp_own_g[ci] > 0 {
505 let nb: i64 = cp_cell_ballots(prefix, inst, cell)
506 let tr: *i64 = sys_mmap(CP_WORD * CP_TREND_SLOTS) as *i64
507 st_trend_split(cp_bv_g, cp_bl_g, nb, qid, sys_now_us() - tw, tr)
508 if tr[0] >= cp_k_g { if tr[2] >= cp_k_g {
509 o = se_cat(out, o, "<p class='meta' data-trend-pm='" as *u8); o = se_catn(out, o, tr[4])
510 o = se_cat(out, o, "'>trend: " as *u8); o = cp_fixed2(out, o, tr[1]); o = se_cat(out, o, " before → " as *u8)
511 o = cp_fixed2(out, o, tr[3]); o = se_cat(out, o, " in the window</p>" as *u8)
512 } else { o = se_cat(out, o, "<p class='meta w'>trend WITHHELD: the recent half is under the floor</p>" as *u8) } }
513 else { o = se_cat(out, o, "<p class='meta w'>trend WITHHELD: the earlier half is under the floor</p>" as *u8) }
514 }
515 } else {
516 o = se_cat(out, o, "<p class='num w'>WITHHELD</p><p class='meta'>" as *u8)
517 if cp_state_g[ci] == KFL_WITHHELD_K { o = se_cat(out, o, "fewer than k people have answered, so no number can be shown without risking someone's privacy" as *u8) }
518 else { o = se_cat(out, o, "publishing this level would let the number of a smaller group beneath it be worked out by subtraction, so it is withheld" as *u8) }
519 o = se_cat(out, o, "</p>" as *u8)
520 }
521 o = se_cat(out, o, "</div>" as *u8)
522 // the level above
523 let pi: i64 = cp_cpar_g[ci]
524 if pi != KFL_NOPARENT {
525 o = se_cat(out, o, "<div class='card' data-parent='" as *u8); o = se_cat(out, o, cp_cid_g[pi] as *u8)
526 o = se_cat(out, o, "' data-parent-state='" as *u8); o = se_cat(out, o, cp_state_str(pi)); o = se_cat(out, o, "'><p class='meta'>the level above: " as *u8)
527 o = cp_html_esc(out, o, cp_cname_g[pi] as *u8); o = se_cat(out, o, " (" as *u8); o = se_cat(out, o, cp_ckind_g[pi] as *u8); o = se_cat(out, o, ")</p>" as *u8)
528 if cp_state_g[pi] == KFL_PUB {
529 let pmp: i64 = cp_mean_pm(pi)
530 let pmg: i64 = cp_margin_pm(pi)
531 o = se_cat(out, o, "<p class='num'>" as *u8); o = cp_fixed2(out, o, pmp)
532 o = se_cat(out, o, " of 10</p><p class='meta'>" as *u8); o = se_catn(out, o, cp_tot_g[pi])
533 o = se_cat(out, o, " people · margin ±" as *u8); o = cp_fixed2(out, o, pmg); o = se_cat(out, o, "</p>" as *u8)
534 if cp_state_g[ci] == KFL_PUB {
535 let bm: *i64 = sys_mmap(CP_WORD * CP_PART_SLOTS) as *i64
536 cp_benchmark(cp_mean_pm(ci), cp_margin_pm(ci), pmp, pmg, bm)
537 o = se_cat(out, o, "<p class='meta' data-benchmark='" as *u8)
538 if bm[1] == 0 { o = se_cat(out, o, "WITHIN" as *u8) } else { if bm[1] == 1 { o = se_cat(out, o, "ABOVE" as *u8) } else { o = se_cat(out, o, "BELOW" as *u8) } }
539 o = se_cat(out, o, "' data-delta-pm='" as *u8); o = se_catn(out, o, bm[0]); o = se_cat(out, o, "'>compared with the level above: " as *u8)
540 if bm[1] == 0 { o = se_cat(out, o, "within the margins (no measurable difference)" as *u8) } else {
541 if bm[1] == 1 { o = se_cat(out, o, "above by " as *u8); o = cp_fixed2(out, o, bm[0]) } else { o = se_cat(out, o, "below by " as *u8); o = cp_fixed2(out, o, 0 - bm[0]) } }
542 o = se_cat(out, o, "</p>" as *u8)
543 } else { o = se_cat(out, o, "<p class='meta' data-benchmark='UNAVAILABLE'>no comparison: this level is withheld</p>" as *u8) }
544 } else { o = se_cat(out, o, "<p class='num w'>WITHHELD</p><p class='meta' data-benchmark='UNAVAILABLE'>the level above is withheld under the same floor, so there is no number to compare with</p>" as *u8) }
545 o = se_cat(out, o, "</div>" as *u8)
546 } else { o = se_cat(out, o, "<p class='meta' data-benchmark='ROOT'>this is the top level</p>" as *u8) }
547 o = se_cat(out, o, "<p class='meta'>Every number on this page is an aggregate over at least k people, and the free-text answers never appear here. Emitted by nx_communitypulse.</p></body></html>\n" as *u8)
548 if cp_write_file(path, out, o) < 0 { cp_p("REFUSED-WRITE path=" as *u8); cp_p(path); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
549 cp_p("PAGE cell=" as *u8); cp_p(cell); cp_p(" state=" as *u8); cp_p(cp_state_str(ci))
550 cp_p(" bytes=" as *u8); cp_pn(o); cp_p(" path=" as *u8); cp_p(path); cp_p("\n" as *u8)
551 return CP_EXIT_OK
552}
553// crisis alert: count of LOW answers at one cell, reported only above the floor, never a person
554func cp_alert(prefix: *u8, inst: *u8, cell: *u8, qid: *u8) -> i64 {
555 cp_model_alloc()
556 let k: i64 = cp_conf_i("kmin" as *u8)
557 if k == LCF_MISS { return CP_EXIT_REFUSED }
558 let low_max: i64 = cp_conf_i("alert_low_max" as *u8)
559 if low_max == LCF_MISS { return CP_EXIT_REFUSED }
560 let count: i64 = cp_conf_i("alert_count" as *u8)
561 if count == LCF_MISS { return CP_EXIT_REFUSED }
562 let nb: i64 = cp_cell_ballots(prefix, inst, cell)
563 if nb < 0 { cp_p("REFUSED-WALK-BOUND\n" as *u8); return CP_EXIT_REFUSED }
564 let m: i64 = st_collect(cp_bv_g, cp_bl_g, nb, qid, cp_xs_g)
565 if m < k {
566 cp_p("ALERT-UNOBSERVABLE cell=" as *u8); cp_p(cell); cp_p(" n=" as *u8); cp_pn(m)
567 cp_p(" k=" as *u8); cp_pn(k); cp_p(" -- under the floor, no count is published\n" as *u8)
568 return CP_EXIT_UNOBSERVABLE
569 }
570 var low: i64 = 0
571 var i: i64 = 0
572 while i < m { if cp_xs_g[i] <= low_max { low = low + 1 } i = i + 1 }
573 if low >= count { cp_p("ALERT cell=" as *u8) } else { cp_p("NO-ALERT cell=" as *u8) }
574 cp_p(cell); cp_p(" low=" as *u8); cp_pn(low); cp_p(" n=" as *u8); cp_pn(m)
575 cp_p(" low_max=" as *u8); cp_pn(low_max); cp_p(" count=" as *u8); cp_pn(count); cp_p("\n" as *u8)
576 if low >= count { return CP_EXIT_REFUSED }
577 return CP_EXIT_OK
578}
579// the export body: header + one row per cell; withheld rows carry no numbers
580func cp_export_body(inst: *u8, qid: *u8, out: *u8) -> i64 {
581 var o: i64 = se_cat(out, 0, "NXCPX1|instrument=" as *u8)
582 o = se_cat(out, o, inst); o = se_cat(out, o, "|qid=" as *u8); o = se_cat(out, o, qid)
583 o = se_cat(out, o, "|k=" as *u8); o = se_catn(out, o, cp_k_g)
584 o = se_cat(out, o, "|cells=" as *u8); o = se_catn(out, o, cp_nc_g)
585 o = se_cat(out, o, "|asof=" as *u8); o = se_catn(out, o, sys_now_us()); o = se_catc(out, o, CP_ASCII_NL)
586 var i: i64 = 0
587 while i < cp_nc_g {
588 o = se_cat(out, o, "row|" as *u8); o = se_cat(out, o, cp_cid_g[i] as *u8)
589 o = se_catc(out, o, CP_ASCII_PIPE); o = se_cat(out, o, cp_ckind_g[i] as *u8)
590 o = se_catc(out, o, CP_ASCII_PIPE); o = se_cat(out, o, cp_cparent_g[i] as *u8)
591 o = se_catc(out, o, CP_ASCII_PIPE); o = se_cat(out, o, cp_state_str(i))
592 if cp_state_g[i] == KFL_PUB {
593 o = se_cat(out, o, "|total=" as *u8); o = se_catn(out, o, cp_tot_g[i])
594 o = se_cat(out, o, "|mean_pm=" as *u8); o = se_catn(out, o, cp_mean_pm(i))
595 o = se_cat(out, o, "|margin_pm=" as *u8); o = se_catn(out, o, cp_margin_pm(i))
596 } else { o = se_cat(out, o, "|total=-|mean_pm=-|margin_pm=-" as *u8) }
597 o = se_catc(out, o, CP_ASCII_NL)
598 i = i + 1
599 }
600 return o
601}
602func cp_open_export(prefix: *u8, inst: *u8, qid: *u8, path: *u8) -> i64 {
603 let seedpath: *u8 = sys_mmap(CP_PATH_CAP)
604 if cp_conf_s("export_seed" as *u8, seedpath, CP_PATH_CAP) <= 0 { cp_p("REFUSED-NO-KEY conf row export_seed is absent; an unsigned export is never written\n" as *u8); return CP_EXIT_REFUSED }
605 let lp: *i64 = sys_mmap(CP_LP_BYTES) as *i64
606 let sb: *u8 = sys_read_file(seedpath, lp)
607 if (sb as i64) == 0 { cp_p("REFUSED-NO-KEY seed file unreadable path=" as *u8); cp_p(seedpath); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
608 if lp[0] < CP_SEED_HEX { cp_p("REFUSED-NO-KEY seed file shorter than 64 hex\n" as *u8); return CP_EXIT_REFUSED }
609 let seed: *u8 = sys_mmap(CP_SEED_BYTES)
610 if cp_unhex(sb, CP_SEED_BYTES, seed) == 0 { cp_p("REFUSED-NO-KEY seed file is not hex\n" as *u8); return CP_EXIT_REFUSED }
611 let pub: i64 = cp_model(prefix, inst, qid)
612 if pub < 0 { return CP_EXIT_REFUSED }
613 if cp_nc_g == 0 { cp_p("UNOBSERVABLE cells=0\n" as *u8); return CP_EXIT_UNOBSERVABLE }
614 let out: *u8 = sys_mmap(CP_OUT_BYTES)
615 let blen: i64 = cp_export_body(inst, qid, out)
616 let dg: *u8 = sys_mmap(CP_HASH_BYTES)
617 sha256_digest(out, blen, dg)
618 let dh: *u8 = sys_mmap(CP_HEX_CAP)
619 cp_hex(dg, CP_HASH_BYTES, dh)
620 let pk: *u8 = sys_mmap(CP_PUB_BYTES)
621 if ed25519_pub_from_priv(seed, pk) != 0 { cp_p("REFUSED-KEY derivation failed\n" as *u8); return CP_EXIT_REFUSED }
622 let sig: *u8 = sys_mmap(CP_SIG_BYTES)
623 if ed25519_sign_full(seed, out, blen, sig) != 0 { cp_p("REFUSED-SIGN\n" as *u8); return CP_EXIT_REFUSED }
624 let ph: *u8 = sys_mmap(CP_HEX_CAP)
625 cp_hex(pk, CP_PUB_BYTES, ph)
626 let sh: *u8 = sys_mmap(CP_HEX_CAP)
627 cp_hex(sig, CP_SIG_BYTES, sh)
628 var o: i64 = blen
629 o = se_cat(out, o, "sha256=" as *u8); o = se_cat(out, o, dh); o = se_catc(out, o, CP_ASCII_NL)
630 o = se_cat(out, o, "pub=" as *u8); o = se_cat(out, o, ph); o = se_catc(out, o, CP_ASCII_NL)
631 o = se_cat(out, o, "sig=" as *u8); o = se_cat(out, o, sh); o = se_catc(out, o, CP_ASCII_NL)
632 if cp_write_file(path, out, o) < 0 { cp_p("REFUSED-WRITE path=" as *u8); cp_p(path); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
633 cp_p("EXPORT path=" as *u8); cp_p(path); cp_p(" bytes=" as *u8); cp_pn(o)
634 cp_p(" rows=" as *u8); cp_pn(cp_nc_g); cp_p(" sha256=" as *u8); cp_p(dh); cp_p(" pub=" as *u8); cp_p(ph); cp_p("\n" as *u8)
635 return CP_EXIT_OK
636}
637// verify an export: body = everything before the line that starts "sha256="; pin and signature both must hold
638func cp_verify_export(path: *u8) -> i64 {
639 let lp: *i64 = sys_mmap(CP_LP_BYTES) as *i64
640 let b: *u8 = sys_read_file(path, lp)
641 if (b as i64) == 0 { cp_p("MALFORMED unreadable path=" as *u8); cp_p(path); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
642 let n: i64 = lp[0]
643 let bo: i64 = lcf_find(b, n, "sha256" as *u8)
644 if bo < 0 { cp_p("MALFORMED no sha256 line\n" as *u8); return CP_EXIT_REFUSED }
645 let blen: i64 = bo - CP_SHA_LINE_PFX // start of the "sha256=" line
646 let dg: *u8 = sys_mmap(CP_HASH_BYTES)
647 sha256_digest(b, blen, dg)
648 let dh: *u8 = sys_mmap(CP_HEX_CAP)
649 cp_hex(dg, CP_HASH_BYTES, dh)
650 let pin: *u8 = sys_mmap(CP_HEX_CAP)
651 lcf_str(b, n, "sha256" as *u8, pin, CP_HEX_CAP)
652 let ph: *u8 = sys_mmap(CP_HEX_CAP)
653 if lcf_str(b, n, "pub" as *u8, ph, CP_HEX_CAP) != CP_PUB_BYTES * CP_TWO { cp_p("MALFORMED pub line\n" as *u8); return CP_EXIT_REFUSED }
654 let sh: *u8 = sys_mmap(CP_HEX_CAP)
655 if lcf_str(b, n, "sig" as *u8, sh, CP_HEX_CAP) != CP_SIG_BYTES * CP_TWO { cp_p("MALFORMED sig line\n" as *u8); return CP_EXIT_REFUSED }
656 let pk: *u8 = sys_mmap(CP_PUB_BYTES)
657 let sig: *u8 = sys_mmap(CP_SIG_BYTES)
658 if cp_unhex(ph, CP_PUB_BYTES, pk) == 0 { cp_p("MALFORMED pub hex\n" as *u8); return CP_EXIT_REFUSED }
659 if cp_unhex(sh, CP_SIG_BYTES, sig) == 0 { cp_p("MALFORMED sig hex\n" as *u8); return CP_EXIT_REFUSED }
660 var pin_ok: i64 = 0
661 if se_seq(dh, pin) == 1 { pin_ok = 1 }
662 var sig_ok: i64 = 0
663 if ed25519_verify_full(pk, b, blen, sig) == NX_ED25519_SIG_OK { sig_ok = 1 }
664 if pin_ok == 1 { if sig_ok == 1 {
665 cp_p("VERIFIED path=" as *u8); cp_p(path); cp_p(" body_bytes=" as *u8); cp_pn(blen); cp_p(" pub=" as *u8); cp_p(ph); cp_p("\n" as *u8)
666 return CP_EXIT_OK
667 } }
668 cp_p("TAMPERED path=" as *u8); cp_p(path); cp_p(" pin_ok=" as *u8); cp_pn(pin_ok); cp_p(" sig_ok=" as *u8); cp_pn(sig_ok); cp_p("\n" as *u8)
669 return CP_EXIT_REFUSED
670}
671// the instrument: validate the spec's licence, citation, kind and item count against the conf, then load it
672// as a survey AT THAT CELL ("<instrument>-<cell>"), anonymous, under the conf floor
673func cp_instrument(prefix: *u8, specpath: *u8, inst: *u8, cell: *u8) -> i64 {
674 cp_model_alloc()
675 if cel_inst_ok(inst) == 0 { cp_p("REFUSED-INSTRUMENT-ID a-z 0-9, at most 15\n" as *u8); return CP_EXIT_REFUSED }
676 if cel_id_ok(cell) == 0 { cp_p("REFUSED-CELL-ID a-z 0-9, at most 24\n" as *u8); return CP_EXIT_REFUSED }
677 let cks: *i64 = sys_mmap(CP_WORD * CP_FIELDS) as *i64
678 let cvs: *i64 = sys_mmap(CP_WORD * CP_FIELDS) as *i64
679 if cel_get(prefix, cell, cks, cvs, CP_FIELDS) < 0 { cp_p("REFUSED-UNKNOWN-CELL cell=" as *u8); cp_p(cell); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
680 let k: i64 = cp_conf_i("kmin" as *u8)
681 if k == LCF_MISS { return CP_EXIT_REFUSED }
682 let lp: *i64 = sys_mmap(CP_LP_BYTES) as *i64
683 let spec: *u8 = sys_read_file(specpath, lp)
684 if (spec as i64) == 0 { cp_p("REFUSED-SPEC-UNREADABLE path=" as *u8); cp_p(specpath); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
685 let sl: i64 = lp[0]
686 if sl > CP_SPEC_CAP { cp_p("REFUSED-SPEC-TOO-LARGE\n" as *u8); return CP_EXIT_REFUSED }
687 let kind: *u8 = sys_mmap(CP_NAME_CAP)
688 let lic: *u8 = sys_mmap(CP_NAME_CAP)
689 let cite: *u8 = sys_mmap(CP_ROW_BYTES)
690 kind[0] = 0 as u8
691 lic[0] = 0 as u8
692 cite[0] = 0 as u8
693 var items: i64 = 0
694 var ls: i64 = 0
695 while ls < sl {
696 var le: i64 = ls
697 while le < sl { if spec[le] == (CP_ASCII_NL as u8) { break } le = le + 1 }
698 var lee: i64 = le
699 if lee > ls { if spec[lee - 1] == (CP_ASCII_CR as u8) { lee = lee - 1 } }
700 if lee > ls {
701 if se_line_starts(spec, ls, lee, "@kind " as *u8) == 1 { se_meta_val(spec, ls, lee, se_slen("@kind " as *u8), kind, CP_NAME_CAP) } else {
702 if se_line_starts(spec, ls, lee, "@license " as *u8) == 1 { se_meta_val(spec, ls, lee, se_slen("@license " as *u8), lic, CP_NAME_CAP) } else {
703 if se_line_starts(spec, ls, lee, "@cite " as *u8) == 1 { se_meta_val(spec, ls, lee, se_slen("@cite " as *u8), cite, CP_ROW_BYTES) } else {
704 if se_line_starts(spec, ls, lee, "Q|" as *u8) == 1 { items = items + 1 } } } }
705 }
706 ls = le + 1
707 }
708 if kind[0] == (0 as u8) { cp_p("REFUSED-NO-KIND spec carries no @kind row\n" as *u8); return CP_EXIT_REFUSED }
709 if lic[0] == (0 as u8) { cp_p("REFUSED-NO-LICENSE spec carries no @license row\n" as *u8); return CP_EXIT_REFUSED }
710 if cite[0] == (0 as u8) { cp_p("REFUSED-NO-CITE spec carries no @cite row\n" as *u8); return CP_EXIT_REFUSED }
711 let ikey: *u8 = sys_mmap(CP_NAME_CAP)
712 var ko: i64 = se_cat(ikey, 0, "items_" as *u8)
713 ko = se_cat(ikey, ko, kind)
714 ikey[ko] = 0 as u8
715 let want: i64 = lcf_int(cp_conf_g, cp_confn_g, ikey)
716 if want == LCF_MISS { cp_p("REFUSED-KIND no conf row " as *u8); cp_p(ikey); cp_p(" declares this instrument kind\n" as *u8); return CP_EXIT_REFUSED }
717 if items != want {
718 cp_p("REFUSED-ITEMS kind=" as *u8); cp_p(kind); cp_p(" declared=" as *u8); cp_pn(want)
719 cp_p(" found=" as *u8); cp_pn(items); cp_p("\n" as *u8)
720 return CP_EXIT_REFUSED
721 }
722 let sid: *u8 = sys_mmap(CEL_KEY_CAP)
723 cel_survey_id(inst, cell, sid)
724 let spec2: *u8 = sys_mmap(CP_SPEC_CAP + CP_ROW_BYTES)
725 var o: i64 = 0
726 var i: i64 = 0
727 while i < sl { spec2[o] = spec[i]; o = o + 1; i = i + 1 }
728 if o > 0 { if spec2[o - 1] != (CP_ASCII_NL as u8) { o = se_catc(spec2, o, CP_ASCII_NL) } }
729 o = se_cat(spec2, o, "@survey " as *u8); o = se_cat(spec2, o, sid); o = se_catc(spec2, o, CP_ASCII_NL)
730 o = se_cat(spec2, o, "@anon 1" as *u8); o = se_catc(spec2, o, CP_ASCII_NL)
731 o = se_cat(spec2, o, "@kmin " as *u8); o = se_catn(spec2, o, k); o = se_catc(spec2, o, CP_ASCII_NL)
732 let idout: *u8 = sys_mmap(CEL_KEY_CAP)
733 let nq: i64 = se_load(prefix, spec2, o, idout, CEL_KEY_CAP)
734 if nq < 0 { cp_p("REFUSED-ENGINE se_load rc=" as *u8); cp_pn(nq); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
735 cp_p("LOADED survey=" as *u8); cp_p(idout); cp_p(" items=" as *u8); cp_pn(nq)
736 cp_p(" kind=" as *u8); cp_p(kind); cp_p(" license=" as *u8); cp_p(lic); cp_p(" k=" as *u8); cp_pn(k); cp_p("\n" as *u8)
737 return CP_EXIT_OK
738}
739// kiosk intake: token-less (a fresh token per ballot), so a per-cell rate row is the only inflation guard
740func cp_kiosk(prefix: *u8, inst: *u8, cell: *u8, argc: i64, argv: *i64, first: i64) -> i64 {
741 cp_model_alloc()
742 let rate_max: i64 = cp_conf_i("kiosk_rate_max" as *u8)
743 if rate_max == LCF_MISS { return CP_EXIT_REFUSED }
744 let window: i64 = cp_conf_i("kiosk_rate_window_us" as *u8)
745 if window == LCF_MISS { return CP_EXIT_REFUSED }
746 let now: i64 = sys_now_us()
747 let nb: i64 = cp_cell_ballots(prefix, inst, cell)
748 if nb < 0 { cp_p("REFUSED-WALK-BOUND\n" as *u8); return CP_EXIT_REFUSED }
749 let sid: *u8 = sys_mmap(CEL_KEY_CAP)
750 cel_survey_id(inst, cell, sid)
751 let kpfx: *u8 = sys_mmap(CEL_KEY_CAP + CP_NAME_CAP)
752 var po: i64 = se_cat(kpfx, 0, "rsp:" as *u8)
753 po = se_cat(kpfx, po, sid); po = se_catc(kpfx, po, CP_ASCII_COLON); po = se_cat(kpfx, po, CP_TOK_PFX)
754 kpfx[po] = 0 as u8
755 var seen: i64 = 0
756 var i: i64 = 0
757 while i < nb {
758 if se_starts(cp_bk_g[i] as *u8, kpfx) == 1 {
759 let ts: i64 = st_ballot_usec(cp_bv_g[i] as *u8, cp_bl_g[i])
760 if ts >= now - window { seen = seen + 1 }
761 }
762 i = i + 1
763 }
764 if seen >= rate_max {
765 cp_p("REFUSED-RATE cell=" as *u8); cp_p(cell); cp_p(" window_us=" as *u8); cp_pn(window)
766 cp_p(" max=" as *u8); cp_pn(rate_max); cp_p(" seen=" as *u8); cp_pn(seen); cp_p("\n" as *u8)
767 return CP_EXIT_REFUSED
768 }
769 let na: i64 = argc - first
770 if na < 1 { cp_p("REFUSED-NO-ANSWERS\n" as *u8); return CP_EXIT_USAGE }
771 let aq: *i64 = sys_mmap(CP_WORD * (na + 1)) as *i64
772 let av: *i64 = sys_mmap(CP_WORD * (na + 1)) as *i64
773 var a: i64 = 0
774 while a < na {
775 let s: *u8 = argv[first + a] as *u8
776 var e: i64 = 0
777 while s[e] != (0 as u8) { if s[e] == (CP_ASCII_EQ as u8) { break } e = e + 1 }
778 if s[e] != (CP_ASCII_EQ as u8) { cp_p("REFUSED-ANSWER-SHAPE want qid=value got " as *u8); cp_p(s); cp_p("\n" as *u8); return CP_EXIT_USAGE }
779 let q: *u8 = sys_mmap(CP_NAME_CAP)
780 var qi: i64 = 0
781 while qi < e { if qi < CP_NAME_CAP - 1 { q[qi] = s[qi] } qi = qi + 1 }
782 q[qi] = 0 as u8
783 aq[a] = q as i64
784 av[a] = ((s as i64) + e + 1)
785 a = a + 1
786 }
787 let tok: *u8 = sys_mmap(CP_NAME_CAP)
788 var to: i64 = se_cat(tok, 0, CP_TOK_PFX)
789 to = se_catn(tok, to, now)
790 tok[to] = 0 as u8
791 let rc: i64 = se_respond(prefix, sid, tok, aq, av, na)
792 if rc < 0 { cp_p("REFUSED-ENGINE se_respond rc=" as *u8); cp_pn(rc); cp_p(" survey=" as *u8); cp_p(sid); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
793 cp_p("RECORDED survey=" as *u8); cp_p(sid); cp_p(" answers=" as *u8); cp_pn(na)
794 cp_p(" kiosk_seen_in_window=" as *u8); cp_pn(seen + 1); cp_p(" of " as *u8); cp_pn(rate_max); cp_p("\n" as *u8)
795 return CP_EXIT_OK
796}
797// the field gate: two real cells named in the conf, each above the floor, and the partition summing -- or abstain
798func cp_field_gate(prefix: *u8, inst: *u8, qid: *u8) -> i64 {
799 let cells: *u8 = sys_mmap(CP_ROW_BYTES)
800 if cp_conf_s("field_cells" as *u8, cells, CP_ROW_BYTES) <= 0 {
801 cp_p("FIELD-UNOBSERVABLE conf row field_cells names no real organisations yet; abstaining, never acquitting\n" as *u8)
802 return CP_EXIT_UNOBSERVABLE
803 }
804 let pub: i64 = cp_model(prefix, inst, qid)
805 if pub < 0 { return CP_EXIT_REFUSED }
806 var ok: i64 = 1
807 var named: i64 = 0
808 var i: i64 = 0
809 let one: *u8 = sys_mmap(CP_NAME_CAP)
810 while cells[i] != (0 as u8) {
811 var w: i64 = 0
812 while cells[i] != (0 as u8) { if cells[i] == (CP_ASCII_COMMA as u8) { break } if w < CP_NAME_CAP - 1 { one[w] = cells[i]; w = w + 1 } i = i + 1 }
813 one[w] = 0 as u8
814 if cells[i] == (CP_ASCII_COMMA as u8) { i = i + 1 }
815 if w > 0 {
816 named = named + 1
817 let ci: i64 = cp_cell_index(one)
818 cp_p("field cell=" as *u8); cp_p(one)
819 if ci < 0 { cp_p(" UNKNOWN\n" as *u8); ok = 0 } else {
820 cp_p(" own=" as *u8); cp_pn(cp_own_g[ci]); cp_p(" k=" as *u8); cp_pn(cp_k_g)
821 if cp_own_g[ci] >= cp_k_g { cp_p(" ABOVE-FLOOR\n" as *u8) } else { cp_p(" UNDER-FLOOR\n" as *u8); ok = 0 }
822 }
823 }
824 }
825 let part: *i64 = sys_mmap(CP_WORD * CP_PART_SLOTS) as *i64
826 if kfl_partition(cp_state_g, cp_nc_g, part) == 0 { ok = 0 }
827 if named < 2 { cp_p("FIELD-FAIL fewer than two cells named\n" as *u8); return CP_EXIT_REFUSED }
828 if ok == 1 { cp_p("FIELD-OK cells=" as *u8); cp_pn(named); cp_p("\n" as *u8); return CP_EXIT_OK }
829 cp_p("FIELD-FAIL\n" as *u8)
830 return CP_EXIT_REFUSED
831}
832func cp_org_scope(prefix: *u8, id: *u8, kind: *u8, name: *u8, parent: *u8, denom: i64) -> i64 {
833 let kinds: *u8 = sys_mmap(CP_ROW_BYTES)
834 if cp_conf_s("cell_kinds" as *u8, kinds, CP_ROW_BYTES) <= 0 { cp_p("REFUSED-CONF-ROW-ABSENT key=cell_kinds\n" as *u8); return CP_EXIT_REFUSED }
835 let rc: i64 = cel_put(prefix, id, kind, name, parent, denom, kinds)
836 if rc == CEL_OK { cp_p("CELL id=" as *u8); cp_p(id); cp_p(" kind=" as *u8); cp_p(kind); cp_p(" parent=" as *u8); cp_p(parent); cp_p(" denom=" as *u8); cp_pn(denom); cp_p("\n" as *u8); return CP_EXIT_OK }
837 if rc == CEL_ERR_ID { cp_p("REFUSED-CELL-ID a-z 0-9, at most 24\n" as *u8) }
838 if rc == CEL_ERR_KIND { cp_p("REFUSED-KIND kind=" as *u8); cp_p(kind); cp_p(" not in cell_kinds=" as *u8); cp_p(kinds); cp_p("\n" as *u8) }
839 if rc == CEL_ERR_PARENT { cp_p("REFUSED-UNKNOWN-PARENT parent=" as *u8); cp_p(parent); cp_p("\n" as *u8) }
840 if rc == CEL_ERR_DENOM { cp_p("REFUSED-DENOM must be >= 0\n" as *u8) }
841 if rc == CEL_ERR_NAME { cp_p("REFUSED-NAME empty\n" as *u8) }
842 if rc == CEL_ERR_STORE { cp_p("REFUSED-STORE commit failed\n" as *u8) }
843 return CP_EXIT_REFUSED
844}
845func cp_cell_list(prefix: *u8) -> i64 {
846 let nc: i64 = cp_load_cells(prefix)
847 if nc < 0 { return CP_EXIT_REFUSED }
848 cp_p("NX-COMMUNITYPULSE cells=" as *u8); cp_pn(nc); cp_p("\n" as *u8)
849 var i: i64 = 0
850 while i < nc {
851 cp_p("cell|" as *u8); cp_p(cp_cid_g[i] as *u8); cp_p("|" as *u8); cp_p(cp_ckind_g[i] as *u8)
852 cp_p("|" as *u8); cp_p(cp_cname_g[i] as *u8); cp_p("|" as *u8); cp_p(cp_cparent_g[i] as *u8)
853 cp_p("|denom=" as *u8); cp_pn(cp_cdenom_g[i]); cp_p("\n" as *u8)
854 i = i + 1
855 }
856 if nc == 0 { return CP_EXIT_UNOBSERVABLE }
857 return CP_EXIT_OK
858}
859func cp_usage() -> i64 {
860 cp_p("usage: nx_communitypulse <conf> cell-put <prefix> <id> <kind> <name> <parent|-> <denom>\n" as *u8)
861 cp_p(" nx_communitypulse <conf> cell-list <prefix>\n" as *u8)
862 cp_p(" nx_communitypulse <conf> instrument <prefix> <specfile> <instrument> <cell>\n" as *u8)
863 cp_p(" nx_communitypulse <conf> kiosk <prefix> <instrument> <cell> <qid=val>...\n" as *u8)
864 cp_p(" nx_communitypulse <conf> rollup|weights <prefix> <instrument> <qid>\n" as *u8)
865 cp_p(" nx_communitypulse <conf> page <prefix> <instrument> <qid> <cell> <out.html>\n" as *u8)
866 cp_p(" nx_communitypulse <conf> alert <prefix> <instrument> <cell> <qid>\n" as *u8)
867 cp_p(" nx_communitypulse <conf> export <prefix> <instrument> <qid> <out>\n" as *u8)
868 cp_p(" nx_communitypulse <conf> verify <file>\n" as *u8)
869 cp_p(" nx_communitypulse <conf> field <prefix> <instrument> <qid>\n" as *u8)
870 cp_p("exits: 0 OK 1 REFUSED/ALERT/TAMPERED 2 usage 3 UNOBSERVABLE\n" as *u8)
871 return CP_EXIT_USAGE
872}
873func main(argc: i64, argv: *i64) -> i64 {
874 if argc < 3 { return cp_usage() }
875 if cp_conf_load(argv[1] as *u8) == 0 { cp_p("REFUSED-CONF-UNREADABLE path=" as *u8); cp_p(argv[1] as *u8); cp_p("\n" as *u8); return CP_EXIT_REFUSED }
876 let verb: *u8 = argv[2] as *u8
877 if se_seq(verb, "cell-put" as *u8) == 1 {
878 if argc < 9 { return cp_usage() }
879 return cp_org_scope(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8, argv[7] as *u8, sv_atoi(argv[8] as *u8))
880 }
881 if se_seq(verb, "cell-list" as *u8) == 1 { if argc < 4 { return cp_usage() } return cp_cell_list(argv[3] as *u8) }
882 if se_seq(verb, "instrument" as *u8) == 1 { if argc < 7 { return cp_usage() } return cp_instrument(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8) }
883 if se_seq(verb, "kiosk" as *u8) == 1 { if argc < 7 { return cp_usage() } return cp_kiosk(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argc, argv, 6) }
884 if se_seq(verb, "rollup" as *u8) == 1 { if argc < 6 { return cp_usage() } return cp_rollup(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) }
885 if se_seq(verb, "weights" as *u8) == 1 { if argc < 6 { return cp_usage() } return cp_weights(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) }
886 if se_seq(verb, "page" as *u8) == 1 { if argc < 8 { return cp_usage() } return cp_public_page(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8, argv[7] as *u8) }
887 if se_seq(verb, "alert" as *u8) == 1 { if argc < 7 { return cp_usage() } return cp_alert(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8) }
888 if se_seq(verb, "export" as *u8) == 1 { if argc < 7 { return cp_usage() } return cp_open_export(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8, argv[6] as *u8) }
889 if se_seq(verb, "verify" as *u8) == 1 { if argc < 4 { return cp_usage() } return cp_verify_export(argv[3] as *u8) }
890 if se_seq(verb, "field" as *u8) == 1 { if argc < 6 { return cp_usage() } return cp_field_gate(argv[3] as *u8, argv[4] as *u8, argv[5] as *u8) }
891 return cp_usage()
892}