nx_survey_serve.nx source
↩ module page · 2064 lines · 118986 B
1// nx_survey_serve.nx -- the SURVEY/POLL product surface over nx_survey_engine (pure core, no main;
2// the nx_office_serve/nx_relate_serve discipline). A one-question welfare pulse and a deep survey are
3// the SAME app -- surveys are spec DATA loaded through the admin lane, never code.
4// ROUTES (base = URL mount prefix, e.g. "/survey" behind the sovereign edge):
5// GET / open+closed survey list
6// GET /s/<id> the survey form (respondent token from ?t=<tok> or freshly minted; the
7// served app.js keeps one stable token per browser -> revote-replace works)
8// POST /s/<id> record a ballot (engine validates; revote REPLACES by construction)
9// GET /s/<id>/done thanks page
10// GET /s/<id>/results AGGREGATE-ONLY results: counts/means/bars; k-floor (@kmin) withholds
11// below quorum; text answers NEVER render here; @results admin -> 403
12// GET /app.js progressive-enhancement client (token persistence; no-JS still works)
13// POST /admin/load|close|report -- key-file-gated (CWD survey_admin.key; FAIL-CLOSED when absent)
14// POST /admin/insights -- key-gated deep analytics report (cross-tab + chi-square significance, segment
15// means, key drivers, concern flags, open-text themes) over nx_survey_stats
16// PRIVACY BY CONSTRUCTION: anon surveys never emit tokens anywhere public; results are counts only;
17// free text is admin-report only; deep breakdowns that could reveal a small cell are admin-lane only.
18// Same-origin POST guard. license_tier: ORIGINAL
19import "nx_survey_sentiment.nx"
20import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
21
22const SV2_MAXB: i64 = 512 // ballots the serve layer aggregates per survey (walk stays LOUD past it)
23const SV2_MAXS: i64 = 64 // surveys listed on home
24
25func sv2_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
26// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
27// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
28// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
29// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
30func sv2_pn(v: i64) -> i64 { nxi_out(v); return 0 }
31func sv2_memhas(buf: *u8, n: i64, needle: *u8) -> i64 {
32 let nl: i64 = se_slen(needle)
33 if nl == 0 { return 0 }
34 var i: i64 = 0
35 while i + nl <= n {
36 var k: i64 = 0
37 var hit: i64 = 1
38 while k < nl { if buf[i+k] != needle[k] { hit = 0; k = nl } else { k = k + 1 } }
39 if hit == 1 { return 1 }
40 i = i + 1
41 }
42 return 0
43}
44// escape & < > while appending
45func sv2_esc(dst: *u8, off: i64, s: *u8) -> i64 {
46 var o: i64 = off
47 var i: i64 = 0
48 while s[i] != (0 as u8) {
49 let c: i64 = s[i] as i64
50 if c == 38 { o = se_cat(dst, o, "&" as *u8) } else {
51 if c == 60 { o = se_cat(dst, o, "<" as *u8) } else {
52 if c == 62 { o = se_cat(dst, o, ">" as *u8) } else { dst[o] = s[i]; o = o + 1 } } }
53 i = i + 1
54 }
55 return o
56}
57func sv2_hexval(c: i64) -> i64 {
58 if c >= 48 { if c <= 57 { return c - 48 } }
59 if c >= 65 { if c <= 70 { return c - 55 } }
60 if c >= 97 { if c <= 102 { return c - 87 } }
61 return 0
62}
63// urldecoded form value for key from body (+ and %XX). the proven of_form_get pattern.
64func sv2_form_get(body: *u8, blen: i64, key: *u8, out: *u8, cap: i64) -> i64 {
65 let kl: i64 = se_slen(key)
66 var i: i64 = 0
67 while i < blen {
68 var atk: i64 = 0
69 if i == 0 { atk = 1 } else { if body[i-1] == (38 as u8) { atk = 1 } }
70 if atk == 1 {
71 var m: i64 = 1
72 var j: i64 = 0
73 while j < kl { if i + j >= blen { m = 0; j = kl } else { if body[i+j] != key[j] { m = 0; j = kl } else { j = j + 1 } } }
74 if m == 1 { if i + kl < blen { if body[i+kl] == (61 as u8) {
75 var q: i64 = i + kl + 1
76 var t: i64 = 0
77 var go: i64 = 1
78 while go == 1 {
79 if q >= blen { go = 0 } else {
80 let c: i64 = body[q] as i64
81 if c == 38 { go = 0 } else {
82 var ch: i64 = c
83 if c == 43 { ch = 32 }
84 if c == 37 { if q + 2 < blen { ch = sv2_hexval(body[q+1] as i64) * 16 + sv2_hexval(body[q+2] as i64); q = q + 2 } }
85 if t < cap - 1 { out[t] = ch as u8; t = t + 1 }
86 q = q + 1
87 }
88 }
89 }
90 out[t] = 0 as u8
91 return t
92 } } }
93 }
94 i = i + 1
95 }
96 out[0] = 0 as u8
97 return 0
98}
99func sv2_write_all(fd: i64, buf: *u8, n: i64) -> i64 {
100 var w: i64 = 0
101 while w < n { let k: i64 = sys_write(fd, (buf as i64 + w) as *u8, n - w); if k <= 0 { return 0 - 1 } w = w + k }
102 return 0
103}
104// read ONE full HTTP request: headers until CRLFCRLF plus Content-Length body (proven pattern)
105func sv2_read_req(fd: i64, buf: *u8, cap: i64) -> i64 {
106 var n: i64 = 0
107 var hdr_end: i64 = 0 - 1
108 var want: i64 = 0 - 1
109 var go: i64 = 1
110 while go == 1 {
111 if n >= cap - 1 { go = 0 } else {
112 let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n)
113 if r <= 0 { go = 0 } else {
114 n = n + r
115 if hdr_end < 0 {
116 var i: i64 = 0
117 while i + 3 < n {
118 if buf[i] == (13 as u8) { if buf[i+1] == (10 as u8) { if buf[i+2] == (13 as u8) { if buf[i+3] == (10 as u8) { hdr_end = i + 4; i = n } } } }
119 i = i + 1
120 }
121 if hdr_end >= 0 {
122 var cl: i64 = 0 - 1
123 let key: *u8 = "Content-Length:" as *u8
124 let kl: i64 = 15
125 var j: i64 = 0
126 while j + kl < hdr_end {
127 var m: i64 = 1
128 var q: i64 = 0
129 while q < kl { if buf[j+q] != key[q] { m = 0; q = kl } else { q = q + 1 } }
130 if m == 1 {
131 var v: i64 = 0
132 var t: i64 = j + kl
133 while t < hdr_end {
134 let c: i64 = buf[t] as i64
135 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } }
136 if c == 13 { t = hdr_end }
137 t = t + 1
138 }
139 cl = v
140 j = hdr_end
141 }
142 j = j + 1
143 }
144 if cl < 0 { want = hdr_end } else { want = hdr_end + cl }
145 }
146 }
147 if want >= 0 { if n >= want { go = 0 } }
148 }
149 }
150 }
151 return n
152}
153func sv2_read_file(path: *u8, buf: *u8, cap: i64) -> i64 {
154 let fd: i64 = sys_openat_rd(path)
155 if fd < 0 { return 0 - 1 }
156 var tot: i64 = 0
157 while tot < cap { let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot); if r <= 0 { break } tot = tot + r }
158 sys_close(fd)
159 return tot
160}
161func sv2_write_file(path: *u8, buf: *u8, n: i64) -> i64 {
162 let fd: i64 = sys_openat_wr(path, 420)
163 if fd < 0 { return 0 - 1 }
164 let rc: i64 = sv2_write_all(fd, buf, n)
165 sys_close(fd)
166 return rc
167}
168// header value (key like "Origin:") from the header region
169func sv2_hdr_get(req: *u8, reqlen: i64, key: *u8, out: *u8, cap: i64) -> i64 {
170 out[0] = 0 as u8
171 var he: i64 = reqlen
172 var i: i64 = 0
173 while i + 3 < reqlen {
174 if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { if req[i+2] == (13 as u8) { if req[i+3] == (10 as u8) { he = i; i = reqlen } } } }
175 i = i + 1
176 }
177 let kl: i64 = se_slen(key)
178 var j: i64 = 0
179 while j + kl < he {
180 var m: i64 = 1
181 var q: i64 = 0
182 while q < kl { if req[j+q] != key[q] { m = 0; q = kl } else { q = q + 1 } }
183 if m == 1 {
184 var t: i64 = j + kl
185 if req[t] == (32 as u8) { t = t + 1 }
186 var o: i64 = 0
187 while t < he { if req[t] == (13 as u8) { t = he } else { if o < cap - 1 { out[o] = req[t]; o = o + 1 } t = t + 1 } }
188 out[o] = 0 as u8
189 return o
190 }
191 j = j + 1
192 }
193 return 0
194}
195func sv2_err(out: *u8, status: *u8, msg: *u8) -> i64 {
196 var o: i64 = se_cat(out, 0, "HTTP/1.1 " as *u8)
197 o = se_cat(out, o, status)
198 o = se_cat(out, o, "\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nLOUD: " as *u8)
199 o = se_cat(out, o, msg)
200 o = se_catc(out, o, 10)
201 return o
202}
203// fixed-point d.dd from x100 (e.g. 742 -> "7.42")
204func sv2_fixed2(dst: *u8, off: i64, x100: i64) -> i64 {
205 var o: i64 = se_catn(dst, off, x100 / 100)
206 o = se_catc(dst, o, 46)
207 let fr: i64 = x100 % 100
208 o = se_catn(dst, o, fr / 10)
209 o = se_catn(dst, o, fr % 10)
210 return o
211}
212
213// ---- pages ----
214func sv2_css(out: *u8, off: i64) -> i64 {
215 var o: i64 = off
216 o = se_cat(out, o, "<style>*{box-sizing:border-box}body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:rgb(248,249,252);color:rgb(24,26,34);max-width:760px;margin:3vh auto;padding:0 16px;line-height:1.5}\n" as *u8)
217 o = se_cat(out, o, "h1{margin:0 0 2px;font-size:1.7rem}.sub{color:rgb(105,110,125);margin:0 0 18px}a{color:rgb(42,77,143)}\n" as *u8)
218 o = se_cat(out, o, ".card{background:rgb(255,255,255);border:1px solid rgb(226,228,236);border-radius:12px;padding:16px 18px;margin:14px 0}\n" as *u8)
219 o = se_cat(out, o, ".k{display:inline-block;padding:1px 10px;border-radius:20px;font-size:.8rem;font-weight:600;color:rgb(255,255,255)}.kopen{background:rgb(26,127,55)}.kclosed{background:rgb(140,144,158)}.kaud{background:rgb(42,77,143)}\n" as *u8)
220 o = se_cat(out, o, "fieldset{border:1px solid rgb(226,228,236);border-radius:10px;margin:12px 0;padding:10px 14px}legend{font-weight:600;padding:0 6px}\n" as *u8)
221 o = se_cat(out, o, ".opt{display:block;padding:7px 10px;border-radius:8px;cursor:pointer}.opt:hover{background:rgb(240,243,250)}.opt input{margin-right:9px}\n" as *u8)
222 o = se_cat(out, o, ".scale{display:flex;flex-wrap:wrap;gap:4px}.scale label{flex:1;min-width:34px;text-align:center;border:1px solid rgb(210,214,226);border-radius:8px;padding:7px 2px;cursor:pointer}.scale input{display:block;margin:0 auto 4px}\n" as *u8)
223 o = se_cat(out, o, "textarea{width:100%;min-height:90px;font-size:.95rem;border:1px solid rgb(210,214,226);border-radius:8px;padding:10px;background:rgb(252,252,254)}\n" as *u8)
224 o = se_cat(out, o, "button{background:rgb(42,77,143);color:rgb(255,255,255);border:0;border-radius:8px;padding:10px 22px;font-size:1rem;font-weight:600;cursor:pointer}\n" as *u8)
225 o = se_cat(out, o, ".hint{color:rgb(105,110,125);font-size:.85rem;margin-top:6px}.foot{margin-top:24px;color:rgb(140,144,158);font-size:.8rem;border-top:1px solid rgb(226,228,236);padding-top:10px}\n" as *u8)
226 o = se_cat(out, o, ".row{display:flex;align-items:center;gap:10px;margin:5px 0}.lbl{flex:0 0 38%;font-size:.92rem}.barw{flex:1;background:rgb(238,241,248);border-radius:6px;height:18px;overflow:hidden}.bar{background:rgb(42,77,143);height:18px}.cnt{flex:0 0 3.2em;text-align:right;font-size:.9rem;color:rgb(105,110,125)}\n" as *u8)
227 o = se_cat(out, o, "@media(prefers-color-scheme:dark){body{background:rgb(15,16,21);color:rgb(228,230,238)}.card{background:rgb(22,24,31);border-color:rgb(40,43,54)}textarea{background:rgb(17,18,24);color:rgb(228,230,238);border-color:rgb(48,52,66)}fieldset{border-color:rgb(40,43,54)}.scale label{border-color:rgb(48,52,66)}.opt:hover{background:rgb(28,30,40)}.barw{background:rgb(28,30,40)}}\n" as *u8)
228 o = se_cat(out, o, "</style>" as *u8)
229 return o
230}
231func sv2_page_top(out: *u8, off: i64, title: *u8) -> i64 {
232 var o: i64 = off
233 o = se_cat(out, o, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<" as *u8)
234 o = se_catc(out, o, 33)
235 o = se_cat(out, o, "DOCTYPE html>\n<html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'><title>" as *u8)
236 o = sv2_esc(out, o, title)
237 o = se_cat(out, o, "</title>" as *u8)
238 o = sv2_css(out, o)
239 o = se_cat(out, o, "</head><body>\n" as *u8)
240 return o
241}
242func sv2_page_foot(out: *u8, off: i64) -> i64 {
243 var o: i64 = off
244 o = se_cat(out, o, "<p class='foot'>Nishi Pulse — sovereign polls & surveys. One respondent = one effective ballot (updates replace, history is never destroyed); anonymous surveys aggregate only, with a k-anonymity floor; written answers go to the survey admin only.</p></body></html>\n" as *u8)
245 return o
246}
247// one home card per survey
248func sv2_home(prefix: *u8, base: *u8, out: *u8, cap: i64) -> i64 {
249 var o: i64 = sv2_page_top(out, 0, "Nishi Pulse - polls and surveys" as *u8)
250 o = se_cat(out, o, "<h1>Nishi Pulse</h1><p class='sub'>Quick one-question polls and deeper surveys — for members, customers and teams.</p>\n" as *u8)
251 let sk: *i64 = sys_mmap(8 * SV2_MAXS) as *i64
252 let sv: *i64 = sys_mmap(8 * SV2_MAXS) as *i64
253 let sl: *i64 = sys_mmap(8 * SV2_MAXS) as *i64
254 let n: i64 = se_surveys(prefix, sk, sv, sl, SV2_MAXS)
255 if n <= 0 { o = se_cat(out, o, "<div class='card'><p class='hint'>No surveys yet. The admin lane loads them as spec data.</p></div>" as *u8) }
256 var i: i64 = 0
257 while i < n {
258 let ks: *i64 = sys_mmap(8 * 32) as *i64
259 let vs: *i64 = sys_mmap(8 * 32) as *i64
260 let nf: i64 = canon_decode(sv[i] as *u8, sl[i], ks, vs, 32)
261 if nf > 0 {
262 let id: *u8 = sv_get(ks, vs, nf, "id" as *u8)
263 let title: *u8 = sv_get(ks, vs, nf, "title" as *u8)
264 let stt: *u8 = sv_get(ks, vs, nf, "status" as *u8)
265 let aud: *u8 = sv_get(ks, vs, nf, "audience" as *u8)
266 let nq: i64 = se_field_n(ks, vs, nf, "nq" as *u8, 0)
267 if (id as i64) != 0 { if (title as i64) != 0 { if (stt as i64) != 0 {
268 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 6px'><a href='" as *u8)
269 o = se_cat(out, o, base)
270 o = se_cat(out, o, "/s/" as *u8)
271 o = se_cat(out, o, id)
272 o = se_cat(out, o, "'>" as *u8)
273 o = sv2_esc(out, o, title)
274 o = se_cat(out, o, "</a></h3><p style='margin:0'>" as *u8)
275 var isopen: i64 = se_seq(stt, "open" as *u8)
276 if isopen == 1 { o = se_cat(out, o, "<span class='k kopen'>open</span> " as *u8) } else { o = se_cat(out, o, "<span class='k kclosed'>closed</span> " as *u8) }
277 if (aud as i64) != 0 { o = se_cat(out, o, "<span class='k kaud'>" as *u8); o = sv2_esc(out, o, aud); o = se_cat(out, o, "</span> " as *u8) }
278 o = se_catn(out, o, nq)
279 o = se_cat(out, o, " question(s) · <a href='" as *u8)
280 o = se_cat(out, o, base)
281 o = se_cat(out, o, "/s/" as *u8)
282 o = se_cat(out, o, id)
283 o = se_cat(out, o, "/results'>results</a></p></div>\n" as *u8)
284 } } }
285 }
286 i = i + 1
287 }
288 o = sv2_page_foot(out, o)
289 return o
290}
291// the survey FORM
292func sv2_form(prefix: *u8, base: *u8, id: *u8, tok: *u8, out: *u8, cap: i64) -> i64 {
293 let mks: *i64 = sys_mmap(8 * 32) as *i64
294 let mvs: *i64 = sys_mmap(8 * 32) as *i64
295 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
296 if mnf < 0 { return 0 - 1 }
297 let title: *u8 = sv_get(mks, mvs, mnf, "title" as *u8)
298 let stt: *u8 = sv_get(mks, mvs, mnf, "status" as *u8)
299 let nq: i64 = se_field_n(mks, mvs, mnf, "nq" as *u8, 0)
300 let anon: i64 = se_field_n(mks, mvs, mnf, "anon" as *u8, 1)
301 let maxtext: i64 = se_field_n(mks, mvs, mnf, "maxtext" as *u8, SE_DEF_MAXTEXT)
302 if (title as i64) == 0 { return 0 - 1 }
303 if (stt as i64) == 0 { return 0 - 1 }
304 var o: i64 = sv2_page_top(out, 0, title)
305 o = se_cat(out, o, "<p><a href='" as *u8)
306 o = se_cat(out, o, base)
307 o = se_cat(out, o, "/'>← all surveys</a></p><h1>" as *u8)
308 o = sv2_esc(out, o, title)
309 o = se_cat(out, o, "</h1>" as *u8)
310 if se_seq(stt, "open" as *u8) == 0 {
311 o = se_cat(out, o, "<div class='card'><p><span class='k kclosed'>closed</span> This survey is closed. <a href='" as *u8)
312 o = se_cat(out, o, base)
313 o = se_cat(out, o, "/s/" as *u8)
314 o = se_cat(out, o, id)
315 o = se_cat(out, o, "/results'>See the results</a>.</p></div>" as *u8)
316 o = sv2_page_foot(out, o)
317 return o
318 }
319 // quota: an open survey with a response cap shows progress; when the cap is met, the form is
320 // withheld (no new respondents) and only the results link is offered. se_ballots -1 = the effective
321 // count overflowed a quota-sized buffer, i.e. already full -> fail closed to the full card.
322 let quota: i64 = se_field_n(mks, mvs, mnf, "quota" as *u8, 0)
323 if quota > 0 {
324 let qcap: i64 = quota + 16
325 let bk: *i64 = sys_mmap(8 * qcap) as *i64
326 let bv: *i64 = sys_mmap(8 * qcap) as *i64
327 let bl: *i64 = sys_mmap(8 * qcap) as *i64
328 let nb: i64 = se_ballots(prefix, id, bk, bv, bl, qcap)
329 var full: i64 = 0
330 if nb < 0 { full = 1 }
331 if nb >= quota { full = 1 }
332 if full == 1 {
333 o = se_cat(out, o, "<div class='card'><p><span class='k kclosed'>full</span> This survey has reached its response quota. <a href='" as *u8)
334 o = se_cat(out, o, base)
335 o = se_cat(out, o, "/s/" as *u8)
336 o = se_cat(out, o, id)
337 o = se_cat(out, o, "/results'>See the results</a>.</p></div>" as *u8)
338 o = sv2_page_foot(out, o)
339 return o
340 }
341 o = se_cat(out, o, "<p class='sub'>" as *u8)
342 o = se_catn(out, o, nb)
343 o = se_cat(out, o, " of " as *u8)
344 o = se_catn(out, o, quota)
345 o = se_cat(out, o, " responses received.</p>" as *u8)
346 }
347 if anon == 1 { o = se_cat(out, o, "<p class='sub'>Anonymous — answers are aggregated; nobody sees who said what.</p>" as *u8) } else { o = se_cat(out, o, "<p class='sub'>Responses are linked to your personal link so you can update them later.</p>" as *u8) }
348 o = se_cat(out, o, "<form method='post' action='" as *u8)
349 o = se_cat(out, o, base)
350 o = se_cat(out, o, "/s/" as *u8)
351 o = se_cat(out, o, id)
352 o = se_cat(out, o, "'><input type='hidden' id='tok' name='tok' value='" as *u8)
353 o = se_cat(out, o, tok)
354 o = se_cat(out, o, "'>" as *u8)
355 var q: i64 = 1
356 while q <= nq {
357 let qks: *i64 = sys_mmap(8 * 32) as *i64
358 let qvs: *i64 = sys_mmap(8 * 32) as *i64
359 let qnf: i64 = se_qst(prefix, id, q, qks, qvs, 32)
360 if qnf > 0 {
361 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
362 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
363 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
364 if (qid as i64) != 0 { if (kind as i64) != 0 { if (prompt as i64) != 0 {
365 let kc: i64 = kind[0] as i64
366 o = se_cat(out, o, "<fieldset><legend>" as *u8)
367 o = sv2_esc(out, o, prompt)
368 o = se_cat(out, o, "</legend>" as *u8)
369 if kc == 67 {
370 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
371 if (opts as i64) != 0 {
372 let no: i64 = se_nopts(opts)
373 let ob: *u8 = sys_mmap(96)
374 var k: i64 = 0
375 while k < no {
376 se_opt(opts, k, ob, 96)
377 o = se_cat(out, o, "<label class='opt'><input type='radio' name='" as *u8)
378 o = se_cat(out, o, qid)
379 o = se_cat(out, o, "' value='" as *u8)
380 o = se_catn(out, o, k)
381 o = se_cat(out, o, "'>" as *u8)
382 o = sv2_esc(out, o, ob)
383 o = se_cat(out, o, "</label>" as *u8)
384 k = k + 1
385 }
386 }
387 } else { if kc == 83 {
388 let lo: i64 = se_field_n(qks, qvs, qnf, "min" as *u8, 0)
389 let hi: i64 = se_field_n(qks, qvs, qnf, "max" as *u8, 10)
390 o = se_cat(out, o, "<div class='scale'>" as *u8)
391 var v: i64 = lo
392 while v <= hi {
393 o = se_cat(out, o, "<label><input type='radio' name='" as *u8)
394 o = se_cat(out, o, qid)
395 o = se_cat(out, o, "' value='" as *u8)
396 o = se_catn(out, o, v)
397 o = se_cat(out, o, "'>" as *u8)
398 o = se_catn(out, o, v)
399 o = se_cat(out, o, "</label>" as *u8)
400 v = v + 1
401 }
402 o = se_cat(out, o, "</div><p class='hint'>scale " as *u8)
403 o = se_catn(out, o, lo)
404 o = se_cat(out, o, "–" as *u8)
405 o = se_catn(out, o, hi)
406 o = se_cat(out, o, " — the question text says what the ends mean</p>" as *u8)
407 } else {
408 o = se_cat(out, o, "<textarea name='" as *u8)
409 o = se_cat(out, o, qid)
410 o = se_cat(out, o, "' maxlength='" as *u8)
411 o = se_catn(out, o, maxtext)
412 o = se_cat(out, o, "' placeholder='Optional'></textarea>" as *u8)
413 } }
414 o = se_cat(out, o, "</fieldset>" as *u8)
415 } } }
416 }
417 q = q + 1
418 }
419 o = se_cat(out, o, "<p><button type='submit'>Submit</button></p><p class='hint'>Submitting again later updates your previous answer — it is never counted twice.</p></form><script src='" as *u8)
420 o = se_cat(out, o, base)
421 o = se_cat(out, o, "/app.js'></script>" as *u8)
422 o = sv2_page_foot(out, o)
423 return o
424}
425// aggregate-only RESULTS
426func sv2_results(prefix: *u8, base: *u8, id: *u8, out: *u8, cap: i64) -> i64 {
427 let mks: *i64 = sys_mmap(8 * 32) as *i64
428 let mvs: *i64 = sys_mmap(8 * 32) as *i64
429 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
430 if mnf < 0 { return 0 - 1 }
431 let rmode: *u8 = sv_get(mks, mvs, mnf, "results" as *u8)
432 if (rmode as i64) != 0 { if se_seq(rmode, "admin" as *u8) == 1 {
433 return sv2_err(out, "403 Forbidden" as *u8, "results for this survey are admin-only" as *u8)
434 } }
435 let title: *u8 = sv_get(mks, mvs, mnf, "title" as *u8)
436 let stt: *u8 = sv_get(mks, mvs, mnf, "status" as *u8)
437 let nq: i64 = se_field_n(mks, mvs, mnf, "nq" as *u8, 0)
438 let kmin: i64 = se_field_n(mks, mvs, mnf, "kmin" as *u8, 0)
439 if (title as i64) == 0 { return 0 - 1 }
440 let bk: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
441 let bv: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
442 let bl: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
443 let nb: i64 = se_ballots(prefix, id, bk, bv, bl, SV2_MAXB)
444 if nb < 0 { return sv2_err(out, "500 Internal Server Error" as *u8, "tally walk hit its bound; compact the store" as *u8) }
445 var o: i64 = sv2_page_top(out, 0, title)
446 o = se_cat(out, o, "<p><a href='" as *u8)
447 o = se_cat(out, o, base)
448 o = se_cat(out, o, "/'>← all surveys</a></p><h1>" as *u8)
449 o = sv2_esc(out, o, title)
450 o = se_cat(out, o, "</h1><p class='sub'>Results — aggregate only" as *u8)
451 if (stt as i64) != 0 { if se_seq(stt, "open" as *u8) == 0 { o = se_cat(out, o, " · closed (final)" as *u8) } }
452 o = se_cat(out, o, "</p>" as *u8)
453 if nb < kmin {
454 o = se_cat(out, o, "<div class='card'><p><b>Results withheld.</b> Fewer than " as *u8)
455 o = se_catn(out, o, kmin)
456 o = se_cat(out, o, " people have responded so far; aggregates unlock at the k-anonymity floor so no individual answer can be inferred.</p></div>" as *u8)
457 o = sv2_page_foot(out, o)
458 return o
459 }
460 o = se_cat(out, o, "<div class='card'><p><b>" as *u8)
461 o = se_catn(out, o, nb)
462 o = se_cat(out, o, "</b> response(s).</p></div>" as *u8)
463 var q: i64 = 1
464 while q <= nq {
465 let qks: *i64 = sys_mmap(8 * 32) as *i64
466 let qvs: *i64 = sys_mmap(8 * 32) as *i64
467 let qnf: i64 = se_qst(prefix, id, q, qks, qvs, 32)
468 if qnf > 0 {
469 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
470 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
471 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
472 if (qid as i64) != 0 { if (kind as i64) != 0 { if (prompt as i64) != 0 {
473 let kc: i64 = kind[0] as i64
474 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>" as *u8)
475 o = sv2_esc(out, o, prompt)
476 o = se_cat(out, o, "</h3>" as *u8)
477 if kc == 67 {
478 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
479 if (opts as i64) != 0 {
480 let no: i64 = se_nopts(opts)
481 let ob: *u8 = sys_mmap(96)
482 let vb: *u8 = sys_mmap(16)
483 var ans: i64 = 0
484 var k: i64 = 0
485 while k < no {
486 se_opt(opts, k, ob, 96)
487 se_catn(vb, 0, k)
488 var ve: i64 = 0
489 while vb[ve] != (0 as u8) { ve = ve + 1 }
490 vb[ve] = 0 as u8
491 let c: i64 = se_count_eq(bv, bl, nb, qid, vb)
492 ans = ans + c
493 var pct: i64 = 0
494 if nb > 0 { pct = c * 100 / nb }
495 o = se_cat(out, o, "<div class='row'><span class='lbl'>" as *u8)
496 o = sv2_esc(out, o, ob)
497 o = se_cat(out, o, "</span><span class='barw'><span class='bar' style='width:" as *u8)
498 o = se_catn(out, o, pct)
499 o = se_cat(out, o, "%;display:block'></span></span><span class='cnt'>" as *u8)
500 o = se_catn(out, o, c)
501 o = se_cat(out, o, "</span></div>" as *u8)
502 k = k + 1
503 }
504 if ans > 0 {
505 let mgc: i64 = st_ci_margin(ans / 2, ans)
506 o = se_cat(out, o, "<p style='margin:6px 0 0;color:rgb(105,110,125);font-size:.85rem'>95% margin of error up to ±" as *u8)
507 o = se_catn(out, o, mgc / 10)
508 o = se_cat(out, o, "% at n=" as *u8)
509 o = se_catn(out, o, ans)
510 o = se_cat(out, o, "</p>" as *u8)
511 }
512 }
513 } else { if kc == 83 {
514 let lo: i64 = se_field_n(qks, qvs, qnf, "min" as *u8, 0)
515 let hi: i64 = se_field_n(qks, qvs, qnf, "max" as *u8, 10)
516 let nst: *i64 = sys_mmap(32) as *i64
517 se_num_stats(bv, bl, nb, qid, nst)
518 o = se_cat(out, o, "<p style='margin:0 0 8px'><b>average " as *u8)
519 if nst[0] > 0 { o = sv2_fixed2(out, o, nst[1] * 100 / nst[0]) } else { o = se_cat(out, o, "-" as *u8) }
520 o = se_cat(out, o, "</b> of " as *u8)
521 o = se_catn(out, o, hi)
522 o = se_cat(out, o, " (" as *u8)
523 o = se_catn(out, o, nst[0])
524 o = se_cat(out, o, " answered)</p>" as *u8)
525 // insight strip (whole-sample, privacy-safe): top-2-box + 95% margin + NPS on 0-10 scales
526 let sxs: *i64 = sys_mmap(8 * (nb + 2)) as *i64
527 let sm: i64 = st_collect(bv, bl, nb, qid, sxs)
528 if sm > 0 {
529 let tbpm: i64 = st_topbox(sxs, sm, hi, 2)
530 let tbk: i64 = (tbpm * sm + 500) / 1000
531 let mg: i64 = st_ci_margin(tbk, sm)
532 o = se_cat(out, o, "<p style='margin:0 0 8px;color:rgb(105,110,125);font-size:.9rem'>Top-2-box <b>" as *u8)
533 o = se_catn(out, o, tbpm / 10)
534 o = se_cat(out, o, "%</b> (95% margin ±" as *u8)
535 o = se_catn(out, o, mg / 10)
536 o = se_cat(out, o, "%)" as *u8)
537 if hi == 10 {
538 let npo: *i64 = sys_mmap(8 * 8) as *i64
539 st_nps(sxs, sm, 9, 6, npo)
540 o = se_cat(out, o, " · NPS <b>" as *u8)
541 if npo[0] >= 0 { o = se_cat(out, o, "+" as *u8) }
542 o = se_catn(out, o, npo[0])
543 o = se_cat(out, o, "</b>" as *u8)
544 }
545 o = se_cat(out, o, "</p>" as *u8)
546 }
547 let vb2: *u8 = sys_mmap(16)
548 var v: i64 = lo
549 while v <= hi {
550 se_catn(vb2, 0, v)
551 var v2e: i64 = 0
552 while vb2[v2e] != (0 as u8) { v2e = v2e + 1 }
553 vb2[v2e] = 0 as u8
554 let c2: i64 = se_count_eq(bv, bl, nb, qid, vb2)
555 var pct2: i64 = 0
556 if nst[0] > 0 { pct2 = c2 * 100 / nst[0] }
557 o = se_cat(out, o, "<div class='row'><span class='lbl'>" as *u8)
558 o = se_catn(out, o, v)
559 o = se_cat(out, o, "</span><span class='barw'><span class='bar' style='width:" as *u8)
560 o = se_catn(out, o, pct2)
561 o = se_cat(out, o, "%;display:block'></span></span><span class='cnt'>" as *u8)
562 o = se_catn(out, o, c2)
563 o = se_cat(out, o, "</span></div>" as *u8)
564 v = v + 1
565 }
566 } else {
567 let tc: i64 = se_text_count(bv, bl, nb, qid)
568 o = se_cat(out, o, "<p style='margin:0'>" as *u8)
569 o = se_catn(out, o, tc)
570 o = se_cat(out, o, " written answer(s) — visible to the survey admin only (privacy by construction).</p>" as *u8)
571 } }
572 o = se_cat(out, o, "</div>" as *u8)
573 } } }
574 }
575 q = q + 1
576 }
577 // embeddable poll snippet -- surface the capability (tools-surface-in-UI). The first question renders as a
578 // compact inline poll on any external site via an iframe pointing at /embed/<id>.
579 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 6px'>Embed this poll</h3><p class='hint'>Drop the first question onto any page (your ward site, the tandem app) as a live one-question poll:</p><textarea readonly style='min-height:70px'>" as *u8)
580 o = sv2_esc(out, o, "<iframe src='" as *u8)
581 o = se_cat(out, o, "https://nishifamily.com" as *u8)
582 o = sv2_esc(out, o, base)
583 o = sv2_esc(out, o, "/embed/" as *u8)
584 o = sv2_esc(out, o, id)
585 o = sv2_esc(out, o, "' style='width:100%;max-width:420px;height:260px;border:1px solid rgb(226,228,236);border-radius:10px' title='Poll'></iframe>" as *u8)
586 o = se_cat(out, o, "</textarea></div>" as *u8)
587 o = sv2_page_foot(out, o)
588 return o
589}
590func sv2_done(base: *u8, id: *u8, out: *u8) -> i64 {
591 var o: i64 = sv2_page_top(out, 0, "Thank you" as *u8)
592 o = se_cat(out, o, "<div class='card'><h1>Thank you.</h1><p>Your response is recorded. Submitting again from this device updates it — it is never counted twice.</p><p><a href='" as *u8)
593 o = se_cat(out, o, base)
594 o = se_cat(out, o, "/s/" as *u8)
595 o = se_cat(out, o, id)
596 o = se_cat(out, o, "/results'>See the results</a> · <a href='" as *u8)
597 o = se_cat(out, o, base)
598 o = se_cat(out, o, "/'>all surveys</a></p></div>" as *u8)
599 o = sv2_page_foot(out, o)
600 return o
601}
602// admin key check: keypath file must exist, be >=8 chars trimmed, and equal the presented key. FAIL-CLOSED.
603func sv2_admin_ok(keypath: *u8, presented: *u8) -> i64 {
604 let kb: *u8 = sys_mmap(256)
605 var n: i64 = sv2_read_file(keypath, kb, 255)
606 if n < 8 { return 0 }
607 while n > 0 {
608 let c: i64 = kb[n-1] as i64
609 var ws: i64 = 0
610 if c == 10 { ws = 1 }
611 if c == 13 { ws = 1 }
612 if c == 32 { ws = 1 }
613 if c == 9 { ws = 1 }
614 if ws == 1 { n = n - 1 } else { break }
615 }
616 if n < 8 { return 0 }
617 kb[n] = 0 as u8
618 if se_slen(presented) != n { return 0 }
619 return se_seq(kb, presented)
620}
621// admin plain-text REPORT (counts + the text answers the public page withholds)
622func sv2_report(prefix: *u8, id: *u8, out: *u8, cap: i64) -> i64 {
623 let mks: *i64 = sys_mmap(8 * 32) as *i64
624 let mvs: *i64 = sys_mmap(8 * 32) as *i64
625 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
626 if mnf < 0 { return 0 - 1 }
627 let title: *u8 = sv_get(mks, mvs, mnf, "title" as *u8)
628 let stt: *u8 = sv_get(mks, mvs, mnf, "status" as *u8)
629 let nq: i64 = se_field_n(mks, mvs, mnf, "nq" as *u8, 0)
630 let anon: i64 = se_field_n(mks, mvs, mnf, "anon" as *u8, 1)
631 let bk: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
632 let bv: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
633 let bl: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
634 let nb: i64 = se_ballots(prefix, id, bk, bv, bl, SV2_MAXB)
635 if nb < 0 { return sv2_err(out, "500 Internal Server Error" as *u8, "tally walk hit its bound" as *u8) }
636 var o: i64 = se_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nSURVEY-REPORT " as *u8)
637 o = se_cat(out, o, id)
638 o = se_cat(out, o, "\ntitle: " as *u8)
639 if (title as i64) != 0 { o = se_cat(out, o, title) }
640 o = se_cat(out, o, "\nstatus: " as *u8)
641 if (stt as i64) != 0 { o = se_cat(out, o, stt) }
642 o = se_cat(out, o, "\nresponses: " as *u8)
643 o = se_catn(out, o, nb)
644 o = se_catc(out, o, 10)
645 var q: i64 = 1
646 while q <= nq {
647 let qks: *i64 = sys_mmap(8 * 32) as *i64
648 let qvs: *i64 = sys_mmap(8 * 32) as *i64
649 let qnf: i64 = se_qst(prefix, id, q, qks, qvs, 32)
650 if qnf > 0 {
651 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
652 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
653 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
654 if (qid as i64) != 0 { if (kind as i64) != 0 { if (prompt as i64) != 0 {
655 let kc: i64 = kind[0] as i64
656 o = se_cat(out, o, "\nQ " as *u8)
657 o = se_cat(out, o, qid)
658 o = se_cat(out, o, ": " as *u8)
659 o = se_cat(out, o, prompt)
660 o = se_catc(out, o, 10)
661 if kc == 67 {
662 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
663 if (opts as i64) != 0 {
664 let no: i64 = se_nopts(opts)
665 let ob: *u8 = sys_mmap(96)
666 let vb: *u8 = sys_mmap(16)
667 var k: i64 = 0
668 while k < no {
669 se_opt(opts, k, ob, 96)
670 se_catn(vb, 0, k)
671 var ve: i64 = 0
672 while vb[ve] != (0 as u8) { ve = ve + 1 }
673 vb[ve] = 0 as u8
674 o = se_cat(out, o, " " as *u8)
675 o = se_cat(out, o, ob)
676 o = se_cat(out, o, ": " as *u8)
677 o = se_catn(out, o, se_count_eq(bv, bl, nb, qid, vb))
678 o = se_catc(out, o, 10)
679 k = k + 1
680 }
681 }
682 } else { if kc == 83 {
683 let nst: *i64 = sys_mmap(32) as *i64
684 se_num_stats(bv, bl, nb, qid, nst)
685 o = se_cat(out, o, " n=" as *u8)
686 o = se_catn(out, o, nst[0])
687 o = se_cat(out, o, " sum=" as *u8)
688 o = se_catn(out, o, nst[1])
689 o = se_cat(out, o, " avg=" as *u8)
690 if nst[0] > 0 { o = sv2_fixed2(out, o, nst[1] * 100 / nst[0]) } else { o = se_cat(out, o, "-" as *u8) }
691 o = se_catc(out, o, 10)
692 } else {
693 let ab: *u8 = sys_mmap(4096)
694 var i: i64 = 0
695 while i < nb {
696 if o > cap - 8192 { o = se_cat(out, o, " ... (truncated)\n" as *u8); i = nb } else {
697 if se_answer_of(bv[i] as *u8, bl[i], qid, ab, 4096) > 0 {
698 o = se_cat(out, o, " - " as *u8)
699 if anon == 0 {
700 o = se_catc(out, o, 91)
701 o = se_cat(out, o, bk[i] as *u8)
702 o = se_catc(out, o, 93)
703 o = se_catc(out, o, 32)
704 }
705 o = se_cat(out, o, ab)
706 o = se_catc(out, o, 10)
707 }
708 i = i + 1
709 }
710 }
711 } }
712 } } }
713 }
714 q = q + 1
715 }
716 return o
717}
718// serve the progressive-enhancement client from CWD (JS lives in a FILE -- the nx_cc literal trap)
719// ---- insights sub-renderers (one per section; kept separate so no single function blows the nx_cc frame
720// limit, and single-responsibility). Each takes the running offset o and returns the new o. ----
721// A+B: scale descriptive table + concern flags (bottom-2-box = who needs follow-up)
722func sv2_ins_scale(out: *u8, o0: i64, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, qprompt: *i64, qmax: *i64, qmin: *i64, nqg: i64) -> i64 {
723 let xs: *i64 = sys_mmap(8 * (nb + 4)) as *i64
724 let dd: *i64 = sys_mmap(8 * 8) as *i64
725 let npo: *i64 = sys_mmap(8 * 8) as *i64
726 var o: i64 = se_cat(out, o0, "<div class='card'><h3 style='margin:0 0 8px'>Scale summary</h3><table><thead><tr><th>Question</th><th>n</th><th>Mean</th><th>Median</th><th>Std dev</th><th>Top-2-box</th><th>NPS</th></tr></thead><tbody>" as *u8)
727 var i: i64 = 0
728 while i < nqg {
729 if qkind[i] == 83 {
730 let m: i64 = st_collect(bv, bl, nb, qidp[i] as *u8, xs)
731 o = se_cat(out, o, "<tr><td>" as *u8)
732 o = sv2_esc(out, o, qprompt[i] as *u8)
733 o = se_cat(out, o, "</td><td>" as *u8)
734 o = se_catn(out, o, m)
735 if m > 0 {
736 st_desc(xs, m, dd)
737 o = se_cat(out, o, "</td><td>" as *u8)
738 o = sv2_fixed2(out, o, dd[3] / 10)
739 o = se_cat(out, o, "</td><td>" as *u8)
740 o = sv2_fixed2(out, o, dd[4] / 10)
741 o = se_cat(out, o, "</td><td>" as *u8)
742 o = sv2_fixed2(out, o, dd[5] / 10)
743 o = se_cat(out, o, "</td><td>" as *u8)
744 o = se_catn(out, o, st_topbox(xs, m, qmax[i], 2) / 10)
745 o = se_cat(out, o, "%</td><td>" as *u8)
746 if qmax[i] == 10 {
747 st_nps(xs, m, 9, 6, npo)
748 if npo[0] >= 0 { o = se_cat(out, o, "+" as *u8) }
749 o = se_catn(out, o, npo[0])
750 } else { o = se_cat(out, o, "—" as *u8) }
751 o = se_cat(out, o, "</td></tr>" as *u8)
752 } else { o = se_cat(out, o, "</td><td>—</td><td>—</td><td>—</td><td>—</td><td>—</td></tr>" as *u8) }
753 }
754 i = i + 1
755 }
756 o = se_cat(out, o, "</tbody></table><p class='hint'>NPS is shown beside the full distribution, mean and margin — its predictive validity is actively studied and criticized in the banked 2024-26 literature; never read it alone (evidence gate).</p></div>" as *u8)
757 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>Concern flags (bottom-2-box)</h3><table><thead><tr><th>Question</th><th>In need</th><th>Share</th></tr></thead><tbody>" as *u8)
758 i = 0
759 while i < nqg {
760 if qkind[i] == 83 {
761 let m: i64 = st_collect(bv, bl, nb, qidp[i] as *u8, xs)
762 if m > 0 {
763 let thr: i64 = qmin[i] + 1
764 var cc: i64 = 0
765 var j: i64 = 0
766 while j < m { if xs[j] <= thr { cc = cc + 1 } j = j + 1 }
767 o = se_cat(out, o, "<tr><td>" as *u8)
768 o = sv2_esc(out, o, qprompt[i] as *u8)
769 o = se_cat(out, o, "</td><td><b>" as *u8)
770 o = se_catn(out, o, cc)
771 o = se_cat(out, o, "</b> of " as *u8)
772 o = se_catn(out, o, m)
773 o = se_cat(out, o, "</td><td>" as *u8)
774 o = se_catn(out, o, cc * 100 / m)
775 o = se_cat(out, o, "%</td></tr>" as *u8)
776 }
777 }
778 i = i + 1
779 }
780 o = se_cat(out, o, "</tbody></table><p class='hint'>Bottom-2-box = respondents at or near the low end — the follow-up list.</p></div>" as *u8)
781 return o
782}
783// C: segment breakdowns -- mean of each scale within each level of each choice question
784func sv2_ins_seg(out: *u8, o0: i64, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, qprompt: *i64, qopts: *i64, qnopt: *i64, nqg: i64) -> i64 {
785 var o: i64 = o0
786 let sn: *i64 = sys_mmap(8 * 16) as *i64
787 let smp: *i64 = sys_mmap(8 * 16) as *i64
788 let ob: *u8 = sys_mmap(96)
789 var si: i64 = 0
790 while si < nqg {
791 if qkind[si] == 67 {
792 var ti: i64 = 0
793 while ti < nqg {
794 if qkind[ti] == 83 {
795 let segn: i64 = qnopt[si]
796 if segn > 0 { if segn <= 12 {
797 st_segment_means(bv, bl, nb, qidp[si] as *u8, qidp[ti] as *u8, segn, sn, smp)
798 var levels: i64 = 0
799 var g: i64 = 0
800 while g < segn { if sn[g] > 0 { levels = levels + 1 } g = g + 1 }
801 if levels >= 2 {
802 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>" as *u8)
803 o = sv2_esc(out, o, qprompt[ti] as *u8)
804 o = se_cat(out, o, " — by " as *u8)
805 o = sv2_esc(out, o, qprompt[si] as *u8)
806 o = se_cat(out, o, "</h3><table><thead><tr><th>Group</th><th>n</th><th>Mean</th></tr></thead><tbody>" as *u8)
807 g = 0
808 while g < segn {
809 se_opt(qopts[si] as *u8, g, ob, 96)
810 o = se_cat(out, o, "<tr><td>" as *u8)
811 o = sv2_esc(out, o, ob)
812 o = se_cat(out, o, "</td><td>" as *u8)
813 o = se_catn(out, o, sn[g])
814 o = se_cat(out, o, "</td><td>" as *u8)
815 if sn[g] > 0 { o = sv2_fixed2(out, o, smp[g] / 10) } else { o = se_cat(out, o, "—" as *u8) }
816 o = se_cat(out, o, "</td></tr>" as *u8)
817 g = g + 1
818 }
819 o = se_cat(out, o, "</tbody></table></div>" as *u8)
820 }
821 } }
822 }
823 ti = ti + 1
824 }
825 }
826 si = si + 1
827 }
828 return o
829}
830// D: cross-tabs + chi-square significance for each pair of choice questions
831func sv2_ins_xtab(out: *u8, o0: i64, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, qprompt: *i64, qopts: *i64, qnopt: *i64, nqg: i64) -> i64 {
832 var o: i64 = o0
833 let ctab: *i64 = sys_mmap(8 * 200) as *i64
834 let cco: *i64 = sys_mmap(8 * 4) as *i64
835 let obc: *u8 = sys_mmap(96)
836 var a: i64 = 0
837 while a < nqg {
838 if qkind[a] == 67 {
839 var b: i64 = a + 1
840 while b < nqg {
841 if qkind[b] == 67 {
842 let ra: i64 = qnopt[a]
843 let cb: i64 = qnopt[b]
844 if ra > 0 { if cb > 0 { if ra <= 12 { if cb <= 12 {
845 let grand: i64 = st_xtab(bv, bl, nb, qidp[a] as *u8, qidp[b] as *u8, ra, cb, ctab)
846 if grand > 0 {
847 let sig: i64 = st_chisq(ctab, ra, cb, cco)
848 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>" as *u8)
849 o = sv2_esc(out, o, qprompt[a] as *u8)
850 o = se_cat(out, o, " × " as *u8)
851 o = sv2_esc(out, o, qprompt[b] as *u8)
852 o = se_cat(out, o, "</h3><table><thead><tr><th></th>" as *u8)
853 var cj: i64 = 0
854 while cj < cb { se_opt(qopts[b] as *u8, cj, obc, 96); o = se_cat(out, o, "<th>" as *u8); o = sv2_esc(out, o, obc); o = se_cat(out, o, "</th>" as *u8); cj = cj + 1 }
855 o = se_cat(out, o, "</tr></thead><tbody>" as *u8)
856 var ri: i64 = 0
857 while ri < ra {
858 se_opt(qopts[a] as *u8, ri, obc, 96)
859 o = se_cat(out, o, "<tr><td><b>" as *u8)
860 o = sv2_esc(out, o, obc)
861 o = se_cat(out, o, "</b></td>" as *u8)
862 cj = 0
863 while cj < cb { o = se_cat(out, o, "<td>" as *u8); o = se_catn(out, o, ctab[ri * cb + cj]); o = se_cat(out, o, "</td>" as *u8); cj = cj + 1 }
864 o = se_cat(out, o, "</tr>" as *u8)
865 ri = ri + 1
866 }
867 o = se_cat(out, o, "</tbody></table><p style='margin:8px 0 0'>chi-square " as *u8)
868 o = sv2_fixed2(out, o, cco[0] / 10)
869 o = se_cat(out, o, ", df " as *u8)
870 o = se_catn(out, o, cco[1])
871 o = se_cat(out, o, " — " as *u8)
872 if sig == 1 { o = se_cat(out, o, "<b>significant</b> association (p < .05)" as *u8) } else { o = se_cat(out, o, "not statistically significant at .05" as *u8) }
873 o = se_cat(out, o, "</p></div>" as *u8)
874 }
875 } } } }
876 }
877 b = b + 1
878 }
879 }
880 a = a + 1
881 }
882 return o
883}
884// ---- machine-readable results JSON (aggregate-only, k-floored, NO free text; all-integer permille, no float) ----
885func j_q(out: *u8, o: i64) -> i64 { out[o] = 34 as u8; return o + 1 }
886func j_key(out: *u8, o: i64, k: *u8) -> i64 { var oo: i64 = j_q(out, o); oo = se_cat(out, oo, k); oo = j_q(out, oo); out[oo] = 58 as u8; return oo + 1 }
887func j_str(out: *u8, o: i64, s: *u8) -> i64 {
888 var oo: i64 = j_q(out, o)
889 var i: i64 = 0
890 while s[i] != (0 as u8) {
891 let c: i64 = s[i] as i64
892 if c == 34 { out[oo] = 92 as u8; oo = oo + 1; out[oo] = 34 as u8; oo = oo + 1 } else {
893 if c == 92 { out[oo] = 92 as u8; oo = oo + 1; out[oo] = 92 as u8; oo = oo + 1 } else {
894 if c < 32 { out[oo] = 32 as u8; oo = oo + 1 } else { out[oo] = s[i]; oo = oo + 1 } } }
895 i = i + 1
896 }
897 return j_q(out, oo)
898}
899func sv2_results_json(prefix: *u8, id: *u8, out: *u8, cap: i64) -> i64 {
900 let mks: *i64 = sys_mmap(8 * 32) as *i64
901 let mvs: *i64 = sys_mmap(8 * 32) as *i64
902 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
903 if mnf < 0 { return 0 - 1 }
904 let rmode: *u8 = sv_get(mks, mvs, mnf, "results" as *u8)
905 if (rmode as i64) != 0 { if se_seq(rmode, "admin" as *u8) == 1 {
906 var eo: i64 = se_cat(out, 0, "HTTP/1.1 403 Forbidden\r\nContent-Type: application/json; charset=utf-8\r\nAccess-Control-Allow-Origin: *\r\n\r\n{" as *u8)
907 eo = j_key(out, eo, "error" as *u8)
908 eo = j_str(out, eo, "results are admin-only for this survey" as *u8)
909 out[eo] = 125 as u8
910 return eo + 1
911 } }
912 let title: *u8 = sv_get(mks, mvs, mnf, "title" as *u8)
913 let status: *u8 = sv_get(mks, mvs, mnf, "status" as *u8)
914 let nq: i64 = se_field_n(mks, mvs, mnf, "nq" as *u8, 0)
915 let kmin: i64 = se_field_n(mks, mvs, mnf, "kmin" as *u8, 0)
916 let bk: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
917 let bv: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
918 let bl: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
919 let nb: i64 = se_ballots(prefix, id, bk, bv, bl, SV2_MAXB)
920 if nb < 0 { return sv2_err(out, "500 Internal Server Error" as *u8, "tally walk hit its bound" as *u8) }
921 var o: i64 = se_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=utf-8\r\nAccess-Control-Allow-Origin: *\r\n\r\n{" as *u8)
922 o = j_key(out, o, "v" as *u8); o = se_cat(out, o, "1," as *u8)
923 o = j_key(out, o, "survey" as *u8); o = j_str(out, o, id); o = se_cat(out, o, "," as *u8)
924 o = j_key(out, o, "title" as *u8); if (title as i64) != 0 { o = j_str(out, o, title) } else { o = se_cat(out, o, "\"\"" as *u8) } o = se_cat(out, o, "," as *u8)
925 o = j_key(out, o, "status" as *u8); if (status as i64) != 0 { o = j_str(out, o, status) } else { o = se_cat(out, o, "\"open\"" as *u8) } o = se_cat(out, o, "," as *u8)
926 o = j_key(out, o, "responses" as *u8); o = se_catn(out, o, nb); o = se_cat(out, o, "," as *u8)
927 o = j_key(out, o, "note" as *u8); o = j_str(out, o, "aggregate-only, integer permille, free text excluded by design" as *u8); o = se_cat(out, o, "," as *u8)
928 if nb < kmin {
929 o = j_key(out, o, "withheld" as *u8); o = se_cat(out, o, "true," as *u8)
930 o = j_key(out, o, "kmin" as *u8); o = se_catn(out, o, kmin)
931 out[o] = 125 as u8
932 return o + 1
933 }
934 o = j_key(out, o, "withheld" as *u8); o = se_cat(out, o, "false," as *u8)
935 o = j_key(out, o, "questions" as *u8); o = se_cat(out, o, "[" as *u8)
936 let xs: *i64 = sys_mmap(8 * (nb + 4)) as *i64
937 let dd: *i64 = sys_mmap(8 * 8) as *i64
938 let npo: *i64 = sys_mmap(8 * 8) as *i64
939 var q: i64 = 1
940 var emitted: i64 = 0
941 while q <= nq {
942 let qks: *i64 = sys_mmap(8 * 32) as *i64
943 let qvs: *i64 = sys_mmap(8 * 32) as *i64
944 let qnf: i64 = se_qst(prefix, id, q, qks, qvs, 32)
945 if qnf > 0 {
946 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
947 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
948 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
949 if (qid as i64) != 0 { if (kind as i64) != 0 { if (prompt as i64) != 0 {
950 let kc: i64 = kind[0] as i64
951 if emitted == 1 { o = se_cat(out, o, "," as *u8) }
952 emitted = 1
953 o = se_cat(out, o, "{" as *u8)
954 o = j_key(out, o, "qid" as *u8); o = j_str(out, o, qid); o = se_cat(out, o, "," as *u8)
955 o = j_key(out, o, "kind" as *u8); o = j_str(out, o, kind); o = se_cat(out, o, "," as *u8)
956 o = j_key(out, o, "prompt" as *u8); o = j_str(out, o, prompt); o = se_cat(out, o, "," as *u8)
957 if kc == 67 {
958 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
959 let no: i64 = se_nopts(opts)
960 let ob: *u8 = sys_mmap(96)
961 let vb: *u8 = sys_mmap(16)
962 var ans: i64 = 0
963 var k: i64 = 0
964 while k < no { se_catn(vb, 0, k); var ve: i64 = 0; while vb[ve] != (0 as u8) { ve = ve + 1 } vb[ve] = 0 as u8; ans = ans + se_count_eq(bv, bl, nb, qid, vb); k = k + 1 }
965 o = j_key(out, o, "n" as *u8); o = se_catn(out, o, ans); o = se_cat(out, o, "," as *u8)
966 o = j_key(out, o, "options" as *u8); o = se_cat(out, o, "[" as *u8)
967 k = 0
968 while k < no {
969 se_opt(opts, k, ob, 96)
970 se_catn(vb, 0, k)
971 var ve2: i64 = 0
972 while vb[ve2] != (0 as u8) { ve2 = ve2 + 1 }
973 vb[ve2] = 0 as u8
974 let c: i64 = se_count_eq(bv, bl, nb, qid, vb)
975 var pct: i64 = 0
976 if ans > 0 { pct = c * 1000 / ans }
977 if k > 0 { o = se_cat(out, o, "," as *u8) }
978 o = se_cat(out, o, "{" as *u8)
979 o = j_key(out, o, "label" as *u8); o = j_str(out, o, ob); o = se_cat(out, o, "," as *u8)
980 o = j_key(out, o, "count" as *u8); o = se_catn(out, o, c); o = se_cat(out, o, "," as *u8)
981 o = j_key(out, o, "pct_permille" as *u8); o = se_catn(out, o, pct)
982 o = se_cat(out, o, "}" as *u8)
983 k = k + 1
984 }
985 o = se_cat(out, o, "]" as *u8)
986 } else { if kc == 83 {
987 let lo: i64 = se_field_n(qks, qvs, qnf, "min" as *u8, 0)
988 let hi: i64 = se_field_n(qks, qvs, qnf, "max" as *u8, 10)
989 let m: i64 = st_collect(bv, bl, nb, qid, xs)
990 o = j_key(out, o, "n" as *u8); o = se_catn(out, o, m); o = se_cat(out, o, "," as *u8)
991 st_desc(xs, m, dd)
992 o = j_key(out, o, "mean_permille" as *u8); o = se_catn(out, o, dd[3]); o = se_cat(out, o, "," as *u8)
993 o = j_key(out, o, "median_permille" as *u8); o = se_catn(out, o, dd[4]); o = se_cat(out, o, "," as *u8)
994 o = j_key(out, o, "stddev_permille" as *u8); o = se_catn(out, o, dd[5]); o = se_cat(out, o, "," as *u8)
995 let tbpm: i64 = st_topbox(xs, m, hi, 2)
996 let tbk: i64 = (tbpm * m + 500) / 1000
997 o = j_key(out, o, "top2box_permille" as *u8); o = se_catn(out, o, tbpm); o = se_cat(out, o, "," as *u8)
998 o = j_key(out, o, "margin_permille" as *u8); o = se_catn(out, o, st_ci_margin(tbk, m)); o = se_cat(out, o, "," as *u8)
999 if hi == 10 { st_nps(xs, m, 9, 6, npo); o = j_key(out, o, "nps" as *u8); o = se_catn(out, o, npo[0]); o = se_cat(out, o, "," as *u8) }
1000 o = j_key(out, o, "dist" as *u8); o = se_cat(out, o, "[" as *u8)
1001 let vb3: *u8 = sys_mmap(16)
1002 var v: i64 = lo
1003 while v <= hi {
1004 se_catn(vb3, 0, v)
1005 var v3e: i64 = 0
1006 while vb3[v3e] != (0 as u8) { v3e = v3e + 1 }
1007 vb3[v3e] = 0 as u8
1008 if v > lo { o = se_cat(out, o, "," as *u8) }
1009 o = se_cat(out, o, "{" as *u8)
1010 o = j_key(out, o, "value" as *u8); o = se_catn(out, o, v); o = se_cat(out, o, "," as *u8)
1011 o = j_key(out, o, "count" as *u8); o = se_catn(out, o, se_count_eq(bv, bl, nb, qid, vb3))
1012 o = se_cat(out, o, "}" as *u8)
1013 v = v + 1
1014 }
1015 o = se_cat(out, o, "]" as *u8)
1016 } else {
1017 o = j_key(out, o, "text_count" as *u8); o = se_catn(out, o, se_text_count(bv, bl, nb, qid))
1018 } }
1019 o = se_cat(out, o, "}" as *u8)
1020 } } }
1021 }
1022 q = q + 1
1023 }
1024 o = se_cat(out, o, "]}" as *u8)
1025 return o
1026}
1027// E: key drivers -- correlation of each scale to the outcome (last scale question), ranked
1028func sv2_ins_drivers(out: *u8, o0: i64, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, qprompt: *i64, nqg: i64) -> i64 {
1029 var o: i64 = o0
1030 let xs: *i64 = sys_mmap(8 * (nb + 4)) as *i64
1031 let ys: *i64 = sys_mmap(8 * (nb + 4)) as *i64
1032 var outc: i64 = 0 - 1
1033 var i: i64 = 0
1034 while i < nqg { if qkind[i] == 83 { outc = i } i = i + 1 }
1035 if outc < 0 { return o }
1036 let dvi: *i64 = sys_mmap(8 * 32) as *i64
1037 let dvr: *i64 = sys_mmap(8 * 32) as *i64
1038 var nd: i64 = 0
1039 i = 0
1040 while i < nqg {
1041 if qkind[i] == 83 { if i != outc {
1042 let m: i64 = st_collect_pairs(bv, bl, nb, qidp[i] as *u8, qidp[outc] as *u8, xs, ys)
1043 if m >= 2 { dvi[nd] = i; dvr[nd] = st_corr(xs, ys, m); nd = nd + 1 }
1044 } }
1045 i = i + 1
1046 }
1047 if nd <= 0 { return o }
1048 var p: i64 = 0
1049 while p < nd {
1050 var best: i64 = p
1051 var t: i64 = p + 1
1052 while t < nd {
1053 var ar: i64 = dvr[t]
1054 if ar < 0 { ar = 0 - ar }
1055 var ab2: i64 = dvr[best]
1056 if ab2 < 0 { ab2 = 0 - ab2 }
1057 if ar > ab2 { best = t }
1058 t = t + 1
1059 }
1060 let ti2: i64 = dvi[p]; dvi[p] = dvi[best]; dvi[best] = ti2
1061 let tr2: i64 = dvr[p]; dvr[p] = dvr[best]; dvr[best] = tr2
1062 p = p + 1
1063 }
1064 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>Key drivers of “" as *u8)
1065 o = sv2_esc(out, o, qprompt[outc] as *u8)
1066 o = se_cat(out, o, "”</h3><table><thead><tr><th>Factor</th><th>Correlation</th></tr></thead><tbody>" as *u8)
1067 p = 0
1068 while p < nd {
1069 o = se_cat(out, o, "<tr><td>" as *u8)
1070 o = sv2_esc(out, o, qprompt[dvi[p]] as *u8)
1071 o = se_cat(out, o, "</td><td>" as *u8)
1072 if dvr[p] >= 0 { o = se_cat(out, o, "+" as *u8) }
1073 o = sv2_fixed2(out, o, dvr[p] / 10)
1074 o = se_cat(out, o, "</td></tr>" as *u8)
1075 p = p + 1
1076 }
1077 o = se_cat(out, o, "</tbody></table><p class='hint'>Pearson correlation (−1.00 to +1.00); the factors that move with the outcome. Correlation is not causation.</p></div>" as *u8)
1078 return o
1079}
1080// F: open-text themes (sovereign frequency)
1081func sv2_ins_themes(out: *u8, o0: i64, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, qprompt: *i64, nqg: i64) -> i64 {
1082 var o: i64 = o0
1083 let terms: *i64 = sys_mmap(8 * 128) as *i64
1084 let freqs: *i64 = sys_mmap(8 * 128) as *i64
1085 var i: i64 = 0
1086 while i < nqg {
1087 if qkind[i] == 84 {
1088 let nt: i64 = st_text_themes(bv, bl, nb, qidp[i] as *u8, terms, freqs, 128)
1089 if nt > 0 {
1090 var p: i64 = 0
1091 while p < nt {
1092 var best: i64 = p
1093 var t: i64 = p + 1
1094 while t < nt { if freqs[t] > freqs[best] { best = t } t = t + 1 }
1095 let ttp: i64 = terms[p]; terms[p] = terms[best]; terms[best] = ttp
1096 let tfp: i64 = freqs[p]; freqs[p] = freqs[best]; freqs[best] = tfp
1097 p = p + 1
1098 }
1099 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>Themes — " as *u8)
1100 o = sv2_esc(out, o, qprompt[i] as *u8)
1101 o = se_cat(out, o, "</h3><p>" as *u8)
1102 var lim: i64 = nt
1103 if lim > 12 { lim = 12 }
1104 p = 0
1105 while p < lim {
1106 o = se_cat(out, o, "<span style='display:inline-block;margin:3px 6px 3px 0;padding:3px 10px;border-radius:14px;background:rgb(238,241,248);color:rgb(42,77,143);font-size:.9rem'>" as *u8)
1107 o = sv2_esc(out, o, terms[p] as *u8)
1108 o = se_cat(out, o, " " as *u8)
1109 o = se_catn(out, o, freqs[p])
1110 o = se_cat(out, o, "</span>" as *u8)
1111 p = p + 1
1112 }
1113 o = se_cat(out, o, "</p><p class='hint'>Word frequency across written answers (sovereign, frequency-based). See the Tone card for emotional valence; neural topic modeling is the named next rung.</p></div>" as *u8)
1114 }
1115 }
1116 i = i + 1
1117 }
1118 return o
1119}
1120// H: OPEN-TEXT SENTIMENT (tone). Per text question, a VADER-style integer compound score aggregated across
1121// the written answers: warming / balanced / cooling + a positive/neutral/negative split. Negation- and
1122// intensifier-aware (nx_survey_sentiment), grounded in banked 2024-26 sentiment research (swc_svy_sent).
1123// Admin-only, like themes: it reads the free text that is WALLED from every public page.
1124func sv2_ins_sentiment(out: *u8, o0: i64, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, qprompt: *i64, nqg: i64) -> i64 {
1125 var o: i64 = o0
1126 let words: *i64 = sys_mmap(8 * SN_MAXLEX) as *i64
1127 let kinds: *i64 = sys_mmap(8 * SN_MAXLEX) as *i64
1128 let vals: *i64 = sys_mmap(8 * SN_MAXLEX) as *i64
1129 let nlex: i64 = sn_load(SN_LEX, words, kinds, vals, SN_MAXLEX)
1130 if nlex < 1 { return o }
1131 let agg: *i64 = sys_mmap(8 * 8) as *i64
1132 var i: i64 = 0
1133 while i < nqg {
1134 if qkind[i] == 84 {
1135 let n: i64 = sn_aggregate_over(bv, bl, nb, qidp[i] as *u8, words, kinds, vals, nlex, agg)
1136 if n > 0 {
1137 let mean: i64 = agg[1]
1138 let pos: i64 = agg[2]
1139 let neu: i64 = agg[3]
1140 let neg: i64 = agg[4]
1141 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>Tone — " as *u8)
1142 o = sv2_esc(out, o, qprompt[i] as *u8)
1143 o = se_cat(out, o, "</h3><p>" as *u8)
1144 if mean >= SN_POS_TH { o = se_cat(out, o, "<b style='color:rgb(26,127,55)'>Warming</b>" as *u8) } else { if mean <= SN_NEG_TH { o = se_cat(out, o, "<b style='color:rgb(176,38,38)'>Cooling</b>" as *u8) } else { o = se_cat(out, o, "<b style='color:rgb(90,94,110)'>Balanced</b>" as *u8) } }
1145 o = se_cat(out, o, " — mean tone " as *u8)
1146 if mean >= 0 { o = se_cat(out, o, "+" as *u8) }
1147 o = se_catn(out, o, mean)
1148 o = se_cat(out, o, "‰ across " as *u8)
1149 o = se_catn(out, o, n)
1150 o = se_cat(out, o, " written answer(s).</p>" as *u8)
1151 // proportional pos/neu/neg bar (percent of n; neutral = remainder so widths always sum to 100)
1152 let pp: i64 = (pos * 100) / n
1153 let gp2: i64 = (neg * 100) / n
1154 var up: i64 = 100 - pp - gp2
1155 if up < 0 { up = 0 }
1156 o = se_cat(out, o, "<div style='display:flex;height:14px;border-radius:7px;overflow:hidden;background:rgb(238,241,248)'>" as *u8)
1157 o = se_cat(out, o, "<div style='width:" as *u8); o = se_catn(out, o, pp); o = se_cat(out, o, "%;background:rgb(26,127,55)'></div>" as *u8)
1158 o = se_cat(out, o, "<div style='width:" as *u8); o = se_catn(out, o, up); o = se_cat(out, o, "%;background:rgb(200,205,216)'></div>" as *u8)
1159 o = se_cat(out, o, "<div style='width:" as *u8); o = se_catn(out, o, gp2); o = se_cat(out, o, "%;background:rgb(176,38,38)'></div>" as *u8)
1160 o = se_cat(out, o, "</div><p class='hint'>" as *u8)
1161 o = se_catn(out, o, pos); o = se_cat(out, o, " positive · " as *u8)
1162 o = se_catn(out, o, neu); o = se_cat(out, o, " neutral · " as *u8)
1163 o = se_catn(out, o, neg); o = se_cat(out, o, " negative — sovereign lexicon sentiment, negation- and intensifier-aware (grounded in banked 2024-26 research); a screening signal, not a verdict.</p></div>" as *u8)
1164 }
1165 }
1166 i = i + 1
1167 }
1168 return o
1169}
1170// G: data-quality / careless-response screening (C/IER). Attention-check pass rate (if the survey has @check
1171// items) + straight-lining over the scale block. FLAGS, never drops -- genuine low-but-varied answers are not
1172// flagged (gate-proven). Longstring is a weak signal on short surveys; the @check attention item is the strong guard.
1173func sv2_ins_quality(out: *u8, o0: i64, prefix: *u8, id: *u8, bv: *i64, bl: *i64, nb: i64, qkind: *i64, qidp: *i64, nqg: i64) -> i64 {
1174 var o: i64 = o0
1175 let mks: *i64 = sys_mmap(8 * 32) as *i64
1176 let mvs: *i64 = sys_mmap(8 * 32) as *i64
1177 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
1178 let empty: *u8 = sys_mmap(4)
1179 empty[0] = 0 as u8
1180 var checks: *u8 = empty
1181 if mnf > 0 {
1182 let cv: *u8 = sv_get(mks, mvs, mnf, "checks" as *u8)
1183 if (cv as i64) != 0 { checks = cv }
1184 }
1185 let sqids: *i64 = sys_mmap(8 * 32) as *i64
1186 var nsq: i64 = 0
1187 var i: i64 = 0
1188 while i < nqg { if qkind[i] == 83 { if nsq < 32 { sqids[nsq] = qidp[i]; nsq = nsq + 1 } } i = i + 1 }
1189 var ls_thresh: i64 = 0
1190 if nsq >= 3 { ls_thresh = nsq }
1191 var have_checks: i64 = 0
1192 if checks[0] != (0 as u8) { have_checks = 1 }
1193 if ls_thresh == 0 { if have_checks == 0 { return o } }
1194 let ro: *i64 = sys_mmap(32) as *i64
1195 sq_rollup(bv, bl, nb, checks, sqids, nsq, ls_thresh, ro)
1196 o = se_cat(out, o, "<div class='card'><h3 style='margin:0 0 8px'>Data quality (careless-response screening)</h3>" as *u8)
1197 o = se_cat(out, o, "<p style='margin:0 0 6px'><b>" as *u8)
1198 o = se_catn(out, o, ro[0])
1199 o = se_cat(out, o, "</b> clean · <b>" as *u8)
1200 o = se_catn(out, o, ro[1])
1201 o = se_cat(out, o, "</b> flagged for review of " as *u8)
1202 o = se_catn(out, o, nb)
1203 o = se_cat(out, o, " response(s)</p><table><tbody>" as *u8)
1204 if have_checks == 1 {
1205 o = se_cat(out, o, "<tr><td>Failed an attention check</td><td><b>" as *u8)
1206 o = se_catn(out, o, ro[2])
1207 o = se_cat(out, o, "</b></td></tr>" as *u8)
1208 }
1209 if ls_thresh > 0 {
1210 o = se_cat(out, o, "<tr><td>Straight-lined (all " as *u8)
1211 o = se_catn(out, o, nsq)
1212 o = se_cat(out, o, " scale answers identical)</td><td><b>" as *u8)
1213 o = se_catn(out, o, ro[3])
1214 o = se_cat(out, o, "</b></td></tr>" as *u8)
1215 }
1216 o = se_cat(out, o, "</tbody></table><p class='hint'>Flagged responses are surfaced for review, NEVER dropped. A genuinely struggling respondent with varied low answers is NOT flagged (gate-proven). Straight-lining is a weak signal on short surveys — add a `@check <qid> <answer>` instructed-response item (the evidence-backed gold standard) for a strong guard.</p></div>" as *u8)
1217 return o
1218}
1219// ---- ADMIN deep-insights report (key-gated; the analytics layer over nx_survey_stats) ----
1220// Gathers the survey's questions, then calls the section renderers above: descriptive table + concern
1221// flags, segment breakdowns (the killer insight), cross-tabs + chi-square significance, key drivers,
1222// and open-text themes. Admin-only because breakdowns can reveal small cells.
1223func sv2_insights(prefix: *u8, base: *u8, id: *u8, out: *u8, cap: i64) -> i64 {
1224 let mks: *i64 = sys_mmap(8 * 32) as *i64
1225 let mvs: *i64 = sys_mmap(8 * 32) as *i64
1226 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
1227 if mnf < 0 { return 0 - 1 }
1228 let title: *u8 = sv_get(mks, mvs, mnf, "title" as *u8)
1229 let nq: i64 = se_field_n(mks, mvs, mnf, "nq" as *u8, 0)
1230 if (title as i64) == 0 { return 0 - 1 }
1231 let bk: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
1232 let bv: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
1233 let bl: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
1234 let nb: i64 = se_ballots(prefix, id, bk, bv, bl, SV2_MAXB)
1235 if nb < 0 { return sv2_err(out, "500 Internal Server Error" as *u8, "tally walk hit its bound; compact the store" as *u8) }
1236 // gather question metadata (pointers into fresh per-question decode buffers stay valid)
1237 let qkind: *i64 = sys_mmap(8 * 32) as *i64
1238 let qidp: *i64 = sys_mmap(8 * 32) as *i64
1239 let qprompt: *i64 = sys_mmap(8 * 32) as *i64
1240 let qopts: *i64 = sys_mmap(8 * 32) as *i64
1241 let qmax: *i64 = sys_mmap(8 * 32) as *i64
1242 let qmin: *i64 = sys_mmap(8 * 32) as *i64
1243 let qnopt: *i64 = sys_mmap(8 * 32) as *i64
1244 var nqg: i64 = 0
1245 var q: i64 = 1
1246 while q <= nq {
1247 if nqg < 32 {
1248 let qks: *i64 = sys_mmap(8 * 32) as *i64
1249 let qvs: *i64 = sys_mmap(8 * 32) as *i64
1250 let qnf: i64 = se_qst(prefix, id, q, qks, qvs, 32)
1251 if qnf > 0 {
1252 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
1253 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
1254 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
1255 if (kind as i64) != 0 { if (qid as i64) != 0 { if (prompt as i64) != 0 {
1256 qkind[nqg] = kind[0] as i64
1257 qidp[nqg] = qid as i64
1258 qprompt[nqg] = prompt as i64
1259 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
1260 qopts[nqg] = opts as i64
1261 qmin[nqg] = se_field_n(qks, qvs, qnf, "min" as *u8, 0)
1262 qmax[nqg] = se_field_n(qks, qvs, qnf, "max" as *u8, 10)
1263 if (opts as i64) != 0 { qnopt[nqg] = se_nopts(opts) } else { qnopt[nqg] = 0 }
1264 nqg = nqg + 1
1265 } } }
1266 }
1267 }
1268 q = q + 1
1269 }
1270 var o: i64 = sv2_page_top(out, 0, title)
1271 o = se_cat(out, o, "<p><a href='" as *u8)
1272 o = se_cat(out, o, base)
1273 o = se_cat(out, o, "/'>← all surveys</a></p><h1>" as *u8)
1274 o = sv2_esc(out, o, title)
1275 o = se_cat(out, o, "</h1><p class='sub'>Insights report — <b>" as *u8)
1276 o = se_catn(out, o, nb)
1277 o = se_cat(out, o, "</b> response(s). Admin-only: breakdowns can reveal small groups.</p>" as *u8)
1278 if nb == 0 { o = se_cat(out, o, "<div class='card'><p>No responses yet.</p></div>" as *u8); o = sv2_page_foot(out, o); return o }
1279 o = sv2_ins_scale(out, o, bv, bl, nb, qkind, qidp, qprompt, qmax, qmin, nqg)
1280 o = sv2_ins_seg(out, o, bv, bl, nb, qkind, qidp, qprompt, qopts, qnopt, nqg)
1281 o = sv2_ins_xtab(out, o, bv, bl, nb, qkind, qidp, qprompt, qopts, qnopt, nqg)
1282 o = sv2_ins_drivers(out, o, bv, bl, nb, qkind, qidp, qprompt, nqg)
1283 o = sv2_ins_themes(out, o, bv, bl, nb, qkind, qidp, qprompt, nqg)
1284 o = sv2_ins_sentiment(out, o, bv, bl, nb, qkind, qidp, qprompt, nqg)
1285 o = sv2_ins_quality(out, o, prefix, id, bv, bl, nb, qkind, qidp, nqg)
1286 o = sv2_page_foot(out, o)
1287 return o
1288}
1289// ---- EMBEDDABLE one-question poll (the operator's original ask: "a simple one question poll on our tandem
1290// site"). Compact, self-contained (own CSS), iframe-friendly. GET = the poll; POST = record + inline results. ----
1291func sv2_embed_top(out: *u8, o0: i64, title: *u8) -> i64 {
1292 var o: i64 = se_cat(out, o0, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n<" as *u8)
1293 o = se_catc(out, o, 33)
1294 o = se_cat(out, o, "DOCTYPE html>\n<html lang='en'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'><title>" as *u8)
1295 o = sv2_esc(out, o, title)
1296 o = se_cat(out, o, "</title><style>*{box-sizing:border-box}body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;margin:0;padding:14px;color:rgb(24,26,34);background:rgb(255,255,255);line-height:1.45}\n" as *u8)
1297 o = se_cat(out, o, ".q{font-weight:600;font-size:1.05rem;margin:0 0 10px}.opt{display:block;padding:8px 11px;border:1px solid rgb(222,225,233);border-radius:8px;margin:6px 0;cursor:pointer}.opt:hover{background:rgb(240,243,250)}.opt input{margin-right:9px}\n" as *u8)
1298 o = se_cat(out, o, "button{background:rgb(42,77,143);color:rgb(255,255,255);border:0;border-radius:8px;padding:9px 20px;font-size:.98rem;font-weight:600;cursor:pointer;margin-top:6px}\n" as *u8)
1299 o = se_cat(out, o, ".row{display:flex;align-items:center;gap:9px;margin:5px 0}.lbl{flex:0 0 40%;font-size:.92rem}.barw{flex:1;background:rgb(238,241,248);border-radius:6px;height:16px;overflow:hidden}.bar{background:rgb(42,77,143);height:16px;display:block}.cnt{flex:0 0 2.6em;text-align:right;font-size:.88rem;color:rgb(105,110,125)}\n" as *u8)
1300 o = se_cat(out, o, ".by{margin:12px 0 0;font-size:.78rem;color:rgb(140,144,158)}.by a{color:rgb(105,110,125)}.n{font-size:.85rem;color:rgb(105,110,125);margin:8px 0 0}\n" as *u8)
1301 o = se_cat(out, o, "@media(prefers-color-scheme:dark){body{background:rgb(22,24,31);color:rgb(228,230,238)}.opt{border-color:rgb(44,48,60)}.opt:hover{background:rgb(30,33,44)}.barw{background:rgb(36,39,50)}}</style></head><body>" as *u8)
1302 return o
1303}
1304// compact inline results (bars) for the first question of a poll
1305func sv2_embed_results(prefix: *u8, base: *u8, id: *u8, out: *u8, cap: i64) -> i64 {
1306 let mks: *i64 = sys_mmap(8 * 32) as *i64
1307 let mvs: *i64 = sys_mmap(8 * 32) as *i64
1308 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
1309 if mnf < 0 { return 0 - 1 }
1310 let kmin: i64 = se_field_n(mks, mvs, mnf, "kmin" as *u8, 0)
1311 let qks: *i64 = sys_mmap(8 * 32) as *i64
1312 let qvs: *i64 = sys_mmap(8 * 32) as *i64
1313 let qnf: i64 = se_qst(prefix, id, 1, qks, qvs, 32)
1314 if qnf < 0 { return 0 - 1 }
1315 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
1316 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
1317 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
1318 if (qid as i64) == 0 { return 0 - 1 }
1319 let bk: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
1320 let bv: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
1321 let bl: *i64 = sys_mmap(8 * SV2_MAXB) as *i64
1322 let nb: i64 = se_ballots(prefix, id, bk, bv, bl, SV2_MAXB)
1323 if nb < 0 { return sv2_err(out, "500 Internal Server Error" as *u8, "tally bound" as *u8) }
1324 var o: i64 = sv2_embed_top(out, 0, prompt)
1325 o = se_cat(out, o, "<p class='q'>" as *u8)
1326 o = sv2_esc(out, o, prompt)
1327 o = se_cat(out, o, "</p>" as *u8)
1328 if nb < kmin {
1329 o = se_cat(out, o, "<p class='n'>Thanks! Results show after " as *u8)
1330 o = se_catn(out, o, kmin)
1331 o = se_cat(out, o, " votes.</p>" as *u8)
1332 } else {
1333 let kc: i64 = kind[0] as i64
1334 if kc == 67 {
1335 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
1336 let no: i64 = se_nopts(opts)
1337 let ob: *u8 = sys_mmap(96)
1338 let vb: *u8 = sys_mmap(16)
1339 var k: i64 = 0
1340 while k < no {
1341 se_opt(opts, k, ob, 96)
1342 se_catn(vb, 0, k)
1343 var ve: i64 = 0
1344 while vb[ve] != (0 as u8) { ve = ve + 1 }
1345 vb[ve] = 0 as u8
1346 let c: i64 = se_count_eq(bv, bl, nb, qid, vb)
1347 var pct: i64 = 0
1348 if nb > 0 { pct = c * 100 / nb }
1349 o = se_cat(out, o, "<div class='row'><span class='lbl'>" as *u8)
1350 o = sv2_esc(out, o, ob)
1351 o = se_cat(out, o, "</span><span class='barw'><span class='bar' style='width:" as *u8)
1352 o = se_catn(out, o, pct)
1353 o = se_cat(out, o, "%'></span></span><span class='cnt'>" as *u8)
1354 o = se_catn(out, o, c)
1355 o = se_cat(out, o, "</span></div>" as *u8)
1356 k = k + 1
1357 }
1358 } else {
1359 let lo: i64 = se_field_n(qks, qvs, qnf, "min" as *u8, 0)
1360 let hi: i64 = se_field_n(qks, qvs, qnf, "max" as *u8, 10)
1361 let vb2: *u8 = sys_mmap(16)
1362 var v: i64 = lo
1363 while v <= hi {
1364 se_catn(vb2, 0, v)
1365 var v2e: i64 = 0
1366 while vb2[v2e] != (0 as u8) { v2e = v2e + 1 }
1367 vb2[v2e] = 0 as u8
1368 let c2: i64 = se_count_eq(bv, bl, nb, qid, vb2)
1369 var pct2: i64 = 0
1370 if nb > 0 { pct2 = c2 * 100 / nb }
1371 o = se_cat(out, o, "<div class='row'><span class='lbl'>" as *u8)
1372 o = se_catn(out, o, v)
1373 o = se_cat(out, o, "</span><span class='barw'><span class='bar' style='width:" as *u8)
1374 o = se_catn(out, o, pct2)
1375 o = se_cat(out, o, "%'></span></span><span class='cnt'>" as *u8)
1376 o = se_catn(out, o, c2)
1377 o = se_cat(out, o, "</span></div>" as *u8)
1378 v = v + 1
1379 }
1380 }
1381 o = se_cat(out, o, "<p class='n'>" as *u8)
1382 o = se_catn(out, o, nb)
1383 o = se_cat(out, o, " response(s)</p>" as *u8)
1384 }
1385 o = se_cat(out, o, "<p class='by'>via <a href='" as *u8)
1386 o = se_cat(out, o, base)
1387 o = se_cat(out, o, "/s/" as *u8)
1388 o = se_cat(out, o, id)
1389 o = se_cat(out, o, "' target='_top'>Nishi Pulse</a></p></body></html>" as *u8)
1390 return o
1391}
1392// the poll form (first question), compact + iframe-friendly; app.js persists the token in the iframe origin.
1393func sv2_embed(prefix: *u8, base: *u8, id: *u8, tok: *u8, out: *u8, cap: i64) -> i64 {
1394 let mks: *i64 = sys_mmap(8 * 32) as *i64
1395 let mvs: *i64 = sys_mmap(8 * 32) as *i64
1396 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
1397 if mnf < 0 { return 0 - 1 }
1398 let stt: *u8 = sv_get(mks, mvs, mnf, "status" as *u8)
1399 if (stt as i64) != 0 { if se_seq(stt, "open" as *u8) == 0 { return sv2_embed_results(prefix, base, id, out, cap) } }
1400 let qks: *i64 = sys_mmap(8 * 32) as *i64
1401 let qvs: *i64 = sys_mmap(8 * 32) as *i64
1402 let qnf: i64 = se_qst(prefix, id, 1, qks, qvs, 32)
1403 if qnf < 0 { return 0 - 1 }
1404 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
1405 let kind: *u8 = sv_get(qks, qvs, qnf, "kind" as *u8)
1406 let prompt: *u8 = sv_get(qks, qvs, qnf, "prompt" as *u8)
1407 if (qid as i64) == 0 { return 0 - 1 }
1408 var o: i64 = sv2_embed_top(out, 0, prompt)
1409 o = se_cat(out, o, "<p class='q'>" as *u8)
1410 o = sv2_esc(out, o, prompt)
1411 o = se_cat(out, o, "</p><form method='post' action='" as *u8)
1412 o = se_cat(out, o, base)
1413 o = se_cat(out, o, "/embed/" as *u8)
1414 o = se_cat(out, o, id)
1415 o = se_cat(out, o, "'><input type='hidden' id='tok' name='tok' value='" as *u8)
1416 o = se_cat(out, o, tok)
1417 o = se_cat(out, o, "'>" as *u8)
1418 let kc: i64 = kind[0] as i64
1419 if kc == 67 {
1420 let opts: *u8 = sv_get(qks, qvs, qnf, "opts" as *u8)
1421 let no: i64 = se_nopts(opts)
1422 let ob: *u8 = sys_mmap(96)
1423 var k: i64 = 0
1424 while k < no {
1425 se_opt(opts, k, ob, 96)
1426 o = se_cat(out, o, "<label class='opt'><input type='radio' name='" as *u8)
1427 o = se_cat(out, o, qid)
1428 o = se_cat(out, o, "' value='" as *u8)
1429 o = se_catn(out, o, k)
1430 o = se_cat(out, o, "'>" as *u8)
1431 o = sv2_esc(out, o, ob)
1432 o = se_cat(out, o, "</label>" as *u8)
1433 k = k + 1
1434 }
1435 } else {
1436 let lo: i64 = se_field_n(qks, qvs, qnf, "min" as *u8, 0)
1437 let hi: i64 = se_field_n(qks, qvs, qnf, "max" as *u8, 10)
1438 var v: i64 = lo
1439 while v <= hi {
1440 o = se_cat(out, o, "<label class='opt'><input type='radio' name='" as *u8)
1441 o = se_cat(out, o, qid)
1442 o = se_cat(out, o, "' value='" as *u8)
1443 o = se_catn(out, o, v)
1444 o = se_cat(out, o, "'>" as *u8)
1445 o = se_catn(out, o, v)
1446 o = se_cat(out, o, "</label>" as *u8)
1447 v = v + 1
1448 }
1449 }
1450 o = se_cat(out, o, "<button type='submit'>Vote</button></form>" as *u8)
1451 o = se_cat(out, o, "<p class='by'>via <a href='" as *u8)
1452 o = se_cat(out, o, base)
1453 o = se_cat(out, o, "/s/" as *u8)
1454 o = se_cat(out, o, id)
1455 o = se_cat(out, o, "' target='_top'>Nishi Pulse</a></p><script src='" as *u8)
1456 o = se_cat(out, o, base)
1457 o = se_cat(out, o, "/app.js'></script></body></html>" as *u8)
1458 return o
1459}
1460// sv2_finalize -- transport wrapper: ensure every response carries Content-Length + Connection: close so a
1461// strict HTTP/1.1 client (our own sovereign browser, behind the buffered edge that keeps the client socket
1462// alive) reads the body cleanly instead of hanging to the timeout. Responses that already declare a
1463// Content-Length (file/js downloads) pass through untouched. Applied once in the daemon accept loop.
1464func sv2_finalize(resp: *u8, n: i64, out: *u8, cap: i64) -> i64 {
1465 var he: i64 = 0 - 1
1466 var i: i64 = 0
1467 while i + 3 < n {
1468 if resp[i] == (13 as u8) { if resp[i+1] == (10 as u8) { if resp[i+2] == (13 as u8) { if resp[i+3] == (10 as u8) { he = i; i = n } } } }
1469 i = i + 1
1470 }
1471 if he < 0 {
1472 var c0: i64 = 0
1473 while c0 < n { if c0 < cap { out[c0] = resp[c0] } c0 = c0 + 1 }
1474 return n
1475 }
1476 if sv2_memhas(resp, he, "Content-Length" as *u8) == 1 {
1477 var c1: i64 = 0
1478 while c1 < n { if c1 < cap { out[c1] = resp[c1] } c1 = c1 + 1 }
1479 return n
1480 }
1481 let bodylen: i64 = n - (he + 4)
1482 var o: i64 = 0
1483 var h: i64 = 0
1484 while h < he { out[o] = resp[h]; o = o + 1; h = h + 1 }
1485 o = se_cat(out, o, "\r\nContent-Length: " as *u8)
1486 o = se_catn(out, o, bodylen)
1487 o = se_cat(out, o, "\r\nConnection: close\r\n\r\n" as *u8)
1488 var b: i64 = he + 4
1489 while b < n { if o < cap { out[o] = resp[b]; o = o + 1 } b = b + 1 }
1490 return o
1491}
1492func sv2_serve_js(out: *u8, cap: i64) -> i64 {
1493 let fb: *u8 = sys_mmap(65536)
1494 let fl: i64 = sv2_read_file("survey_app.js" as *u8, fb, 65535)
1495 if fl <= 0 { return sv2_err(out, "404 Not Found" as *u8, "app.js missing" as *u8) }
1496 var o: i64 = se_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: application/javascript; charset=utf-8\r\nContent-Length: " as *u8)
1497 o = se_catn(out, o, fl)
1498 o = se_cat(out, o, "\r\nCache-Control: max-age=300\r\n\r\n" as *u8)
1499 var i: i64 = 0
1500 while i < fl { if o + i < cap { out[o + i] = fb[i] } i = i + 1 }
1501 return o + fl
1502}
1503
1504// the app: request bytes -> response bytes (mount-agnostic; keypath = admin key file, FAIL-CLOSED)
1505func sv2_handle(prefix: *u8, base: *u8, keypath: *u8, req: *u8, reqlen: i64, out: *u8, cap: i64) -> i64 {
1506 // path token between the first two spaces
1507 let fullpath: *u8 = sys_mmap(400)
1508 var i: i64 = 0
1509 while i < reqlen { if req[i] == (32 as u8) { i = i + 1; break } i = i + 1 }
1510 var t: i64 = 0
1511 while i < reqlen { if req[i] == (32 as u8) { break } if t < 399 { fullpath[t] = req[i]; t = t + 1 } i = i + 1 }
1512 fullpath[t] = 0 as u8
1513 // capture the query (for ?t=<tok>) THEN strip it off the routing path
1514 let qbuf: *u8 = sys_mmap(256)
1515 qbuf[0] = 0 as u8
1516 var qi: i64 = 0
1517 while fullpath[qi] != (0 as u8) {
1518 if fullpath[qi] == (63 as u8) {
1519 var qo: i64 = 0
1520 var qj: i64 = qi + 1
1521 while fullpath[qj] != (0 as u8) { if qo < 255 { qbuf[qo] = fullpath[qj]; qo = qo + 1 } qj = qj + 1 }
1522 qbuf[qo] = 0 as u8
1523 fullpath[qi] = 0 as u8
1524 } else { qi = qi + 1 }
1525 }
1526 // strip the mount prefix
1527 var path: *u8 = fullpath
1528 if base[0] != (0 as u8) {
1529 var bm: i64 = 1
1530 var bi: i64 = 0
1531 while base[bi] != (0 as u8) { if fullpath[bi] != base[bi] { bm = 0; bi = se_slen(base) } else { bi = bi + 1 } }
1532 if bm == 1 { path = (fullpath as i64 + se_slen(base)) as *u8 }
1533 }
1534 var is_post: i64 = 0
1535 if req[0] == (80 as u8) { if req[1] == (79 as u8) { is_post = 1 } }
1536
1537 if is_post == 1 {
1538 // same-origin guard (when Origin is present it must contain our Host) -- EXCEPT /embed/ which is
1539 // cross-origin BY DESIGN (a one-question poll embedded on another site). Safe: an embed vote is an
1540 // anonymous poll answer, revote-replace caps inflation per token, no sensitive/state-changing action.
1541 if se_starts(path, "/embed/" as *u8) == 0 {
1542 let ob: *u8 = sys_mmap(300)
1543 let hb: *u8 = sys_mmap(300)
1544 sv2_hdr_get(req, reqlen, "Origin:" as *u8, ob, 300)
1545 sv2_hdr_get(req, reqlen, "Host:" as *u8, hb, 300)
1546 if ob[0] != (0 as u8) { if hb[0] != (0 as u8) {
1547 if sv2_memhas(ob, se_slen(ob), hb) == 0 { return sv2_err(out, "403 Forbidden" as *u8, "cross-origin POST refused" as *u8) }
1548 } }
1549 }
1550 var bs: i64 = 0 - 1
1551 var j: i64 = 0
1552 while j + 3 < reqlen {
1553 if req[j] == (13 as u8) { if req[j+1] == (10 as u8) { if req[j+2] == (13 as u8) { if req[j+3] == (10 as u8) { bs = j + 4; j = reqlen } } } }
1554 j = j + 1
1555 }
1556 if bs < 0 { return sv2_err(out, "400 Bad Request" as *u8, "no body" as *u8) }
1557 let body: *u8 = (req as i64 + bs) as *u8
1558 let blen: i64 = reqlen - bs
1559 // ---- admin lane (key-file gated, fail-closed) ----
1560 if se_starts(path, "/admin/" as *u8) == 1 {
1561 let key: *u8 = sys_mmap(256)
1562 sv2_form_get(body, blen, "key" as *u8, key, 256)
1563 if sv2_admin_ok(keypath, key) == 0 { return sv2_err(out, "403 Forbidden" as *u8, "admin key refused (fail-closed)" as *u8) }
1564 if se_seq(path, "/admin/load" as *u8) == 1 {
1565 let spec: *u8 = sys_mmap(65536)
1566 let slen: i64 = sv2_form_get(body, blen, "spec" as *u8, spec, 65536)
1567 if slen <= 0 { return sv2_err(out, "400 Bad Request" as *u8, "empty spec" as *u8) }
1568 let idb: *u8 = sys_mmap(64)
1569 let nq: i64 = se_load(prefix, spec, slen, idb, 64)
1570 if nq < 1 {
1571 var eo: i64 = sv2_err(out, "400 Bad Request" as *u8, "spec refused, code " as *u8)
1572 eo = se_catn(out, eo - 1, nq)
1573 out[eo] = 10 as u8
1574 return eo + 1
1575 }
1576 var o: i64 = se_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nLOADED id=" as *u8)
1577 o = se_cat(out, o, idb)
1578 o = se_cat(out, o, " nq=" as *u8)
1579 o = se_catn(out, o, nq)
1580 o = se_catc(out, o, 10)
1581 return o
1582 }
1583 if se_seq(path, "/admin/close" as *u8) == 1 {
1584 let idb: *u8 = sys_mmap(64)
1585 sv2_form_get(body, blen, "id" as *u8, idb, 64)
1586 if se_id_ok(idb) == 0 { return sv2_err(out, "400 Bad Request" as *u8, "bad id" as *u8) }
1587 if se_close(prefix, idb) != 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1588 var o: i64 = se_cat(out, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nCLOSED " as *u8)
1589 o = se_cat(out, o, idb)
1590 o = se_catc(out, o, 10)
1591 return o
1592 }
1593 if se_seq(path, "/admin/report" as *u8) == 1 {
1594 let idb: *u8 = sys_mmap(64)
1595 sv2_form_get(body, blen, "id" as *u8, idb, 64)
1596 if se_id_ok(idb) == 0 { return sv2_err(out, "400 Bad Request" as *u8, "bad id" as *u8) }
1597 let n: i64 = sv2_report(prefix, idb, out, cap)
1598 if n < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1599 return n
1600 }
1601 if se_seq(path, "/admin/insights" as *u8) == 1 {
1602 let idb: *u8 = sys_mmap(64)
1603 sv2_form_get(body, blen, "id" as *u8, idb, 64)
1604 if se_id_ok(idb) == 0 { return sv2_err(out, "400 Bad Request" as *u8, "bad id" as *u8) }
1605 let n: i64 = sv2_insights(prefix, base, idb, out, cap)
1606 if n < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1607 return n
1608 }
1609 return sv2_err(out, "404 Not Found" as *u8, "no such admin route" as *u8)
1610 }
1611 // ---- public respond: POST /s/<id> ----
1612 if se_starts(path, "/s/" as *u8) == 1 {
1613 let id: *u8 = sys_mmap(64)
1614 var pq: i64 = 3
1615 var it: i64 = 0
1616 while path[pq] != (0 as u8) { if path[pq] == (47 as u8) { break } if it < 63 { id[it] = path[pq]; it = it + 1 } pq = pq + 1 }
1617 id[it] = 0 as u8
1618 if se_id_ok(id) == 0 { return sv2_err(out, "404 Not Found" as *u8, "bad survey id" as *u8) }
1619 let tok: *u8 = sys_mmap(96)
1620 sv2_form_get(body, blen, "tok" as *u8, tok, 96)
1621 // gather answers: one form field per question qid
1622 let mks: *i64 = sys_mmap(8 * 32) as *i64
1623 let mvs: *i64 = sys_mmap(8 * 32) as *i64
1624 let mnf: i64 = se_meta(prefix, id, mks, mvs, 32)
1625 if mnf < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1626 let nq: i64 = se_field_n(mks, mvs, mnf, "nq" as *u8, 0)
1627 let aq: *i64 = sys_mmap(8 * (nq + 2)) as *i64
1628 let av: *i64 = sys_mmap(8 * (nq + 2)) as *i64
1629 var na: i64 = 0
1630 var q: i64 = 1
1631 while q <= nq {
1632 let qks: *i64 = sys_mmap(8 * 32) as *i64
1633 let qvs: *i64 = sys_mmap(8 * 32) as *i64
1634 let qnf: i64 = se_qst(prefix, id, q, qks, qvs, 32)
1635 if qnf > 0 {
1636 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
1637 if (qid as i64) != 0 {
1638 let vb: *u8 = sys_mmap(4096)
1639 let vl: i64 = sv2_form_get(body, blen, qid, vb, 4096)
1640 if vl > 0 {
1641 // stable copy of the qid (question record buffers stay alive per request)
1642 aq[na] = qid as i64
1643 av[na] = vb as i64
1644 na = na + 1
1645 }
1646 }
1647 }
1648 q = q + 1
1649 }
1650 let rc: i64 = se_respond(prefix, id, tok, aq, av, na)
1651 if rc == 1 {
1652 var o: i64 = se_cat(out, 0, "HTTP/1.1 303 See Other\r\nLocation: " as *u8)
1653 o = se_cat(out, o, base)
1654 o = se_cat(out, o, "/s/" as *u8)
1655 o = se_cat(out, o, id)
1656 o = se_cat(out, o, "/done\r\n\r\n" as *u8)
1657 return o
1658 }
1659 if rc == (0 - 2) { return sv2_err(out, "409 Conflict" as *u8, "survey is closed" as *u8) }
1660 if rc == (0 - 8) { return sv2_err(out, "409 Conflict" as *u8, "this survey has reached its response quota (full)" as *u8) }
1661 if rc == (0 - 5) { return sv2_err(out, "400 Bad Request" as *u8, "bad respondent token" as *u8) }
1662 if rc == (0 - 3) { return sv2_err(out, "400 Bad Request" as *u8, "missing or invalid answer" as *u8) }
1663 if rc == (0 - 6) { return sv2_err(out, "400 Bad Request" as *u8, "unknown question answered" as *u8) }
1664 if rc == (0 - 1) { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1665 return sv2_err(out, "500 Internal Server Error" as *u8, "store write failed" as *u8)
1666 }
1667 // ---- embed poll vote: POST /embed/<id> (cross-origin allowed; first question = the poll) ----
1668 if se_starts(path, "/embed/" as *u8) == 1 {
1669 let id: *u8 = sys_mmap(64)
1670 var pq: i64 = 7
1671 var it: i64 = 0
1672 while path[pq] != (0 as u8) { if path[pq] == (47 as u8) { break } if it < 63 { id[it] = path[pq]; it = it + 1 } pq = pq + 1 }
1673 id[it] = 0 as u8
1674 if se_id_ok(id) == 0 { return sv2_err(out, "404 Not Found" as *u8, "bad poll id" as *u8) }
1675 let tok: *u8 = sys_mmap(96)
1676 sv2_form_get(body, blen, "tok" as *u8, tok, 96)
1677 let qks: *i64 = sys_mmap(8 * 32) as *i64
1678 let qvs: *i64 = sys_mmap(8 * 32) as *i64
1679 let qnf: i64 = se_qst(prefix, id, 1, qks, qvs, 32)
1680 if qnf < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such poll" as *u8) }
1681 let qid: *u8 = sv_get(qks, qvs, qnf, "qid" as *u8)
1682 if (qid as i64) == 0 { return sv2_err(out, "404 Not Found" as *u8, "no such poll" as *u8) }
1683 let aq: *i64 = sys_mmap(8 * 4) as *i64
1684 let av: *i64 = sys_mmap(8 * 4) as *i64
1685 var na: i64 = 0
1686 let vb: *u8 = sys_mmap(4096)
1687 let vl: i64 = sv2_form_get(body, blen, qid, vb, 4096)
1688 if vl > 0 { aq[0] = qid as i64; av[0] = vb as i64; na = 1 }
1689 let rc: i64 = se_respond(prefix, id, tok, aq, av, na)
1690 // recorded, benign revote, or a closed poll -> show the compact tally (poll UX). bad input -> loud.
1691 if rc == 1 { return sv2_embed_results(prefix, base, id, out, cap) }
1692 if rc == (0 - 2) { return sv2_embed_results(prefix, base, id, out, cap) }
1693 if rc == (0 - 8) { return sv2_embed_results(prefix, base, id, out, cap) }
1694 if rc == (0 - 3) { return sv2_err(out, "400 Bad Request" as *u8, "pick an option to vote" as *u8) }
1695 if rc == (0 - 5) { return sv2_err(out, "400 Bad Request" as *u8, "bad token" as *u8) }
1696 return sv2_err(out, "404 Not Found" as *u8, "no such poll" as *u8)
1697 }
1698 return sv2_err(out, "404 Not Found" as *u8, "no such POST route" as *u8)
1699 }
1700
1701 // ---- GET routes ----
1702 if se_seq(path, "/app.js" as *u8) == 1 { return sv2_serve_js(out, cap) }
1703 if se_seq(path, "/" as *u8) == 1 { return sv2_home(prefix, base, out, cap) }
1704 if path[0] == (0 as u8) { return sv2_home(prefix, base, out, cap) }
1705 // embeddable one-question poll (iframe-friendly, compact): GET /embed/<id>
1706 if se_starts(path, "/embed/" as *u8) == 1 {
1707 let id: *u8 = sys_mmap(64)
1708 var pq: i64 = 7
1709 var it: i64 = 0
1710 while path[pq] != (0 as u8) { if path[pq] == (47 as u8) { break } if it < 63 { id[it] = path[pq]; it = it + 1 } pq = pq + 1 }
1711 id[it] = 0 as u8
1712 if se_id_ok(id) == 0 { return sv2_err(out, "404 Not Found" as *u8, "bad poll id" as *u8) }
1713 let tok: *u8 = sys_mmap(96)
1714 var mo: i64 = se_cat(tok, 0, "e" as *u8)
1715 mo = se_catn(tok, mo, sys_now_us())
1716 tok[mo] = 0 as u8
1717 let n: i64 = sv2_embed(prefix, base, id, tok, out, cap)
1718 if n < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such poll" as *u8) }
1719 return n
1720 }
1721 if se_starts(path, "/s/" as *u8) == 1 {
1722 let id: *u8 = sys_mmap(64)
1723 var pq: i64 = 3
1724 var it: i64 = 0
1725 while path[pq] != (0 as u8) { if path[pq] == (47 as u8) { break } if it < 63 { id[it] = path[pq]; it = it + 1 } pq = pq + 1 }
1726 id[it] = 0 as u8
1727 if se_id_ok(id) == 0 { return sv2_err(out, "404 Not Found" as *u8, "bad survey id" as *u8) }
1728 let sub: *u8 = (path as i64 + 3 + it) as *u8
1729 if se_seq(sub, "/results.json" as *u8) == 1 {
1730 let n: i64 = sv2_results_json(prefix, id, out, cap)
1731 if n < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1732 return n
1733 }
1734 if se_seq(sub, "/results" as *u8) == 1 {
1735 let n: i64 = sv2_results(prefix, base, id, out, cap)
1736 if n < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1737 return n
1738 }
1739 if se_seq(sub, "/done" as *u8) == 1 { return sv2_done(base, id, out) }
1740 if sub[0] != (0 as u8) { return sv2_err(out, "404 Not Found" as *u8, "no such route" as *u8) }
1741 // token from ?t=<tok> or freshly minted (app.js swaps in the browser's stable token)
1742 let tok: *u8 = sys_mmap(96)
1743 tok[0] = 0 as u8
1744 var qp: i64 = 0
1745 while qbuf[qp] != (0 as u8) {
1746 var att: i64 = 0
1747 if qp == 0 { att = 1 } else { if qbuf[qp-1] == (38 as u8) { att = 1 } }
1748 if att == 1 { if qbuf[qp] == (116 as u8) { if qbuf[qp+1] == (61 as u8) {
1749 var to2: i64 = 0
1750 var qj2: i64 = qp + 2
1751 var go2: i64 = 1
1752 while go2 == 1 {
1753 if qbuf[qj2] == (0 as u8) { go2 = 0 } else {
1754 if qbuf[qj2] == (38 as u8) { go2 = 0 } else {
1755 if to2 < 95 { tok[to2] = qbuf[qj2]; to2 = to2 + 1 }
1756 qj2 = qj2 + 1
1757 }
1758 }
1759 }
1760 tok[to2] = 0 as u8
1761 } } }
1762 qp = qp + 1
1763 }
1764 if se_tok_ok(tok) == 0 {
1765 var mo: i64 = se_cat(tok, 0, "v" as *u8)
1766 mo = se_catn(tok, mo, sys_now_us())
1767 tok[mo] = 0 as u8
1768 }
1769 let n: i64 = sv2_form(prefix, base, id, tok, out, cap)
1770 if n < 0 { return sv2_err(out, "404 Not Found" as *u8, "no such survey" as *u8) }
1771 return n
1772 }
1773 return sv2_err(out, "404 Not Found" as *u8, "no such route" as *u8)
1774}
1775
1776// ---- offline selftest body (consumed by _hdl_build/nx_survey_serve_gate) ----
1777func sv2_ck(name: *u8, cond: i64, pass: *i64, fail: *i64) -> i64 {
1778 if cond == 1 { sv2_p(" PASS " as *u8); sv2_p(name); sv2_p("\n" as *u8); pass[0] = pass[0] + 1; return 1 }
1779 sv2_p(" FAIL " as *u8); sv2_p(name); sv2_p("\n" as *u8); fail[0] = fail[0] + 1
1780 return 0
1781}
1782// build+run one POST against sv2_handle (body must be pre-urlencoded)
1783func sv2_post(prefix: *u8, base: *u8, keypath: *u8, path: *u8, body: *u8, origin: *u8, out: *u8, cap: i64) -> i64 {
1784 let req: *u8 = sys_mmap(262144)
1785 var ro: i64 = se_cat(req, 0, "POST " as *u8)
1786 ro = se_cat(req, ro, path)
1787 ro = se_cat(req, ro, " HTTP/1.1\r\nHost: nishifamily.com" as *u8)
1788 if origin[0] != (0 as u8) {
1789 ro = se_cat(req, ro, "\r\nOrigin: " as *u8)
1790 ro = se_cat(req, ro, origin)
1791 }
1792 ro = se_cat(req, ro, "\r\nContent-Length: " as *u8)
1793 ro = se_catn(req, ro, se_slen(body))
1794 ro = se_cat(req, ro, "\r\n\r\n" as *u8)
1795 ro = se_cat(req, ro, body)
1796 return sv2_handle(prefix, base, keypath, req, ro, out, cap)
1797}
1798func sv2_get(prefix: *u8, base: *u8, keypath: *u8, path: *u8, out: *u8, cap: i64) -> i64 {
1799 let req: *u8 = sys_mmap(4096)
1800 var ro: i64 = se_cat(req, 0, "GET " as *u8)
1801 ro = se_cat(req, ro, path)
1802 ro = se_cat(req, ro, " HTTP/1.1\r\nHost: nishifamily.com\r\n\r\n" as *u8)
1803 return sv2_handle(prefix, base, keypath, req, ro, out, cap)
1804}
1805func sv2_selftest() -> i64 {
1806 sv2_p("=== NX-SURVEY-SERVE SELFTEST (forms, ballots, aggregates, k-floor, admin lane, XSS, mount) ===\n" as *u8)
1807 let pass: *i64 = sys_mmap(16) as *i64
1808 let fail: *i64 = sys_mmap(16) as *i64
1809 pass[0] = 0
1810 fail[0] = 0
1811 // hermetic store prefix + admin key file
1812 let pfx: *u8 = sys_mmap(64)
1813 var pp: i64 = se_cat(pfx, 0, "/tmp/svys_" as *u8)
1814 pp = se_catn(pfx, pp, sys_now_us())
1815 pp = se_catc(pfx, pp, 45)
1816 pfx[pp] = 0 as u8
1817 let keypath: *u8 = sys_mmap(64)
1818 var kp: i64 = se_cat(keypath, 0, "/tmp/svyk_" as *u8)
1819 kp = se_catn(keypath, kp, sys_now_us())
1820 keypath[kp] = 0 as u8
1821 sv2_write_file(keypath, "test-admin-key-2026\n" as *u8, 20)
1822 let base: *u8 = sys_mmap(4)
1823 base[0] = 0 as u8
1824 let noorig: *u8 = sys_mmap(4)
1825 noorig[0] = 0 as u8
1826 let out: *u8 = sys_mmap(1048576)
1827 // S1 admin load (title carries a <script> probe for the XSS teeth)
1828 var n: i64 = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+pulse%0A%40title+Pulse+%3Cscript%3Ealert%281%29%3C%2Fscript%3E%0A%40anon+1%0A%40kmin+2%0AQ%7Cq1%7CC%7CBest+day%7CMon%7CTue%7CWed%0A" as *u8, noorig, out, 1048576)
1829 var s1: i64 = 0
1830 if sv2_memhas(out, n, "LOADED id=pulse nq=1" as *u8) == 1 { s1 = 1 }
1831 sv2_ck("S1 admin load -> LOADED nq=1" as *u8, s1, pass, fail)
1832 // S2 home lists it, escaped
1833 n = sv2_get(pfx, base, keypath, "/" as *u8, out, 1048576)
1834 var s2: i64 = 0
1835 if sv2_memhas(out, n, "<script>" as *u8) == 1 { if sv2_memhas(out, n, "<script>alert" as *u8) == 0 { if sv2_memhas(out, n, "kopen" as *u8) == 1 { s2 = 1 } } }
1836 sv2_ck("S2 home lists survey; title XSS-escaped" as *u8, s2, pass, fail)
1837 // S3 form renders radios + token
1838 n = sv2_get(pfx, base, keypath, "/s/pulse" as *u8, out, 1048576)
1839 var s3: i64 = 0
1840 if sv2_memhas(out, n, "name='q1' value='2'" as *u8) == 1 { if sv2_memhas(out, n, "name='tok'" as *u8) == 1 { if sv2_memhas(out, n, "<script>" as *u8) == 1 { s3 = 1 } } }
1841 sv2_ck("S3 form renders 3 options + hidden token; prompt escaped" as *u8, s3, pass, fail)
1842 // S3b ?t= token honored
1843 n = sv2_get(pfx, base, keypath, "/s/pulse?t=alice-tok" as *u8, out, 1048576)
1844 sv2_ck("S3b personal-link token lands in the form" as *u8, sv2_memhas(out, n, "value='alice-tok'" as *u8), pass, fail)
1845 // S4 respond -> 303 done
1846 n = sv2_post(pfx, base, keypath, "/s/pulse" as *u8, "tok=alice-tok&q1=1" as *u8, noorig, out, 1048576)
1847 var s4: i64 = 0
1848 if sv2_memhas(out, n, "303 See Other" as *u8) == 1 { if sv2_memhas(out, n, "/s/pulse/done" as *u8) == 1 { s4 = 1 } }
1849 sv2_ck("S4 respond -> 303 to done" as *u8, s4, pass, fail)
1850 // S5 k-floor: 1 ballot < kmin 2 -> withheld
1851 n = sv2_get(pfx, base, keypath, "/s/pulse/results" as *u8, out, 1048576)
1852 sv2_ck("S5 k-floor withholds below quorum" as *u8, sv2_memhas(out, n, "Results withheld" as *u8), pass, fail)
1853 // S6 second ballot -> results show
1854 sv2_post(pfx, base, keypath, "/s/pulse" as *u8, "tok=bob-tok00&q1=1" as *u8, noorig, out, 1048576)
1855 n = sv2_get(pfx, base, keypath, "/s/pulse/results" as *u8, out, 1048576)
1856 var s6: i64 = 0
1857 if sv2_memhas(out, n, "<b>2</b> response(s)" as *u8) == 1 { if sv2_memhas(out, n, "class='bar'" as *u8) == 1 { s6 = 1 } }
1858 sv2_ck("S6 at quorum: n=2 + bars render" as *u8, s6, pass, fail)
1859 // S6b anonymity: tokens never appear in public results
1860 var s6b: i64 = 1
1861 if sv2_memhas(out, n, "alice-tok" as *u8) == 1 { s6b = 0 }
1862 if sv2_memhas(out, n, "bob-tok00" as *u8) == 1 { s6b = 0 }
1863 sv2_ck("S6b tokens never rendered on results" as *u8, s6b, pass, fail)
1864 // S7 revote replaces
1865 sv2_post(pfx, base, keypath, "/s/pulse" as *u8, "tok=alice-tok&q1=2" as *u8, noorig, out, 1048576)
1866 n = sv2_get(pfx, base, keypath, "/s/pulse/results" as *u8, out, 1048576)
1867 sv2_ck("S7 revote: still 2 responses (replaced, not added)" as *u8, sv2_memhas(out, n, "<b>2</b> response(s)" as *u8), pass, fail)
1868 // S8 invalid answer 400
1869 n = sv2_post(pfx, base, keypath, "/s/pulse" as *u8, "tok=carol-tok&q1=9" as *u8, noorig, out, 1048576)
1870 sv2_ck("S8 invalid option -> 400" as *u8, sv2_memhas(out, n, "400" as *u8), pass, fail)
1871 // S9 cross-origin POST 403
1872 n = sv2_post(pfx, base, keypath, "/s/pulse" as *u8, "tok=evil-tok0&q1=1" as *u8, "http://evil.example" as *u8, out, 1048576)
1873 sv2_ck("S9 cross-origin POST -> 403" as *u8, sv2_memhas(out, n, "403" as *u8), pass, fail)
1874 // S10 admin close -> respond 409, form shows closed
1875 n = sv2_post(pfx, base, keypath, "/admin/close" as *u8, "key=test-admin-key-2026&id=pulse" as *u8, noorig, out, 1048576)
1876 var s10: i64 = 0
1877 if sv2_memhas(out, n, "CLOSED pulse" as *u8) == 1 {
1878 n = sv2_post(pfx, base, keypath, "/s/pulse" as *u8, "tok=dave-tok0&q1=1" as *u8, noorig, out, 1048576)
1879 if sv2_memhas(out, n, "409" as *u8) == 1 { s10 = 1 }
1880 }
1881 sv2_ck("S10 close freezes: respond -> 409" as *u8, s10, pass, fail)
1882 n = sv2_get(pfx, base, keypath, "/s/pulse" as *u8, out, 1048576)
1883 sv2_ck("S10b closed form points to results" as *u8, sv2_memhas(out, n, "This survey is closed" as *u8), pass, fail)
1884 // S11 admin auth: wrong key + fail-closed missing keyfile
1885 n = sv2_post(pfx, base, keypath, "/admin/close" as *u8, "key=wrong-key-here-x&id=pulse" as *u8, noorig, out, 1048576)
1886 var s11: i64 = sv2_memhas(out, n, "403" as *u8)
1887 let nokey: *u8 = "/tmp/svyk_absent_never" as *u8
1888 n = sv2_post(pfx, base, nokey, "/admin/close" as *u8, "key=test-admin-key-2026&id=pulse" as *u8, noorig, out, 1048576)
1889 var s11b: i64 = sv2_memhas(out, n, "403" as *u8)
1890 var s11ok: i64 = 0
1891 if s11 == 1 { if s11b == 1 { s11ok = 1 } }
1892 sv2_ck("S11 wrong key 403 + absent keyfile FAIL-CLOSED 403" as *u8, s11ok, pass, fail)
1893 // S12 deep survey with admin-only results
1894 n = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+deep%0A%40title+Deep%0A%40anon+0%0A%40kmin+1%0A%40results+admin%0A%40maxtext+60%0AQ%7Cw1%7CS%7C0%7C10%7CFood+security%0AQ%7Cw2%7CT%7CWhat+would+help%0A" as *u8, noorig, out, 1048576)
1895 var s12: i64 = sv2_memhas(out, n, "LOADED id=deep nq=2" as *u8)
1896 n = sv2_post(pfx, base, keypath, "/s/deep" as *u8, "tok=dave-tok0&w1=3&w2=need+steady+work" as *u8, noorig, out, 1048576)
1897 var s12b: i64 = sv2_memhas(out, n, "303" as *u8)
1898 var s12ok: i64 = 0
1899 if s12 == 1 { if s12b == 1 { s12ok = 1 } }
1900 sv2_ck("S12 deep survey loads + records (scale + text)" as *u8, s12ok, pass, fail)
1901 n = sv2_get(pfx, base, keypath, "/s/deep/results" as *u8, out, 1048576)
1902 sv2_ck("S12b @results admin -> public results 403" as *u8, sv2_memhas(out, n, "403" as *u8), pass, fail)
1903 n = sv2_post(pfx, base, keypath, "/admin/report" as *u8, "key=test-admin-key-2026&id=deep" as *u8, noorig, out, 1048576)
1904 var s12c: i64 = 0
1905 if sv2_memhas(out, n, "need steady work" as *u8) == 1 { if sv2_memhas(out, n, "avg=3.00" as *u8) == 1 { if sv2_memhas(out, n, "[rsp:deep:dave-tok0]" as *u8) == 1 { s12c = 1 } } }
1906 sv2_ck("S12c admin report carries text answer + avg + identified token (anon=0)" as *u8, s12c, pass, fail)
1907 // S13 unknown survey 404
1908 n = sv2_get(pfx, base, keypath, "/s/ghost" as *u8, out, 1048576)
1909 sv2_ck("S13 unknown survey -> 404" as *u8, sv2_memhas(out, n, "404" as *u8), pass, fail)
1910 // S14 mount prefix + query strip
1911 let mbase: *u8 = "/survey" as *u8
1912 n = sv2_get(pfx, mbase, keypath, "/survey/s/deep?b=cache" as *u8, out, 1048576)
1913 var s14: i64 = 0
1914 if sv2_memhas(out, n, "action='/survey/s/deep'" as *u8) == 1 { if sv2_memhas(out, n, "/survey/app.js" as *u8) == 1 { s14 = 1 } }
1915 sv2_ck("S14 mounted at /survey: query stripped, links carry prefix" as *u8, s14, pass, fail)
1916 n = sv2_get(pfx, mbase, keypath, "/survey" as *u8, out, 1048576)
1917 sv2_ck("S14b /survey bare -> home" as *u8, sv2_memhas(out, n, "Nishi Pulse" as *u8), pass, fail)
1918 // S15 done page
1919 n = sv2_get(pfx, base, keypath, "/s/deep/done" as *u8, out, 1048576)
1920 sv2_ck("S15 done page renders" as *u8, sv2_memhas(out, n, "Thank you" as *u8), pass, fail)
1921 // ---- S16-S19 ANALYTICS: load a 5-question survey, respond, check public metrics + admin insights ----
1922 n = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+ana%0A%40title+Ana%0A%40anon+0%0A%40kmin+1%0AQ%7Cfood%7CS%7C0%7C10%7CFood+security%0AQ%7Coverall%7CS%7C0%7C10%7COverall+wellbeing%0AQ%7Chouse%7CC%7CHousing%7CStable%7CRisk%7CCrisis%0AQ%7Cwork%7CC%7CWork%7CEmployed%7CUnemployed%0AQ%7Chelp%7CT%7CWhat+helps%0A" as *u8, noorig, out, 1048576)
1923 var s16load: i64 = sv2_memhas(out, n, "LOADED id=ana nq=5" as *u8)
1924 sv2_post(pfx, base, keypath, "/s/ana" as *u8, "tok=aa000000&food=10&overall=9&house=0&work=0&help=need+work" as *u8, noorig, out, 1048576)
1925 sv2_post(pfx, base, keypath, "/s/ana" as *u8, "tok=bb000000&food=3&overall=4&house=2&work=1&help=housing+help" as *u8, noorig, out, 1048576)
1926 sv2_post(pfx, base, keypath, "/s/ana" as *u8, "tok=cc000000&food=7&overall=7&house=0&work=0&help=need+work+now" as *u8, noorig, out, 1048576)
1927 sv2_post(pfx, base, keypath, "/s/ana" as *u8, "tok=dd000000&food=2&overall=3&house=1&work=1&help=more+work" as *u8, noorig, out, 1048576)
1928 sv2_ck("S16 analytics survey loaded + 4 ballots" as *u8, s16load, pass, fail)
1929 // public results carry the whole-sample insight strip (top-2-box + margin + NPS)
1930 n = sv2_get(pfx, base, keypath, "/s/ana/results" as *u8, out, 1048576)
1931 var s17: i64 = 0
1932 if sv2_memhas(out, n, "Top-2-box" as *u8) == 1 { if sv2_memhas(out, n, "margin" as *u8) == 1 { if sv2_memhas(out, n, "NPS" as *u8) == 1 { s17 = 1 } } }
1933 sv2_ck("S17 public results show top-2-box + margin + NPS" as *u8, s17, pass, fail)
1934 // admin insights report: descriptive + concern + segment breakdown + cross-tab chi-square + drivers + themes
1935 n = sv2_post(pfx, base, keypath, "/admin/insights" as *u8, "key=test-admin-key-2026&id=ana" as *u8, noorig, out, 1048576)
1936 var s18: i64 = 0
1937 if sv2_memhas(out, n, "Insights report" as *u8) == 1 {
1938 if sv2_memhas(out, n, "Scale summary" as *u8) == 1 {
1939 if sv2_memhas(out, n, "Concern flags" as *u8) == 1 {
1940 if sv2_memhas(out, n, "chi-square" as *u8) == 1 {
1941 if sv2_memhas(out, n, "Key drivers" as *u8) == 1 {
1942 if sv2_memhas(out, n, "Themes" as *u8) == 1 { s18 = 1 } } } } } }
1943 sv2_ck("S18 admin insights: summary+concern+cross-tab+drivers+themes render" as *u8, s18, pass, fail)
1944 // the open-text theme 'work' surfaces in the report
1945 sv2_ck("S18b open-text theme 'work' surfaces" as *u8, sv2_memhas(out, n, ">work " as *u8), pass, fail)
1946 // insights is admin-gated: wrong key -> 403, no leak
1947 n = sv2_post(pfx, base, keypath, "/admin/insights" as *u8, "key=wrong-key-here-x&id=ana" as *u8, noorig, out, 1048576)
1948 var s19: i64 = 0
1949 if sv2_memhas(out, n, "403" as *u8) == 1 { if sv2_memhas(out, n, "Insights report" as *u8) == 0 { s19 = 1 } }
1950 sv2_ck("S19 insights admin-gated: wrong key 403, no leak" as *u8, s19, pass, fail)
1951 // ---- S20 DATA QUALITY: a survey with a @check attention item; clean + careless responses screened ----
1952 n = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+qa%0A%40title+QA%0A%40anon+1%0A%40kmin+1%0AQ%7Cs1%7CS%7C0%7C10%7CScale+one%0AQ%7Cs2%7CS%7C0%7C10%7CScale+two%0AQ%7Cs3%7CS%7C0%7C10%7CScale+three%0AQ%7Catt%7CC%7CAttention+pick+B%7CA%7CB%7CC%0A%40check+att+1%0A" as *u8, noorig, out, 1048576)
1953 var s20load: i64 = sv2_memhas(out, n, "LOADED id=qa nq=4" as *u8)
1954 sv2_post(pfx, base, keypath, "/s/qa" as *u8, "tok=qa-clean0&s1=8&s2=6&s3=7&att=1" as *u8, noorig, out, 1048576)
1955 sv2_post(pfx, base, keypath, "/s/qa" as *u8, "tok=qa-carel0&s1=5&s2=5&s3=5&att=0" as *u8, noorig, out, 1048576)
1956 sv2_ck("S20 QA survey with @check loaded + clean/careless responses" as *u8, s20load, pass, fail)
1957 n = sv2_post(pfx, base, keypath, "/admin/insights" as *u8, "key=test-admin-key-2026&id=qa" as *u8, noorig, out, 1048576)
1958 var s20: i64 = 0
1959 if sv2_memhas(out, n, "Data quality" as *u8) == 1 {
1960 if sv2_memhas(out, n, "Failed an attention check" as *u8) == 1 {
1961 if sv2_memhas(out, n, "Straight-lined" as *u8) == 1 { s20 = 1 } }
1962 }
1963 sv2_ck("S20b insights renders Data quality (attention + straight-line screening)" as *u8, s20, pass, fail)
1964 // ---- S21 finalize: strict-HTTP/1.1 Content-Length for the sovereign browser behind the buffered edge ----
1965 let fsrc: *u8 = sys_mmap(256)
1966 var fo: i64 = se_cat(fsrc, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<b>hi</b>" as *u8)
1967 let fdst: *u8 = sys_mmap(512)
1968 let fn: i64 = sv2_finalize(fsrc, fo, fdst, 512)
1969 var s21: i64 = 0
1970 if sv2_memhas(fdst, fn, "Content-Length: 9" as *u8) == 1 { if sv2_memhas(fdst, fn, "<b>hi</b>" as *u8) == 1 { s21 = 1 } }
1971 sv2_ck("S21 finalize adds Content-Length (body=9) for strict clients" as *u8, s21, pass, fail)
1972 let gsrc: *u8 = sys_mmap(128)
1973 var go: i64 = se_cat(gsrc, 0, "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc" as *u8)
1974 let gdst: *u8 = sys_mmap(128)
1975 let gnn: i64 = sv2_finalize(gsrc, go, gdst, 128)
1976 var s21b: i64 = 0
1977 if gnn == go { s21b = 1 }
1978 sv2_ck("S21b response already carrying Content-Length passes through unchanged" as *u8, s21b, pass, fail)
1979 // ---- S22 results.json: machine-readable aggregate API on the 'ana' survey (4 ballots from S16) ----
1980 n = sv2_get(pfx, base, keypath, "/s/ana/results.json" as *u8, out, 1048576)
1981 var s22: i64 = 0
1982 if sv2_memhas(out, n, "application/json" as *u8) == 1 {
1983 if sv2_memhas(out, n, "\"survey\":\"ana\"" as *u8) == 1 {
1984 if sv2_memhas(out, n, "\"responses\":4" as *u8) == 1 {
1985 if sv2_memhas(out, n, "\"mean_permille\":5500" as *u8) == 1 {
1986 if sv2_memhas(out, n, "\"nps\":-25" as *u8) == 1 { s22 = 1 } } } }
1987 }
1988 sv2_ck("S22 results.json: exact aggregates (survey/responses/mean_permille/nps)" as *u8, s22, pass, fail)
1989 var s22b: i64 = 0
1990 if sv2_memhas(out, n, "\"label\":\"Stable\"" as *u8) == 1 { if sv2_memhas(out, n, "\"count\":2" as *u8) == 1 { if sv2_memhas(out, n, "\"text_count\":4" as *u8) == 1 { s22b = 1 } } }
1991 sv2_ck("S22b results.json: choice options + text_count present" as *u8, s22b, pass, fail)
1992 // PRIVACY: free-text answers must NEVER appear in the machine API
1993 var s22c: i64 = 1
1994 if sv2_memhas(out, n, "housing help" as *u8) == 1 { s22c = 0 }
1995 if sv2_memhas(out, n, "need work" as *u8) == 1 { s22c = 0 }
1996 sv2_ck("S22c results.json privacy: no free-text answers leak" as *u8, s22c, pass, fail)
1997 // admin-results survey -> results.json 403
1998 n = sv2_get(pfx, base, keypath, "/s/deep/results.json" as *u8, out, 1048576)
1999 sv2_ck("S22d results.json honors @results admin -> 403" as *u8, sv2_memhas(out, n, "403" as *u8), pass, fail)
2000 // ---- S23 EMBEDDABLE POLL: /embed/<id> compact iframe poll + cross-origin vote ----
2001 n = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+poll1%0A%40title+Poll+One%0A%40anon+1%0AQ%7Cq1%7CC%7CFavorite%7CApple%7CBanana%7CCherry%0A" as *u8, noorig, out, 1048576)
2002 var s23load: i64 = sv2_memhas(out, n, "LOADED id=poll1 nq=1" as *u8)
2003 sv2_ck("S23 poll1 (1-Q) loaded" as *u8, s23load, pass, fail)
2004 n = sv2_get(pfx, base, keypath, "/embed/poll1" as *u8, out, 1048576)
2005 var s23b: i64 = 0
2006 if sv2_memhas(out, n, "action='/embed/poll1'" as *u8) == 1 { if sv2_memhas(out, n, "name='q1'" as *u8) == 1 { if sv2_memhas(out, n, "Apple" as *u8) == 1 { if sv2_memhas(out, n, "/app.js" as *u8) == 1 { s23b = 1 } } } }
2007 sv2_ck("S23b GET /embed/poll1 = compact poll (radios + action + app.js)" as *u8, s23b, pass, fail)
2008 // cross-origin vote (embedded on another site) MUST be accepted, not 403
2009 n = sv2_post(pfx, base, keypath, "/embed/poll1" as *u8, "tok=embedtoken1&q1=1" as *u8, "http://tandem.example" as *u8, out, 1048576)
2010 var s23c: i64 = 0
2011 if sv2_memhas(out, n, "403" as *u8) == 0 { if sv2_memhas(out, n, "response(s)" as *u8) == 1 { if sv2_memhas(out, n, "class='bar'" as *u8) == 1 { s23c = 1 } } }
2012 sv2_ck("S23c cross-origin embed vote accepted (NOT 403) -> inline results bars" as *u8, s23c, pass, fail)
2013 // the results page surfaces the embed snippet
2014 n = sv2_get(pfx, base, keypath, "/s/poll1/results" as *u8, out, 1048576)
2015 var s23d: i64 = 0
2016 if sv2_memhas(out, n, "Embed this poll" as *u8) == 1 { if sv2_memhas(out, n, "/embed/poll1" as *u8) == 1 { s23d = 1 } }
2017 sv2_ck("S23d results page surfaces the embed snippet (tools-in-UI)" as *u8, s23d, pass, fail)
2018 // ---- S24 RESPONSE QUOTA: @quota caps DISTINCT respondents; revote exempt; form shows progress/full ----
2019 n = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+cap1%0A%40title+Capped%0A%40quota+2%0AQ%7Cq1%7CC%7CPick%7CYes%7CNo%0A" as *u8, noorig, out, 1048576)
2020 sv2_ck("S24 quota survey cap1 loaded (@quota 2)" as *u8, sv2_memhas(out, n, "LOADED id=cap1 nq=1" as *u8), pass, fail)
2021 n = sv2_get(pfx, base, keypath, "/s/cap1" as *u8, out, 1048576)
2022 sv2_ck("S24b open form shows progress '0 of 2 responses received'" as *u8, sv2_memhas(out, n, "0 of 2 responses received" as *u8), pass, fail)
2023 n = sv2_post(pfx, base, keypath, "/s/cap1" as *u8, "tok=qa-tok0&q1=0" as *u8, noorig, out, 1048576)
2024 sv2_ck("S24c respondent A accepted -> 303 (1 of 2)" as *u8, sv2_memhas(out, n, "303" as *u8), pass, fail)
2025 n = sv2_post(pfx, base, keypath, "/s/cap1" as *u8, "tok=qb-tok0&q1=1" as *u8, noorig, out, 1048576)
2026 sv2_ck("S24d respondent B accepted -> 303 (2 of 2 = full)" as *u8, sv2_memhas(out, n, "303" as *u8), pass, fail)
2027 n = sv2_post(pfx, base, keypath, "/s/cap1" as *u8, "tok=qc-tok0&q1=0" as *u8, noorig, out, 1048576)
2028 var s24e: i64 = 0
2029 if sv2_memhas(out, n, "409" as *u8) == 1 { if sv2_memhas(out, n, "quota" as *u8) == 1 { s24e = 1 } }
2030 sv2_ck("S24e NEW respondent C REFUSED -> 409 quota reached" as *u8, s24e, pass, fail)
2031 n = sv2_post(pfx, base, keypath, "/s/cap1" as *u8, "tok=qa-tok0&q1=1" as *u8, noorig, out, 1048576)
2032 sv2_ck("S24f EXISTING respondent A revote accepted -> 303 (exempt, never inflates)" as *u8, sv2_memhas(out, n, "303" as *u8), pass, fail)
2033 n = sv2_get(pfx, base, keypath, "/s/cap1" as *u8, out, 1048576)
2034 var s24g: i64 = 0
2035 if sv2_memhas(out, n, "reached its response quota" as *u8) == 1 { if sv2_memhas(out, n, "See the results" as *u8) == 1 { if sv2_memhas(out, n, "name='q1'" as *u8) == 0 { s24g = 1 } } }
2036 sv2_ck("S24g full form WITHHOLDS the questionnaire, offers results link" as *u8, s24g, pass, fail)
2037 // ---- S25 OPEN-TEXT SENTIMENT: the admin Tone card scores written answers (warming/cooling + split) ----
2038 n = sv2_post(pfx, base, keypath, "/admin/load" as *u8, "key=test-admin-key-2026&spec=%40survey+tone1%0A%40title+Tone+Test%0AQ%7Cc1%7CT%7CHow+are+things%3F%0A" as *u8, noorig, out, 1048576)
2039 sv2_ck("S25 tone survey (1 text question) loaded" as *u8, sv2_memhas(out, n, "LOADED id=tone1 nq=1" as *u8), pass, fail)
2040 n = sv2_post(pfx, base, keypath, "/s/tone1" as *u8, "tok=tt-a1&c1=the+staff+were+very+helpful+and+kind" as *u8, noorig, out, 1048576)
2041 n = sv2_post(pfx, base, keypath, "/s/tone1" as *u8, "tok=tt-b1&c1=not+helpful+at+all" as *u8, noorig, out, 1048576)
2042 n = sv2_post(pfx, base, keypath, "/s/tone1" as *u8, "tok=tt-c1&c1=the+meeting+is+at+noon" as *u8, noorig, out, 1048576)
2043 n = sv2_post(pfx, base, keypath, "/admin/insights" as *u8, "key=test-admin-key-2026&id=tone1" as *u8, noorig, out, 1048576)
2044 var s25: i64 = 0
2045 if sv2_memhas(out, n, "Tone —" as *u8) == 1 { if sv2_memhas(out, n, "Warming" as *u8) == 1 { if sv2_memhas(out, n, "1 positive" as *u8) == 1 { if sv2_memhas(out, n, "1 negative" as *u8) == 1 { s25 = 1 } } } }
2046 sv2_ck("S25a Tone card: Warming + 1 positive + 1 negative (mean +136, negation-aware)" as *u8, s25, pass, fail)
2047 sv2_ck("S25b mean tone +136 permille rendered" as *u8, sv2_memhas(out, n, "+136‰" as *u8), pass, fail)
2048 // negation adversary end-to-end: 'not helpful' must NOT be miscounted positive (would be 2 pos / 0 neg)
2049 var s25c: i64 = 0
2050 if sv2_memhas(out, n, "2 positive" as *u8) == 0 { if sv2_memhas(out, n, "1 negative" as *u8) == 1 { s25c = 1 } }
2051 sv2_ck("S25c NEGATION honored live: 'not helpful' scored negative, not positive" as *u8, s25c, pass, fail)
2052 // sentiment is admin-walled: the PUBLIC results page must NOT carry the Tone card
2053 n = sv2_get(pfx, base, keypath, "/s/tone1/results" as *u8, out, 1048576)
2054 var s25d: i64 = 0
2055 if sv2_memhas(out, n, "Tone —" as *u8) == 0 { s25d = 1 }
2056 sv2_ck("S25d sentiment is admin-only: public results has NO Tone card" as *u8, s25d, pass, fail)
2057 sv2_p("NX-SURVEY-SERVE SELFTEST pass=" as *u8)
2058 sv2_pn(pass[0])
2059 sv2_p(" fail=" as *u8)
2060 sv2_pn(fail[0])
2061 if fail[0] == 0 { sv2_p(" verdict=GREEN (survey surface: ballots, aggregates, k-floor, admin lane, privacy walls)\n" as *u8); return 0 }
2062 sv2_p(" verdict=RED\n" as *u8)
2063 return 1
2064}